这篇文章,你可以学习
- 单路快速排序
- 随机化
- 双路快速排序
- 三路快速排序
快速排序的过程
网络异常,图片无法展示
|
网络异常,图片无法展示
|
快速排序解读
网络异常,图片无法展示
|
网络异常,图片无法展示
|
partition的实现
方式一:创建两个额外的空间
网络异常,图片无法展示
|
网络异常,图片无法展示
|
网络异常,图片无法展示
|
方式二:原地partition
网络异常,图片无法展示
|
网络异常,图片无法展示
|
网络异常,图片无法展示
|
网络异常,图片无法展示
|
网络异常,图片无法展示
|
- 假设[l, r]区间的第一个元素为判断元素。
- 让其前面的元素都小于判断元素,后面的元素都大于判断元素。这就找到了判断元素应该放置的位置。
j = l
i = l + 1
- 当当前i位置的元素小于判断元素,那么将让j++, 然后后交换i 和 j位置的元素。
- 最后,当i越界后,即交换 j 和 l 位置的元素。
// 这个函数就是查找到arr[l]位置应该处于什么位置 private static <E extends Comparable<E>> int partition(E[] arr, int l, int r) { int j = l; for(int i = l + 1; i <= r; i++) { if(arr[l].compareTo(arr[i]) > 0) { // i位置的元素小于当前元素 j ++; // 交换j和i的位置 swap(arr, j, i); } } // 当i遍历完后,交换j和l的位置。 swap(arr, j, l); // 最后返回j的下标 return j; } // 交换数组中两个元素 private static <E> void swap(E[] arr, int j, int i) { E temp = arr[j]; arr[j] = arr[i]; arr[i] = temp; }
上面partition算法问题一,对于有序数组来说
他就是在partition的时候,元素分配不平衡。
网络异常,图片无法展示
|
网络异常,图片无法展示
|
如何解决上面的问题呢?
不能固定我们选取的标注点为最左边的元素,要随机选取。
网络异常,图片无法展示
|
为快速排序添加随机化
网络异常,图片无法展示
|
// 这个函数就是查找到arr[l]位置应该处于什么位置 // 循环不变量:arr[l + 1...j] < v; arr[j + 1...i] >= v; private static <E extends Comparable<E>> int partition(E[] arr, int l, int r) { // j = l; // 随机化一个下标,防止每次都使用第一个元素作为基准。对于有序数组,那么他的时间复杂度就变为o(n2) // int randomIndex = (int) Math.floor(Math.random() * (r - l + 1)) + l; + int randomIndex = (new Random()).nextInt(r - l + 1) +l; + swap(arr, randomIndex, l); int j = l; for(int i = l + 1; i <= r; i++) { if(arr[l].compareTo(arr[i]) > 0) { // i位置的元素小于当前元素 j ++; // 交换j和i的位置 swap(arr, j, i); } } // 当i遍历完后,交换j和l的位置。 swap(arr, j, l); // 最后返回j的下标 return j; }
上面partition算法存在问题二,对于元素都相同的数组
我们对于和标注点相同的元素,无能为力。所以,我们需要重新设计partition算法,平均分配与标注点相同元素到标注点的两侧。
所以就有了双路快速排序算法。
网络异常,图片无法展示
|
网络异常,图片无法展示
|
网络异常,图片无法展示
|
网络异常,图片无法展示
|
网络异常,图片无法展示
|
- 通过随机下标,交换与区间第一个元素的位置,选取标注点。
- i = l + 1;
- j = r;
- arr[i] >= arr[l],暂停。arr[j] < arr[l],暂停。然后交换i和j的位置。i++, j--。
- 如果二者比较不成功,那么就进行i++ / j--;
- i > j时,循环结束。交换j和l的位置元素。
private static <E extends Comparable<E>> int partition2(E[] arr, int l , int r) { // int randomIndex = (new Random()).nextInt(r - l + 1) + l; int randomIndex = (int) Math.floor(Math.random() * (r - l + 1)) + l; swap(arr, randomIndex, l); int i = l + 1; int j = r; while(true) { // i未超界,并且元素小于标注数 while(i <= j && arr[i].compareTo(arr[l]) < 0) { i++; } // j未超界,并且元素大于标注数 while(j >= i && arr[j].compareTo(arr[l]) > 0) { j--; } // 越界,跳出循环。 if(i >= j) break; // 上面i, j下标都暂停,那么交换两个位置的元素 swap(arr, i, j); i++; j--; } // 最后交换l和j的位置 swap(arr, l, j); return j; } // 交换数组中两个元素 private static <E> void swap(E[] arr, int j, int i) { E temp = arr[j]; arr[j] = arr[i]; arr[i] = temp; }
快速排序算法的复杂度分析
网络异常,图片无法展示
|
三路快速排序算法
- 当i位置元素等于l位置元素
网络异常,图片无法展示
|
- 当i位置元素小于l位置元素
网络异常,图片无法展示
|
- 当i位置元素大于l位置元素
网络异常,图片无法展示
|
- 循环结束的条件,i >= gt
- 最后交换l 和 lt位置的元素。然后返回lt。
网络异常,图片无法展示
|
网络异常,图片无法展示
|
public static <E extends Comparable<E>> void sort3(E[] arr, int l, int r) { if(l >= r) { return; } int randomIndex = (new Random()).nextInt((r - l + 1)) + l; swap(arr, l, randomIndex); // 循环不变量 arr[l ... lt - 1] < v ; arr[lt ... gt - 1] == v ; arr[gt ... r] > v; // 开始的时候区间元素都为空 int lt = l, i = l + 1, gt = r + 1; while(i < gt) { // 当i 和 gt重合时 if(arr[l].compareTo(arr[i]) < 0) { // 交换gt和i的位置 gt--; swap(arr, i, gt); }else if(arr[l].compareTo(arr[i]) > 0) { // 交换lt和i的位置 lt++; swap(arr, lt, i); i++; }else { // 二者相等,i直接++ i++; } } // 交换二者位置。 l, lt swap(arr, l, lt); sort3(arr, l, lt - 1); sort3(arr, gt, r); } // 交换数组中两个元素 private static <E> void swap(E[] arr, int j, int i) { E temp = arr[j]; arr[j] = arr[i]; arr[i] = temp; }
三路快速排序算法解决LeetCode 75 颜色分类
class Solution { public void sortColors(int[] nums) { // nums[0 ... zero] == 0; nums[zero +1, i] == 1; nums[two, n - 1] == 2; int zero = -1, i = 0, two = nums.length; while(i < two) { if(nums[i] == 0) { zero ++; swap(nums, zero, i); i++; }else if(nums[i] == 2) { two --; swap(nums, i, two); }else { // nums[i] == 1 i++; } } } private void swap(int [] nums, int i, int j ) { int t = nums[i]; nums[i] = nums[j]; nums[j] = t; } }
找到数组中第k小的元素
网络异常,图片无法展示
|
LeetCode相关习题。
- 215
- 剑指offer 40