1. 0、 咱们先简单的理解,要求一个序列中最小的k个数,按照惯有的思维方式,很简单,先对这个序列从小到大排序,然后输出前面的最小的k个数即可。
2. 1、 至于选取什么的排序方法,我想你可能会第一时间想到快速排序,我们知道,快速排序平均所费时间为n*logn,然后再遍历序列中前k个元素输出,即可,总的时间复杂度为O(n*logn+k)=O(n*logn)。。
3. 3、 当然,更好的办法是维护k个元素的最大堆,原理与上述第2个方案一致,即用容量为k的最大堆存储最先遍历到的k个数,并假设它们即是最小的k个数,建堆费时O(k)后,有k1<k2<…<kmax(kmax设为大顶堆中最大元素)。继续遍历数列,每次遍历一个元素x,与堆顶元素比较,x<kmax,更新堆(用时logk),否则不更新堆。这样下来,总费时O(k+(n-k)*logk)=O(n*logk)。此方法得益于在堆中,查找等各项操作时间复杂度均为logk(不然,就如上述思路2所述:直接用数组也可以找出前k个小的元素,用时O(n*k))。
4. 4、 按编程之美第141页上解法二的所述,类似快速排序的划分方法,N个数存储在数组S中,再从数组中随机选取一个数X(随机选取枢纽元,可做到线性期望时间O(N)的复杂度),把数组划分为Sa和Sb俩部分,Sa<=X<=Sb,如果要查找的k个元素小于Sa的元素个数,则返回Sa中较小的k个元素,否则返回Sa中k个小的元素+Sb中小的k-|Sa|个元素。像上述过程一样,这个运用类似快速排序的partition的快速选择SELECT算法寻找最小的k个元素,在最坏情况下亦能做到O(N)的复杂度。不过值得一提的是,这个快速选择SELECT算法是选取数组中“中位数的中位数”作为枢纽元,而非随机选取枢纽元。
编程之美的答案:
5. Kbig(S, k):
6. if(k <= 0):
7. return [] // 返回空数组
8. if(length S <= k):
9. return S
10. (Sa, Sb) = Partition(S)
11. return Kbig(Sa, k).Append(Kbig(Sb, k – length Sa)
12.
13. Partition(S):
14. Sa = [] // 初始化为空数组
15. Sb = [] // 初始化为空数组
16. Swap(s[1], S[Random()%length S]) // 随机选择一个数作为分组标准,以
17. // 避免特殊数据下的算法退化,也可
18. // 以通过对整个数据进行洗牌预处理
19. // 实现这个目的
20. p = S[1]
21. for i in [2: length S]:
22. S[i] > p ? Sa.Append(S[i]) : Sb.Append(S[i])
23. // 将p加入较小的组,可以避免分组失败,也使分组
24. // 更均匀,提高效率
25. length Sa < length Sb ? Sa.Append(p) : Sb.Append(p)
26. return (Sa, Sb)