内容来自刘宇波老师算法与数据结构体系课
为什么快速排序需要随机化

1、单路快速排序

| |
| |
| |
| |
| public class QuickSort { |
| |
| private static final Random RANDOM = new Random(); |
| |
| private QuickSort() { |
| } |
| |
| |
| |
| |
| public static <E extends Comparable<E>> void sort(E[] arr) { |
| sort(arr, 0, arr.length - 1); |
| } |
| |
| |
| |
| |
| private static <E extends Comparable<E>> void sort(E[] arr, int l, int r) { |
| if (r - l <= 15) { |
| InsertionSort.sort(arr, l, r); |
| return; |
| } |
| |
| int p = partition(arr, l, r); |
| |
| sort(arr, l, p - 1); |
| sort(arr, p + 1, r); |
| } |
| |
| |
| |
| |
| private static <E extends Comparable<E>> int partition(E[] arr, int l, int r) { |
| int p = RANDOM.nextInt(r - l + 1) + l; |
| swap(arr, l, p); |
| |
| E v = arr[l]; |
| int j = l; |
| |
| |
| for (int i = l + 1; i <= r; i++) { |
| if (arr[i].compareTo(v) < 0) swap(arr, i, ++j); |
| } |
| |
| swap(arr, l, j); |
| return j; |
| } |
| |
| private static <E> void swap(E[] arr, int a, int b) { |
| E k = arr[a]; |
| arr[a] = arr[b]; |
| arr[b] = k; |
| } |
| } |
2、双路快速排序

| |
| |
| |
| public class QuickSort { |
| |
| private static final Random RANDOM = new Random(); |
| |
| private QuickSort() { |
| } |
| |
| |
| |
| |
| public static <E extends Comparable<E>> void sort(E[] arr) { |
| sort(arr, 0, arr.length - 1); |
| } |
| |
| |
| |
| |
| public static <E extends Comparable<E>> void sort(E[] arr, int l, int r) { |
| if (r - l <= 15) { |
| InsertionSort.sort(arr, l, r); |
| return; |
| } |
| |
| int p = partition(arr, l, r); |
| |
| sort(arr, l, p - 1); |
| sort(arr, p + 1, r); |
| } |
| |
| |
| |
| |
| public static <E extends Comparable<E>> int partition(E[] arr, int l, int r) { |
| int p = RANDOM.nextInt(r - l + 1) + l; |
| swap(arr, l, p); |
| |
| E v = arr[l]; |
| int p1 = l + 1; |
| int p2 = r; |
| |
| |
| |
| while (true) { |
| while (p1 <= p2 && arr[p1].compareTo(v) < 0) p1++; |
| while (p1 <= p2 && arr[p2].compareTo(v) > 0) p2--; |
| |
| if (p1 >= p2) break; |
| |
| swap(arr, p1++, p2--); |
| } |
| |
| swap(arr, l, p2); |
| return p2; |
| } |
| |
| public static <E> void swap(E[] arr, int a, int b) { |
| E k = arr[a]; |
| arr[a] = arr[b]; |
| arr[b] = k; |
| } |
| } |
3、三路快速排序

