您的位置:首页 > 其它

寻找数组中最小的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))。

/*利用快速排序的思想实现寻找数组中最小的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)的复杂度.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: