前面学习了在期望时间内选择任意顺序统计量,这次就运用这种顺序统计量的选择去实现线性时间的选择。
1.算法概述
1.将集合分成ceil(n/5)个组,即每个组中的元素都为5,最后一组元素数量为n mod 5
2.对每个组分别使用插入排序,并寻找出每个组的中位数。
3.每个组的中位数形成数量为ceil(n/5)的集合,在此集合内再求其中位数,即中位数的中位数,记为x。(这里就要再次递归调用select函数)
4.使用分区函数partition(就是上次笔记里学习到的partition分区),得到索引i,索引位置之前的低分区要比索引位置之后的高分区数量x小1
5.如果i=k,则返回x;如果i<k,则递归调用低分区(left,i-1,k);如果i>k,则递归调用高分区(i+1,right,k-i)
以上就是整个算法的过程,看起来比较的笼统,因为真正涉及到实现的时候有挺多的细节问题要注意到。尤其是在第(5)步递归调用的时候,需要控制好边界条件和分区函数的思路一样。
2.Java实现
public class SelectN {
public static void InsertSort(int[] arr,int start,int end) {
for(int i=start;i<=end;i++) {
for(int j=start;j<i;j++) {
if(arr[i]<arr[j]) {
int x=arr[i];
for(int k=i;k>j;k--) {
arr[k]=arr[k-1];
}
arr[j]=x;
break;
}
}
}
}
public static int partition(int[] arr,int start,int end,int key) {
int i=start;
int j=end;
while(true) {
while(i<end&&arr[i]<=key)
i++;
while(arr[j]>key)
j--;
if(i>=j)
break;
swap(arr,i,j);
}
swap(arr,findk(arr,key),j); //交换最终结果
return j;
}
//获得元素的索引
public static int findk(int[] arr,int key) {
for(int i=0;i<arr.length;i++)
if(arr[i]==key)
return i;
return -1;
}
public static int select(int[] arr,int left,int right,int k) {
if(right-left<5) {
InsertSort(arr,left,right);
return arr[left + k - 1];
}
int group=(right-left+5)/5;
for(int i=0;i<group;i++) {
int l=left+i*5;
int r;
if(left+i*5+4>right)
r=right;
else
r=left+5*i+4;
InsertSort(arr,l,r);
swap(arr,left+i,(r+l)/2); //将中位数都放在第一组集合中
}
InsertSort(arr,left,left+group-1); //对中位数进行排序
int line=select(arr,left,left+group-1,(group+1)/2);
int lower=partition(arr,left,right,line); //得到中位数的中位数分区后的索引
if(k==lower) //若索引等于k,则直接返回
return arr[lower];
else if(k<=lower-1)
return select(arr,left,lower-