[置顶] 递归与分治策略-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的总长度。在追求时间复杂度最低的路上,你表达了你最深沉的爱意。
相关文章推荐
- 递归与分治策略-2.9.1线性时间选择(随机划分基准)(第k小问题)
- 线性时间选择问题-第k小(大)问题-递归与分治
- 递归与分治策略之线性时间选择(随机划分线性选择)
- 递归与分治策略之线性选择(第k小元素)
- 线性时间选择问题——分治
- 选择问题:线性时间内找到序列的第k小的元素
- 线性时间选择第k小(递归)
- 线性时间选择第K小元素(随机化选择第K小元素)C++
- 线性时间选择:从n个元素中找出第k小的元素
- 用线性时间选择来找无序数组的中位数
- 算法导论-第九章-中位数和顺序统计量:最坏情况为线性时间的选择算法C++实现
- 在线性时间复杂度内求解第k小元素问题
- 线性选择序列第k小 / 中位数附近k个数
- 快速选择(quick select) + 线性时间选择(linear-time select) - 求出n个数中第k大的数
- 中位数和顺序统计,以线性期望时间做选择
- 选择问题(线性时间复杂度)
- 分治算法;随机化划分函数;快速排序;线性时间选择第K小元素;快速排序平均时间复杂度nlgn;
- 线性时间求第k小(分治思想)
- 递归分治策略——Java之棋盘覆盖问题解决
- 递归与分治策略之棋盘覆盖问题