【快排】
- 核心思想:本来的问题规模很大,不断缩小问题规模
- 算法
假定待排序的数组为 arr ,start = 0,end = arr.length -1
(1)在[ start , end ] 范围中 选择一个arr[ index ],以这个数为基准,遍历将数组分成两半
(2)将原问题 变成了 [ start , index-1] 和 [ index+1, end] 两个子问题 ,对两个子问题 运用步骤(1)
(3)直到问题规模变成 1 ,就不在再解子问题
【代码】
private void swap(int[] numbers, int a, int b) { int temp = numbers[a]; numbers[a] = numbers[b]; numbers[b] = temp; } // 随机选择一个基准,将大于基准和小于基准的数分别放两边 private int partition(int[] numbers, int start, int end) { // if(start == end) return start; int index = new Random().nextInt(end-start)+start; swap(numbers, index, end); // 将基准放末尾 int smallCnt = start; // 小于基准个数 for(int i=start; i<end; ++i) { if(numbers[i] < numbers[end]) { if(i!=smallCnt) { swap(numbers, i, smallCnt); } smallCnt++; } } swap(numbers, smallCnt, end); // 将基准放中间 return smallCnt; } public void quickSort(int[] data, int start, int end) { if(end==start) return; int index = partition(data, start, end); if(index>start) { quickSort(data, start, index-1); } if(end>index) { quickSort(data, index+1, end); } }
运用快排的思想可以解决很多类似的问题:
运用快排中的partition函数,可以减小问题规模
1、找第 k 大或者第k小的数
private void swap(int[] numbers, int a, int b) { int temp = numbers[a]; numbers[a] = numbers[b]; numbers[b] = temp; } private int partition(int[] numbers, int start, int end) { if(start == end) return start; int index = new Random().nextInt(end-start)+start; swap(numbers, index, end); int smallCnt = start; for(int i=start; i<end; ++i) { if(numbers[i] < numbers[end]) { if(i!=smallCnt) { swap(numbers, i, smallCnt); } smallCnt++; } } swap(numbers, smallCnt, end); return smallCnt; } public int finKMin(int[] numbers, int k) { int start = 0; int end = numbers.length-1; // 使用partition 每次缩小问题规模 // 如果 index < k-1 那么,第k小在[index+1, end]中 // 如果index > k-1 那么,第k小在[start, index-1] int index = partition(numbers, start, end); while(index != k-1) { if(index < k-1) { start = index + 1; }else { end = index-1; } index = partition(numbers, start, end); } return numbers[index]; }
2、找数组中k个最小的数
- 思路同问题1
private void swap(int[] numbers, int a, int b) { int temp = numbers[a]; numbers[a] = numbers[b]; numbers[b] = temp; } private int partition(int[] numbers, int start, int end) { if(start == end) return start; int index = new Random().nextInt(end-start)+start; swap(numbers, index, end); int smallCnt = start; for(int i=start; i<end; ++i) { if(numbers[i] < numbers[end]) { if(i!=smallCnt) { swap(numbers, i, smallCnt); } smallCnt++; } } swap(numbers, smallCnt, end); return smallCnt; } // 找最小的k个数 public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) { ArrayList<Integer> ans = new ArrayList<>(); // 特殊情况,处理 if(input == null || k<=0) return ans; if(input.length <= k) { for(int i=0;i<input.length;++i) { ans.add(input[i]); } return ans; } // 核心代码 int start = 0; int end = input.length-1; int index = partition(input, start, end); while(k!=index+1) { if(index+1>k) { end = index-1; }else { start = index+1; } index = partition(input, start, end); } // 保存结果 for(int i=0;i<k;++i) { ans.add(input[i]); } return ans; }
3、找数组出现次数过半的数,
- 就是找数组的中位数,不过要注意处理检查结果的正确性,因为中位数不一定再数组中出现次数过半
/** * 面试题39: 数组中出现次数超一半的数字 * @author Saber * (1)最容易想到,先排序,再找中间数 * (2)利用哈希表记录每个数字出现的次数 * (3)快排变形 * (4)以下方法 */ public class P39 { /* * 采用阵地攻守的思想: 第一个数字作为第一个士兵,守阵地;count = 1; 遇到相同元素,count++; 遇到不相同元素,即为敌人,同归于尽,count--;当遇到count为0的情况,又以新的i值作为守阵地的士兵,继续下去,到最后还留在阵地上的士兵,有可能是主元素。 再加一次循环,记录这个士兵的个数看是否大于数组一般即可。 */ public int MoreThanHalfNum_Solution(int [] array) { // 参数检验 if(array == null || array.length<=0) return 0; int result = array[0]; int times = 1; for(int i=1;i<array.length;++i){ if(times == 0){ result = array[i]; times = 1; }else if(result == array[i]){ times++; }else{ times--; } } // 检查result 是否是答案 int cnt = 0; for(int i=0;i<array.length;++i){ if(array[i]==result){ cnt++; if(cnt>array.length/2){ return array[i]; } } } return 0; } private void swap(int[] numbers, int a, int b) { int temp = numbers[a]; numbers[a] = numbers[b]; numbers[b] = temp; } private int partition(int[] numbers, int start, int end){ if(start == end) return start; int index = new Random().nextInt(end-start) + start; //先把选中的数 与 数组最后一个数 交换位置 swap(numbers, index, end); //遍历数组,以选中的数字为界限,将数组中的元素分两半 int smallCnt = start; for(int i=start;i<end;++i) { if(numbers[i]<numbers[end]) { if(i!=smallCnt) { swap(numbers, i, smallCnt); } smallCnt++; } } swap(numbers, smallCnt, end); return smallCnt; } // 基于partition的解法 public int fun(int [] array) { int start = 0; int end = array.length-1; int middle = array.length>>1; int index = partition(array, start, end); while(index != middle) { if(index > middle) { end = index-1; }else { start = index+1; } index = partition(array, start, end); } int ans = array[middle]; int cnt = 0; for(int i=0;i<array.length;++i){ if(array[i]==ans){ cnt++; if(cnt>middle){ return array[i]; } } } return 0; } public static void main(String[] args) { int[] numbers = new int[] {3,1,1}; System.out.println(new P39().fun(numbers)); } }