与快速排序一样,我们将输入数组进行递归划分,与快速排序不同的是,快速排序会递归处理划分的两边,而Randomized_Select()只处理划分一边。快速排序的期望运行时间是 nlg(n),而Randomized_Select()期望运行时间是n。
Randomized_Select()的最坏运行时间为n平方,即使找最小元素也是如此,因为在每次划分是可能极不走运地总是按余下的元素中最大的来进行划分。
算法思想:
1.调用q=Randomized_Partition(A,p,r)随机返回一个主元,在数组[p,r]中第 q-p小。
2 k=q-p+1 为比A[q]元素小的个数(包括本身)
3.判断i 与 k 的大小,若相等,即A[q]为第 i 小,返回A[q]。
4.若i < k,则所需元素落在划分的低区 递归调用 Randomized_Select(A,p,q-1,i)
5.若i > k,则所需元素落在划分的高区 因为已经知道有k个值小于第 i 小的元素,则递归调用 Randomized_Select(A,q+1,r,i-k)
以下是例程:
#include <STDIO.H>
#include <STDLIB.H>
#include <TIME.H>
int A[]={5,7,1,4,3,2,9,8,6};
int Length = sizeof(A)/sizeof(A[0])-1;
int Partition(int *A,int p,int r)
{
int x=A[r];
int i=p-1;
int j;
int temp;
for (j=p;j<=r-1;j++)
{
if (A[j]<=x)
{
i=i+1;
temp=A[i];
A[i]=A[j];
A[j]=temp;
}
}
temp=A[i+1];
A[i+1]=A[r];
A[r]=temp;
return i+1;
}
int Randomized_Partition(int *A,int p,int r) //随机一个元素作为主元,数组分成两份
{
int temp;
int c=(int)(((int)rand()%(r-p+1))+ p);
temp=A[r];
A[r]=A[c];
A[c]=temp;
return Partition(A,p,r);
}
int Randomized_Select(int *A,int p,int r,int i) //选择函数
{
int q,k;
if (p == r)
return A[p];
q = Randomized_Partition(A,p,r);
k = q-p+1;
if(i == k)
return A[q];
else if( i<k )
return Randomized_Select(A,p,q-1,i); //不可能调用0个元素的子数组,因为在上一层 p==r 时即已经被返回
else
return Randomized_Select(A,q+1,r,i-k);
}
int main()
{
srand((int)time(NULL)); //随机种子
printf("%d\n",Randomized_Select(A,0,Length,4));
return 0;
}
像Randomized_Select()一样,Select算法通过对数组的递归划分来找出所需元素。Select使用的也是来自快速排序的确定性划分算法Partition,但做了修改,把划分的主元也作为输入参数,Partition(int *A,int p,int r,int x),x为数组中被指定的主元。最坏情况运行时间为O(n).详细原理及步骤见《算法导论》。
Select(int *A,int p,int r,int i)算法思想:
1.将输入数组的n个元素划分为n/5组,每组5个元素,且至多只有一组由剩下的n mod 5个元素组成。
2.首先对每组元素进行插入排序,然后确定每组有序元素的中位数。
3.递归调用Select(),找出上述所有组中位数的中位数,若为偶数个,可以约定用哪一个。
4.调用Partition(int *A,int p,int r,int x),x为上一步中的中位数,k低区元素数目多1,因此x为第k小的元素。
5.若i == k ,返回 x ,如果i < k,则在低区递归调用Select(),若 i > k ,则在高区递归查找第i -k小的元素。
#include <STDIO.H>
#include <STDLIB.H>
void Insert_Sort(int *A,int p, int r)
{
int i,j,key;
for (j=p+1;j<=r;j++)
{
key = A[j];
i = j-1;
while(i>=p && A[i]>key)
{
A[i+1] = A[i];
i = i-1;
}
A[i+1] = key;
}
}
int Part_Insert_Sort(int *A,int p,int r,int *B) //对数组A[]分组,每组5个元素,分别进行插入排序
{
int i=0;
int Length = r-p+1;
if (Length < 5) //元素个数少于5个
{
Insert_Sort(A,p,r);
B[i]=A[p+(Length-1)%5/2];
}
else
{
for (i=0;i<Length/5;i++)
{
Insert_Sort(A,p+i*5,p+i*5+4);
B[i]=A[i*5+2]; //B[i] 存储各组中位数
}
if ( Length%5 != 0 )
{
Insert_Sort(A,Length-1-(Length-1)%5,Length-1);
B[i]=A[Length-1-Length%5/2]; //B[i] 存储最后一组中位数
}
}
return i; // 返回分组的个数
}
int Partition(int *A,int p,int r,int x) //修改过后的Partition(),把主元x作为输入参数
{
int i=p-1;
int j;
int temp;
int adr=0; //保存主元的位置
int flag=0;
for (j=p;j<=r;j++)
{
if (A[j]<x)
{
i=i+1;
temp=A[i];
A[i]=A[j];
A[j]=temp;
if (i == adr && flag)
{
adr = j;
}
}
else if ( A[j] == x)
{
flag = 1;
adr=j;
}
}
if ( flag && (i+1)!= adr )
{
temp=A[i+1];
A[i+1]=A[adr];
A[adr]=temp;
}
return i+1; //返回小于主元x的元素个数-1
}
int Select(int A[],int p,int r,int i)
{
int q,k,x;
int *B = (int *)malloc(sizeof(int)*(((r-p)+1)/5+1));
int Length_B=0;
if (p == r)
return A[p];
Length_B = Part_Insert_Sort(A,p,r,B); //分组进行插入排序,B[]存储各组的中位数
x=Select(B,0,Length_B,Length_B/2+1); //x为各组中位数的中位数。若为偶数个,取较小的作为中位数
free(B);
q = Partition(A,p,r,x);
k = q-p+1;
if(i == k)
return A[q];
else if( i<k )
return Select(A,p,q-1,i);
else
return Select(A,q+1,r,i-k);
}
int main()
{
int A[]={5,4,12,13,7,6,1,14,8,9,3,11,10,2};
int Length_A = sizeof(A)/sizeof(A[0])-1;
int i=0;
for (i=0;i<=Length_A;i++)
{
printf("%d\n",Select(A,0,Length_A,i+1));
}
return 0;
}