寻找数组中最小的k个数 "利用快速排序的思想"
2013-10-18 00:33
344 查看
主要思想是:类似快速排序的划分方法, N个数存储在数组S中, 再从数组中随机选取一个数X(随机选取枢纽元, 可做到线性期望时间O(N)的复杂度), 把数组划分为Sa和Sb俩部分, Sa<=X<=Sb, 如果要查找的k个元素小于Sa的元素个数, 则返回Sa中较小的k个元素, 否则返回Sa中所有元素+Sb中小的k-|Sa|个元素. 像上述过程一样, 这个运用类似快速排序的partition的快速选择SELECT算法寻找最小的k个元素, 在最坏情况下亦能做到O(N)的复杂度.
不过值得一提的是, 这个快速选择SELECT算法是选取数组中"中位数的中位数"作为枢纽元, 而非随机选取枢纽元.
1. If |S| = 1, then k = 1 and return the elements in S as the answer. If a cutoff for small files is being used and |S| <=CUTOFF, then sort S and return the kth smallest element.
2. Pick a pivot element, v (- S.(选取一个枢纽元v属于S)
3. Partition S - {v} into S1 and S2, as was done with quicksort.
(将集合S-{v}分割成S1和S2,就像我们在快速排序中所作的那样)
4. If k <= |S1|, then the kth smallest element must be in S1. In this case, return quickselect (S1, k). If k = 1 + |S1|, then the pivot is the kth smallest element and we can return it as the answer. Otherwise, the kth smallest element lies in S2, and it
is the (k - |S1| - 1)st smallest element in S2. We make a recursive call and return quickselect (S2, k - |S1| - 1).
(如果k<=|S1|,那么第k个最小元素必然在S1中。在这种情况下,返回quickselect(S1,k)。如果k=1+|S1|,那么枢纽元素就是第k个最小元素,即找到,直接返回它。否则,这第k个最小元素就在S2中,即S2中的第(k-|S1|-1)个最小元素,我们递归调用并返回quickselect(S2,k-|S1|-1))。
1. 与快速排序相比, 快速选择只做了一次递归调用而不是两次. 快速选择的最坏情况和快速排序的相同, 也是O(N^2), 最坏情况发生在枢纽元的选取不当, 以致S1,或S2中有一个序列为空;
2. 这就好比快速排序的运行时间与划分时否对称有关, 划分的好或对称, 那么快速排序可达最佳的运行时间O(n*logn), 划分的不好或不对称, 则会有最坏的运行时间为O(N^2). 而枢纽元的选择完全决定快速排序的partition过程是否划分对称.
3. 快速选择也是一样, 如果枢纽元的选取不当, 则依然会有最坏的运行时间为O(N^2)的情况发生. 那么, 怎么避免这个最坏情况的发生, 或者说就算是最坏情况下, 亦能保证快速选择的运行时间为O(N)? 关键, 还是看你的枢纽元怎么选取.
4. 像上述程序使用三数中值作为枢纽元的方法可以使得最坏情况发生的概率几乎可以忽略不计. 然而, 通过一种更好的方法, 如“五分化中项的中项”, 或"中位数的中位数"等方法选取枢纽元, 我们将能彻底保证在最坏情况下依然是线性O(N)的复杂度.
不过值得一提的是, 这个快速选择SELECT算法是选取数组中"中位数的中位数"作为枢纽元, 而非随机选取枢纽元.
1. If |S| = 1, then k = 1 and return the elements in S as the answer. If a cutoff for small files is being used and |S| <=CUTOFF, then sort S and return the kth smallest element.
2. Pick a pivot element, v (- S.(选取一个枢纽元v属于S)
3. Partition S - {v} into S1 and S2, as was done with quicksort.
(将集合S-{v}分割成S1和S2,就像我们在快速排序中所作的那样)
4. If k <= |S1|, then the kth smallest element must be in S1. In this case, return quickselect (S1, k). If k = 1 + |S1|, then the pivot is the kth smallest element and we can return it as the answer. Otherwise, the kth smallest element lies in S2, and it
is the (k - |S1| - 1)st smallest element in S2. We make a recursive call and return quickselect (S2, k - |S1| - 1).
(如果k<=|S1|,那么第k个最小元素必然在S1中。在这种情况下,返回quickselect(S1,k)。如果k=1+|S1|,那么枢纽元素就是第k个最小元素,即找到,直接返回它。否则,这第k个最小元素就在S2中,即S2中的第(k-|S1|-1)个最小元素,我们递归调用并返回quickselect(S2,k-|S1|-1))。
/*利用快速排序的思想实现寻找数组中最小的k个元素*/ /* 1. If |S| = 1, then k = 1 and return the elements in S as the answer. If a cutoff for small files is being used and |S| <=CUTOFF, then sort S and return the kth smallest element. 2. Pick a pivot element, v (- S.(选取一个枢纽元v属于S) 3. Partition S - {v} into S1 and S2, as was done with quicksort.(将集合S-{v}分割成S1和S2,就像我们在快速排序中所作的那样) 4. If k <= |S1|, then the kth smallest element must be in S1. In this case, return quickselect (S1, k). If k = 1 + |S1|, then the pivot is the kth smallest element and we can return it as the answer. Otherwise, the kth smallest element lies in S2, and it is the (k - |S1| - 1)st smallest element in S2. We make a recursive call and return quickselect (S2, k - |S1| - 1). */ //q_select places the kth smallest element in a[k] void q_select(input_type a[], int k, int left, int right) { int i,j; input_type pivot; if( left+CUTOFF <= right) { pivot = median3( a, left, right); //取三数中值作为枢纽元,可以消除最坏情况而保证此算法是O(N)的.不过,这还只局限在理论意义上. i = left; j = right-1; for(;;) { while(a[++i]<pivot); while(a[--j]>pivot); if(i<j) swap(&a[i],&a[j]); else break; } swap(&a[i],&a[right-1]); //restore pivot if(k<i) q_select(a,k,left,i-1); else if(k>i) q_select(a,k-i,i+1,right); } else insert_sort(a,left,right); }
1. 与快速排序相比, 快速选择只做了一次递归调用而不是两次. 快速选择的最坏情况和快速排序的相同, 也是O(N^2), 最坏情况发生在枢纽元的选取不当, 以致S1,或S2中有一个序列为空;
2. 这就好比快速排序的运行时间与划分时否对称有关, 划分的好或对称, 那么快速排序可达最佳的运行时间O(n*logn), 划分的不好或不对称, 则会有最坏的运行时间为O(N^2). 而枢纽元的选择完全决定快速排序的partition过程是否划分对称.
3. 快速选择也是一样, 如果枢纽元的选取不当, 则依然会有最坏的运行时间为O(N^2)的情况发生. 那么, 怎么避免这个最坏情况的发生, 或者说就算是最坏情况下, 亦能保证快速选择的运行时间为O(N)? 关键, 还是看你的枢纽元怎么选取.
4. 像上述程序使用三数中值作为枢纽元的方法可以使得最坏情况发生的概率几乎可以忽略不计. 然而, 通过一种更好的方法, 如“五分化中项的中项”, 或"中位数的中位数"等方法选取枢纽元, 我们将能彻底保证在最坏情况下依然是线性O(N)的复杂度.
相关文章推荐
- 寻找数组中最小的k个数 "最小堆方法"
- 利用快速排序的思想寻找乱序数组第k大数
- 利用快排思想寻找数组中第K大(小)的数字
- 利用快速排序思想求数组第k大元素
- 利用快速排序思想找出数组中第K大的数
- 利用"线段树"相关算法解决有关数组的问题[待续]
- LA 3026 && POJ 1961 Period (利用kmp中的next数组找最小的循环节 )
- 利用快速排序中的切分函数寻找数组主元素
- [BUC-2.10]利用分治的思想求无序数组中的最大值和最小值以及求第二大的值(分治的思想可以使得比较的次数1.5N-2)
- 窥探算法之美妙——寻找数组中最小的K个数&python中巧用最大堆
- 寻找一维数组int num[5]中最大、最小及其坐标位置并 打印输出。(数组值从键盘读取)
- 编程之美-寻找数组中的最大值和最小值方法整理
- 【转载】利用"SQL"语句自动生成序号的两种方式。
- 数组与指针---都是"退化"惹的祸
- 第k大(小)数,寻找最小的k个数(进一步要求顺序与原数组中元素顺序一致)
- 小白成长日记(10)--使用快速排序寻找乱序数组中的第k大的值(c++)
- 编程之美_009寻找数组中的最大值和最小值
- 将数组"student a am i"改为"i am a student".
- 寻找数组中最小的k个数(快排和堆排)
- objective-C 数组二 "可变数组"