| |
| |
| |
| |
| public class QuickSort { |
| |
| private static final Random RANDOM = new Random(); |
| |
| private QuickSort() { |
| } |
| |
| public static <E extends Comparable<E>> void sort(E[] arr) { |
| sort(arr, 0, arr.length - 1); |
| } |
| |
| private static <E extends Comparable<E>> void sort(E[] arr, int l, int r) { |
| if (r - l <= 15) { |
| InsertionSort.sort(arr, l, r); |
| return; |
| } |
| |
| int[] p = partition(arr, l, r); |
| |
| sort(arr, l, p[0]); |
| sort(arr, p[1], r); |
| } |
| |
| private static <E extends Comparable<E>> int[] partition(E[] arr, int l, int r) { |
| int p = RANDOM.nextInt(r - l + 1) + l; |
| swap(arr, l, p); |
| |
| E v = arr[l]; |
| int p1 = l; |
| int p2 = r + 1; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| int i = l + 1; |
| while (i < p2) { |
| if (arr[i].compareTo(v) < 0) swap(arr, ++p1, i++); |
| else if (arr[i].compareTo(v) > 0) swap(arr, --p2, i); |
| else i++; |
| } |
| |
| swap(arr, l, p1); |
| return new int[]{p1 - 1, p2}; |
| } |
| |
| private static <E> void swap(E[] arr, int a, int b) { |
| E k = arr[a]; |
| arr[a] = arr[b]; |
| arr[b] = k; |
| } |
| } |
4、Partition 总结
| 单路快速排序法:完全有序的数据退化,因此引入随机化,但是具有大量重复元素的数据退化 |
| 双路快速排序法:可以让一样的数据比较平均的分配到左右两边 |
| 三路快速排序法:对完全重复的数据排序,复杂度为 O(n) |
|
单路 |
双路 |
三路 |
随机数据 |
✓ |
✓ |
✓ |
有序数据 |
✓ |
✓ |
✓ |
重复数据 |
× |
✓ |
O(n) |
| public class Partition { |
| |
| private static final Random RANDOM = new Random(); |
| |
| private Partition() { |
| } |
| |
| |
| |
| |
| public static <E extends Comparable<E>> int partition1(E[] arr, int l, int r) { |
| int p = RANDOM.nextInt(r - l + 1) + l; |
| swap(arr, l, p); |
| |
| E v = arr[l]; |
| int j = l; |
| |
| for (int i = l + 1; i <= r; i++) { |
| if (arr[i].compareTo(v) < 0) swap(arr, i, ++j); |
| } |
| |
| swap(arr, l, j); |
| return j; |
| } |
| |
| |
| |
| |
| public static <E extends Comparable<E>> int partition2(E[] arr, int l, int r) { |
| int p = RANDOM.nextInt(r - l + 1) + l; |
| swap(arr, l, p); |
| |
| E v = arr[l]; |
| int p1 = l + 1; |
| int p2 = r; |
| |
| while (true) { |
| while (p1 <= p2 && arr[p1].compareTo(v) < 0) p1++; |
| while (p1 <= p2 && arr[p2].compareTo(v) > 0) p2--; |
| |
| if (p1 >= p2) break; |
| |
| swap(arr, p1++, p2--); |
| } |
| |
| swap(arr, l, p2); |
| return p2; |
| } |
| |
| |
| |
| |
| public static <E extends Comparable<E>> int[] partition3(E[] arr, int l, int r) { |
| int p = RANDOM.nextInt(r - l + 1) + l; |
| swap(arr, l, p); |
| |
| E v = arr[l]; |
| int p1 = l; |
| int p2 = r + 1; |
| |
| int i = l + 1; |
| while (i < p2) { |
| if (arr[i].compareTo(v) < 0) swap(arr, ++p1, i++); |
| else if (arr[i].compareTo(v) > 0) swap(arr, --p2, i); |
| else i++; |
| } |
| |
| swap(arr, l, p1); |
| return new int[]{p1 - 1, p2}; |
| } |
| |
| private static <E extends Comparable<E>> void insertionSort(E[] arr, int l, int r) { |
| for (int i = l + 1; i <= r; i++) { |
| E k = arr[i]; |
| int j; |
| for (j = i; j - 1 >= l && arr[j - 1].compareTo(k) > 0; j--) { |
| arr[j] = arr[j - 1]; |
| } |
| arr[j] = k; |
| } |
| } |
| |
| private static <E> void swap(E[] arr, int a, int b) { |
| E k = arr[a]; |
| arr[a] = arr[b]; |
| arr[b] = k; |
| } |
| } |
5、复杂度分析


