1.期望爲線性時間的選擇算法
與快速排序一樣,我們將輸入數組進行遞歸劃分,與快速排序不同的是,快速排序會遞歸處理劃分的兩邊,而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;
}
2.最壞情況爲線性時間的選擇算法
像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;
}