快速选择算法是根据快速排序的划分过程得来,它主要用于在O(n)时间内选择出一组序列的第i个顺序统计量,即第i个最大或最小的数。
对于在一组无序的序列中找出第i个顺序统计量,我们可以把序列进行排序即可,但是最小的时间复杂度是O(nlogn),另外一种方法就是在此说的快速选择算法。
我们可以回想一下快速排序的partition过程,该过程返回枢纽元素的位置i,那么就可说明比枢纽元素小的元素有i-1个,比枢纽元素大的元素有n-i个,因此枢纽元素就是第i个顺序统计量。假设我们要找出第k个顺序统计量,在每次调用partition后,比较k与i的大小:
k=i,枢纽元素就是要找的元素,直接返回。
k<i,要找的元素在枢纽元素的左边,递归调用partition在左边序列中找第k个顺序统计量。
k>i,要找的元素在枢纽元素的右边,递归调用partition在右边序列中找第k-i个顺序统计量。
我们知道,partition过程的时间复杂度是O(n),在快速选择算法中由于partition要多次递归调用自己,如果选择序列的第一个元素为枢纽元素,那么最坏情况下每次划分的低区或者高区至多只有一个元素,这样将会导致划分的极不平衡,最终导致快速选择算法的最坏时间复杂度为O(n^2),只能在平均情况下达到O(n)。因此,关键是选择一个合适的枢纽元素。下面的快速选择算法方法将能够选择出合适的枢纽元素并保证快速选择算法的最坏时间复杂度为O(n):
1.把序列每5个元素为一组进行划分,最后一组不足5个元素也规定为一组。
2.把每组元素使用插入排序进行排序。
3.找出每组的中位数,递归调用快速选择算法找出这些中位数的中位数。
4.以步骤3中找出的中位数的中位数为枢纽元调用修改过的partition过程。
5.比较步骤4中partition过程的返回值i与k的值,然后按照i与k的关系进行相应操作。
下面是我的实现:
/**< 快速选择算法用来在线性时间内找出集合中第i小的元素 */
#ifndef QUICKSELECT_HPP_INCLUDED
#define QUICKSELECT_HPP_INCLUDED
#include "SimpleSort.hpp"
const int GROUP_SIZE = 5; //组的长度
template <class Type>
Type quickSelect(Type *start, Type *end, int i);
//以每组GROUP_SIZE个元素分组并把每组排序
template<typename Type>
void groupAndSort(Type *start, Type *end)
{
for(Type *p=start; p<end; p += GROUP_SIZE) //划分5个元素一组,每组进行插入排序
{
if((end-p) >= GROUP_SIZE) //是否够5个元素
insertionSort(p, GROUP_SIZE);
else
insertionSort(p, end-p);
}
}
//求各组中位数的中位数
template<typename Type>
Type getMidOfMid(Type *start, Type *end)
{
int groups = (end-start-1)/GROUP_SIZE+1; //组数
Type *mids = new Type[groups]; //保存每组的中位数
int k=0;
for(Type *p=start; p<end; p += GROUP_SIZE)
{
if((end-p) >= GROUP_SIZE)
mids[k++] = *(p+(GROUP_SIZE-1)/2);
else
mids[k++] = *(p+(end-p-1)/2);
}
Type midOfmid = quickSelect(mids, mids+groups, (groups-1)/2+1);//递归查找中位数的中位数
delete []mids;
return midOfmid;
}
//以pat为枢纽元划分数组
template<typename Type>
Type* partition(Type *p, Type *r, const Type& pat)
{
r--;
while(p < r)
{
while(p<r && *r>pat) //一定要加p<r
r--;
while(p<r && *p<pat)//一定要加p<r
p++;
if(p<r)
{
swap(*r, *p);
r--;
p++;
}
}
return r;
}
//快速选择法查找第i个顺序统计量
template <class Type>
Type quickSelect(Type *start, Type *end, int i)
{
if(end-start<=1) //少于一个元素
return *start;
groupAndSort(start, end); //分组排序
Type midOfmid = getMidOfMid(start, end); //求中位数的中位数
Type *pat = partition(start, end, midOfmid); //划分
int length = pat-start+1; //不大于midOfmid的区间的长度
if(length < i) //若比i小,在高区查找i-length
return quickSelect(pat+1, end, i-length);
else if(length > i) //若比i大,在低区查找i
return quickSelect(start, pat+1, i);
else
return midOfmid; //相等则直接返回midOfmid
}
#endif // QUICKSELECT_HPP_INCLUDED