最坏情况线性时间的选择(找第i小的元素):
1.分组:每组最多5个元素(这里可以为5,7,...个元素);
2.找中位数:在每组中找出中位数;
3.中位数的中位数:找第2步中得到的中位数数组中的中位数;
4.划分:将第3步得到的中位数作为划分元素划分数组,返回中位数的位置k;
5.如果中位数是第i小的元素则返回,否则如果i<k,则在低区递归调用找第i小的元素;否则在高区递归找第i-k小的元素;
代码的实现稍微有一点不同。因为我们用C++编码的时候小标从0开始,最后测试了147个元素的情况来验证代码的正确性:
下面是简单的实现:
/*
x做划分元素
保证x肯定是其中的元素
*/
int Partition(DataType* A,int L,int H,DataType x)
{
int i=L-1,j;
for(j=L;j<=H;++j)
if(A[j]==x) break;
swap(A[j],A[H]);
for(j=L;j<H;++j)
{
if(A[j]<=x)
{
++i;
swap(A[i],A[j]);
}
}
swap(A[i+1],A[H]);
return i+1;
}
void InsertSort(DataType* A,int L,int H)
{
int i,j;
DataType key;
for(j=L+1;j<=H;++j)//插入排序
{
key = A[j];
i = j-1;
while(i>=L && A[i]>key)
{
A[i+1] = A[i];
--i;
}
A[i+1] = key;
}
}
inline int Mid(int L,int H)
{
return L+(H-L)/2;
}
DataType Median(DataType* A,int L,int H)//返回中位数
{
InsertSort(A,L,H);
return A[Mid(L,H)];
}
DataType Select(DataType* A,int L,int H,int i)
{
if(H-L<5)
{
InsertSort(A,L,H);
return A[L+i];
}
int j,left,right;
for(j=0; j<(H-L+5)/5; ++j)//分组
{
left=L+j*5, right=(L+j*5+4)>H ? H : L+j*5+4;
InsertSort(A,left,right);
swap(A[L+j], A[Mid(left,right)]);//把几个中位数放到前面
}
left = L;right = L+(H-L)/5;
DataType x = Select(A,left,right,Mid(left,right)-left); //中位数的中位数
int M = Partition(A,L,H,x);
int K = M-L; //左侧元素个数为M-L,右侧元素个数H-M-1,中间一个位划分元素
if(i==K) return A[M];
else if(i<K) return Select(A,L,M-1,i);
else return Select(A,M+1,H,i-K-1);
}
void TestSelect()
{
const int MAX = 147; //数据的数目
DataType A[MAX];
int i;
for(i=0;i<MAX;++i) A[i] = i+1;
random_shuffle(A,A+MAX);//随机排列,打乱顺序
cout<<"Init A:\n";
copy(A,A+MAX,ostream_iterator<DataType>(cout," "));
cout<<endl;
for(int i=0;i<MAX;++i)
{
cout<<i<<" th element: ";
cout<<Select(A,0,MAX-1,i)<<endl;
}
cout<<endl;
}