快速排序就是一个二叉树的前序遍历

    1. void sort(int[] nums, int lo, int hi) {
    2. if (lo >= hi) {
    3. return;
    4. }
    5. // 对 nums[lo..hi] 进行切分
    6. // 使得 nums[lo..p-1] <= nums[p] < nums[p+1..hi]
    7. int p = partition(nums, lo, hi);
    8. // 去左右子数组进行切分
    9. sort(nums, lo, p - 1);
    10. sort(nums, p + 1, hi);
    11. }

    快速排序是先将一个元素排好序,然后再将剩下的元素排好序。快排核心在于partition 函数:
    partition 函数的作用是在 nums[lo..hi] 中寻找一个分界点 p,通过交换元素使得 nums[lo..p-1] 都小于等于 nums[p],且 nums[p+1..hi] 都大于 nums[p]

    一个元素左边的元素都比它小,右边的元素都比它大,就是它自己已经被放到正确的位置上了 所以 partition 函数干的事情,其实就是把 nums[p] 这个元素排好序了。

    一个元素被排好序了,剩下的就是递归左右两边。。。。
    从二叉树的视角,我们可以把子数组 nums[lo..hi] 理解成二叉树节点上的值,srot 函数理解成二叉树的遍历函数。最后形成一棵二叉搜索树 快速排序的过程是一个构造二叉搜索树的过程。二叉搜索树的构造,那就不得不说二叉搜索树不平衡的极端情况,极端情况下二叉搜索树会退化成一个链表,导致操作效率大幅降低。
    我们为了避免出现这种极端情况,需要引入随机性
    快速排序 - 图1

    1. class Quick {
    2. public static void sort(int[] nums) {
    3. // 为了避免出现耗时的极端情况,先随机打乱
    4. shuffle(nums);
    5. // 排序整个数组(原地修改)
    6. sort(nums, 0, nums.length - 1);
    7. }
    8. private static void sort(int[] nums, int lo, int hi) {
    9. if (lo >= hi) {
    10. return;
    11. }
    12. // 对 nums[lo..hi] 进行切分
    13. // 使得 nums[lo..p-1] <= nums[p] < nums[p+1..hi]
    14. int p = partition(nums, lo, hi);
    15. sort(nums, lo, p - 1);
    16. sort(nums, p + 1, hi);
    17. }
    18. // 对 nums[lo..hi] 进行切分
    19. private static int partition(int[] nums, int lo, int hi) {
    20. int pivot = nums[lo];
    21. // 关于区间的边界控制需格外小心,稍有不慎就会出错
    22. // 我这里把 i, j 定义为开区间,同时定义:
    23. // [lo, i) <= pivot;(j, hi] > pivot
    24. // 之后都要正确维护这个边界区间的定义
    25. int i = lo + 1, j = hi;
    26. // 当 i > j 时结束循环,以保证区间 [lo, hi] 都被覆盖
    27. while (i <= j) {
    28. while (i < hi && nums[i] <= pivot) {
    29. i++;
    30. // 此 while 结束时恰好 nums[i] > pivot
    31. }
    32. while (j > lo && nums[j] > pivot) {
    33. j--;
    34. // 此 while 结束时恰好 nums[j] <= pivot
    35. }
    36. // 此时 [lo, i) <= pivot && (j, hi] > pivot
    37. if (i >= j) {
    38. break;
    39. }
    40. swap(nums, i, j);
    41. }
    42. // 将 pivot 放到合适的位置,即 pivot 左边元素较小,右边元素较大
    43. swap(nums, lo, j);
    44. return j;
    45. }
    46. // 洗牌算法,将输入的数组随机打乱
    47. private static void shuffle(int[] nums) {
    48. Random rand = new Random();
    49. int n = nums.length;
    50. for (int i = 0 ; i < n; i++) {
    51. // 生成 [i, n - 1] 的随机数
    52. int r = i + rand.nextInt(n - i);
    53. swap(nums, i, r);
    54. }
    55. }
    56. // 原地交换数组中的两个元素
    57. private static void swap(int[] nums, int i, int j) {
    58. int temp = nums[i];
    59. nums[i] = nums[j];
    60. nums[j] = temp;
    61. }
    62. }