6、标定点为 mid 的快速排序
| public class QuickSortMid { |
| |
| private QuickSortMid() { |
| } |
| |
| |
| |
| |
| public static <E extends Comparable<E>> void sort(E[] arr) { |
| sort(arr, 0, arr.length - 1); |
| } |
| |
| |
| |
| |
| private static <E extends Comparable<E>> void sort(E[] arr, int l, int r) { |
| if (l >= r) return; |
| |
| int p = partition(arr, l, r); |
| |
| sort(arr, l, p - 1); |
| sort(arr, p + 1, r); |
| } |
| |
| |
| |
| |
| private static <E extends Comparable<E>> int partition(E[] arr, int l, int r) { |
| int mid = l + (r - l) / 2; |
| swap(arr, l, mid); |
| |
| E v = arr[l]; |
| int j = l; |
| |
| |
| for (int i = l + 1; i <= r; i++) { |
| if (arr[i].compareTo(v) < 0) swap(arr, i, ++j); |
| } |
| |
| swap(arr, l, j); |
| return j; |
| } |
| |
| private static <E> void swap(E[] arr, int a, int b) { |
| E k = arr[a]; |
| arr[a] = arr[b]; |
| arr[b] = k; |
| } |
| } |
| |
| |
| |
| public static Integer[] generateSpecialArray(int length) { |
| Integer[] arr = new Integer[length]; |
| generateSpecialArray(arr, 0, length - 1, 0); |
| return arr; |
| } |
| |
| |
| |
| |
| private static void generateSpecialArray(Integer[] arr, int l, int r, int value) { |
| if (l > r) return; |
| |
| int mid = l + (r - l) / 2; |
| arr[mid] = value; |
| |
| swap(arr, l, mid); |
| generateSpecialArray(arr, l + 1, r, value + 1); |
| swap(arr, l, mid); |
| } |
7、Select K 与 Top K 问题
另一种思路,利用优先队列解决 Select K 与 Top K 问题

7.1、Select K 问题
215 - 数组中的第 K 个最大元素
| |
| |
| |
| |
| public class FindKthLargest { |
| |
| public static int findKthLargest(int[] arr, int k) { |
| return selectK(arr, 0, arr.length - 1, arr.length - k, new Random()); |
| } |
| |
| private static int selectK(int[] arr, int l, int r, int k, Random random) { |
| int p = partition(arr, l, r, random); |
| |
| if (p == k) return arr[p]; |
| |
| if (p < k) return selectK(arr, p + 1, r, k, random); |
| return selectK(arr, l, p - 1, k, random); |
| } |
| |
| |
| |
| |
| private static int partition(int[] arr, int l, int r, Random random) { |
| int p = random.nextInt(r - l + 1) + l; |
| swap(arr, l, p); |
| |
| int v = arr[l]; |
| int p1 = l + 1; |
| int p2 = r; |
| |
| while (true) { |
| while (p1 <= p2 && arr[p1] < v) p1++; |
| while (p1 <= p2 && arr[p2] > v) p2--; |
| |
| if (p1 >= p2) break; |
| |
| swap(arr, p1++, p2--); |
| } |
| |
| swap(arr, l, p2); |
| return p2; |
| } |
| |
| private static void swap(int[] arr, int a, int b) { |
| int k = arr[a]; |
| arr[a] = arr[b]; |
| arr[b] = k; |
| } |
| } |
7.2、Top K 问题
剑指 Offer 40 - 最小的 K 个数
| |
| |
| |
| |
| public class GetLeastNumbers { |
| |
| public static int[] getLeastNumbers(int[] arr, int k) { |
| if (k == 0) return new int[0]; |
| return selectK(arr, 0, arr.length - 1, k - 1, new Random()); |
| } |
| |
| private static int[] selectK(int[] arr, int l, int r, int k, Random random) { |
| int p = partition(arr, l, r, random); |
| |
| if (p == k) return Arrays.copyOf(arr, k + 1); |
| |
| if (p < k) return selectK(arr, p + 1, r, k, random); |
| return selectK(arr, l, p - 1, k, random); |
| } |
| |
| |
| |
| |
| private static int partition(int[] arr, int l, int r, Random random) { |
| int p = random.nextInt(r - l + 1) + l; |
| swap(arr, l, p); |
| |
| int v = arr[l]; |
| int p1 = l + 1; |
| int p2 = r; |
| |
| while (true) { |
| while (p1 <= p2 && arr[p1] < v) p1++; |
| while (p1 <= p2 && arr[p2] > v) p2--; |
| |
| if (p1 >= p2) break; |
| |
| swap(arr, p1++, p2--); |
| } |
| |
| swap(arr, l, p2); |
| return p2; |
| } |
| |
| private static void swap(int[] arr, int a, int b) { |
| int k = arr[a]; |
| arr[a] = arr[b]; |
| arr[b] = k; |
| } |
| } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步