您的位置:首页 > 其它

[置顶] 递归与分治策略-2.9.2线性时间选择(取中位数的中位数基准)(第k小问题)

2017-10-14 14:04 411 查看
import java.util.Random;
/**
* 线性时间选择——选择中位数的中位数基准
*/
public class test2_9_2 {
static int n = 100;
static Comparable[] a = new Comparable
;   //暂定长度为100的数组
private static Comparable select(int left,int right,int k){
int n = right - left+1;
Comparable temp;
if(n<76){   //right-left<75
bubbleSort(left,right);
return a[left+k-1];
}
//1.找出每组中位数并依次置换在最前排
for(int i=0;i<=(n-5)/5;i++){
int s = left+i*5;   //每组第一个元素
int t = s + 4;      //每组最后一个元素
for(int j=0;j<3;j++)
bubble(s,t-j);  //遍历一次只做一次冒泡
temp = a[s+2];
a[s+2] = a[left+i];
a[left+i] = temp;
}
//2.递归找出前(n-5)/5个元素的中位数X
/**
* 这样写方便大家理解,简化下写第三个参数是(n+5)/10;因为要在划分组数n1除以2,
* 此数在计算机里int型整数会直接舍掉后面小数点,故需要进一位防止误差
*/
//虽然返回的是Comparable类型的X元素,不是X元素下标,但执行select算法时,对位于最前排的中位数的中位数已经排好序
Comparable x = select(left,left+(n-5)/5,(n-5)/5/2+1);
//3.根据之前两步确定的基准X利用快排思想进行划分,得出该基准对应下标,并排好左右大小
////3.1此步根据上一步得出的基准X遍历查询其下标,这样无需修改partition方法直接调用即可
for(int q=left;q<right;q++)
if(a[q].compareTo(x)==0){
temp = a[left];
a[left] = a[q];
a[q] = temp;
break;
}
////3.2得出X下标后开始划分,即使最坏情况下可以少处理1/4数组元素
int i = partition(left,right);
int j = i-left+1;
if(k<=j) return select(left,i,k);
else return select(i+1,right,k-j);
}
private static void bubble(int left,int right){  //冒泡排序,每次调用只起一次泡
Comparable temp;
for(int j=left;j<right;j++){
if(a[j+1].compareTo(
cdd2
a[j])<0){
temp = a[j+1];
a[j+1] = a[j];
a[j] = temp;
}
}
}
private static void bubbleSort(int left,int right){  //冒泡排序n-1次冒泡
Comparable temp;
for(int i=left;i<right;i++){
boolean YN = true;
for(int j=left;j<right-i;j++){
if(a[j+1].compareTo(a[j])<0){
temp = a[j+1];
a[j+1] = a[j];
a[j] = temp;
YN = false;
}
}
if(YN) break;
}
}
private static int partition(int left,int right){
int i = left,j = right+1;
Comparable x = a[left],temp;
while(true){
while(a[++i].compareTo(x)<0&&i<right);
while(a[--j].compareTo(x)>0);
if(i>=j){
a[left] = a[j];
a[j] = x;
break;
}
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
return j;
}
public static void main(String[] args) {
int k = 5;   //第k小元素
Random random = new Random();
System.out.print("排序前数组:");
for(int i=0;i<n;i++){
a[i] = random.nextInt(1000);   //产生n个0~1000的随机数并输入到数组里
System.out.print(a[i]+" ");
}
System.out.println();

System.out.println("第"+k+"小的数组元素是:"+select(0,a.length-1,k));

System.out.print("排序后数组:");
for(int i=0;i<n;i++){
System.out.print(a[i]+" ");
}

}
}


运行结果如下:

排序前数组:453 979 588 279 608 721 677 27...(后面省略n个元素)
第5小的数组元素是:84
排序后数组:18 27 35 52 84 97 102 134 ...(后面省略n个元素)


为深入理解select算法的特点,在进行输入大量数据时能明显发现前排元素已成序,而后面的元素还是乱序。eg:

排序后数组:18 27 35 52 84 97 102 134.....
697 915 644 640 608 717 792


总结:select算法是防止在随机线性时间选择算法中出现最坏情况(如随机选择基准可能会选择到边缘元素)下而产生的。总体核心算法步骤如下:

①找出每组(长度为5)中位数并依次置换在最前排。

②递归找出最前排(n/5)个元素的中位数。

③利用快排思想递归确定该中位数的位置并做划分,左小右大。



这样如果输入的元素数量级达到上百万上亿下,通过这样不断的划分,直到划分最后的长度小于一定数量级(如75)即可用简单排序算法(如冒泡)对前排元素进行完整排序,然后取出第k小元素。

感悟:费尽心思,取了一个又一个的中位数,只为考虑到存在最坏情况下,求一个更好的基准X。即便这样,每组遍历也只缩短了1/4的总长度。在追求时间复杂度最低的路上,你表达了你最深沉的爱意。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: