学习由n个不同的树的集合中选择第i个顺序统计量的问题。
输入:一个包含n个(互异的)数的集合A,1≤i≤n的整数i。
输出:元素x∈A,并且 A中恰好有i个元素小于它。
求解第i小的元素的算法设计
1. Max 和 Min
在一个有n个元素的集合中,确定Max 和 Min的方法是遍历。
同时找到最大值和最小值,我们只需要最多3⌊n/2⌋次比较就可以确定。具体的方法是对输入的元素成对的处理,首先我们将一对输入元素进行比较,然后将较小的与最小值比较,较大的与最大值比较。这样每两个元素仅需要三次比较。
2. 期望时间是线性时间的选择算法
//划分算法 A[q]作为划分数组的主元
PARTITION(A,p,q){
x=A[q]
i=p-1
for j=p to q-1
if(A[j]<=x){//将比A[q]小的数值统一按照顺序移动到前面
i=i+1
exchange A[i] with A[j]
}
exchange A[i+1] with A[q]
return i+1;
}
//随机划分算法 随机选择主元
RANDOMIZED-PARTITION(A,p,q){
i=Random(p,q)
exchange A[q] with A[i];
return PARTITION(A,p,q)
}
//Algorithms
//这里不是从整个数组中找,而是从其中的一段中找 递归的算法设计如下
RANDOMIZED-SELECT(A,p,q,i){
if(p==q)//检查递归的基本情况,A中仅仅包括一个元素这种情况下 i=1.
return A[p]
r=RANDOMIZED-PARTITION(A,p,q)//随机划分函数
k=r-p+1;//小于A[r]的元素的个数的计算
if(i==k){
return A[r]
else if(i<k)
RANDOMIZED-SELECT(A,p,r-1,i)
else
RANDOMIZED-SELECT(A,r+1,q,i)
}
}
如上 RANDOMIZED−SELECT(A,p,q,i)所示,在算法中首先检查数组仅包含一个元素的情况,i=1.其他的情况,我们调用随机划分函数RANDOMIZED−PARTITION(A,p,q),类似于快速排序中的思路,将数组A划分为A[p....r−1]和A[r+1...q],A[r]称为主元.然后计算前面部分的元素个数k=r−p+1;//小于A[r]的元素的个数的计算 ,后面进行检查和递归求解。
3. 最坏情况为线性的选择算法
指示器随机变量(indicator random variable):给定一个样本空间S和 事件A,那么事件A对应的指示器随机变量I{A}=1(如果A发生),0(如果A不发生);显而易见,事件A对应的指示器随机变量的期望等于事件A发生的概率。实例分析见指示器随机变量
类似于RANDOMIZED-SELECT算法,SELECT通过对输入数组的递归划分来找出所需元素,但是该算法能够保证对数组的一个好的划分。核心就是在划分主元的确定上,将PARTITION中的主元也作为输入参数进行输入。
具体算法的文字描述:
Step 1:把数组划分为若干个子数组,每个子数组里包含5个数,因为会有无法整除的可能,所以最后一个子数组可能会小于5.
Step 2:用插入排序把每个组的5个数排序,然后找出中位数。
Step 3:把获得的中位数又排序,找出中位数的中位数x。如果中位数的个数是偶数,那么取排好序的第 m/2 个数,m指的是中位数的个数。
Step 4:把原来的数组使用类似快排的方法,分成两个部分。一部分比x大,一部分比x小。我们可以假设左边的数小,右边的数大。然后我们可以得到“中位数的中位数”的位置i.
//这个地方是调用修改后的PARTITION,返回当前x的位置。 假设下标从1开始。
Step 5:如果i = k, 那么那个“中位数的中位数”就是第小的数。如果 i < k, 则在低区递归调用SELECT来找出第i小的元素,如果i > k, 则在高区递归查找第i-k大的元素。
分析算法:
整个过程中,第1,2,4步所需时间为O(n), 注意第2步的复杂度不为O(n^2),第3步的复杂度为 T(n/5),第五步的复杂度为 T(7n/10)。注意这里第2步虽然我们使用的是插入排序,但是待排的序列长度为常数5,所以对一组的排序时间花费为O(1),对于n/5个组,其时间预期是O(n/5),即O(n)。
时间预期为T(n)<=T(n/5)+T(7n/10+6)+O(n)
算法伪代码描述:
//设定主元参数的划分函数
PARTITION(A,p,q,key){
i=p-1;
for j=p to q-1{
if(A[j]<=key){//将比A[q]小的数值统一按照顺序移动到前面
i=i+1
exchange A[i] with A[j]
}
}
return i+1;
}
//---------------------------------
SELECT(A,p,q,i){
if(A.length<5){
InsertSort(A,p,q);
return A[i];
}
groups=A.length/5;
midValues[groups]=0;//声明初始化一个中位数数组
for(int t=0;t<groups;t++){
InsertSort(A,t*5,t*5+5);
midvalues[t] = A[t*5+3];//中
}
InsertSort(midvalues,0,groups);
if(groups/2==0){
x=midvalues[groups/2];//中位数的中位数作为主元
}else{
x=midvalues[groups/2+1];//中位数的中位数作为主元
}
r=PARTITION(A,p,q,x)//设定主元的划分函数
k=r-p+1;//小于A[r]的元素的个数的计算
if(i==k){
return A[r]
else if(i<k)
SELECT(A,p,r-1,i)
else
SELECT(A,r+1,q,i)
}
}