高级排序算法(快速排序)

255 阅读4分钟

这篇文章,你可以学习

  • 单路快速排序
  • 随机化
  • 双路快速排序
  • 三路快速排序

快速排序的过程

image.png

image.png

快速排序解读

image.png

image.png

partition的实现

方式一:创建两个额外的空间

image.png

image.png

image.png

方式二:原地partition

image.png

image.png

image.png

image.png

image.png

  • 假设[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的时候,元素分配不平衡。 image.png

image.png

如何解决上面的问题呢?

不能固定我们选取的标注点为最左边的元素,要随机选取。 image.png

为快速排序添加随机化

image.png

    // 这个函数就是查找到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算法,平均分配与标注点相同元素到标注点的两侧。

所以就有了双路快速排序算法

image.png

image.png

image.png

image.png

image.png

  • 通过随机下标,交换与区间第一个元素的位置,选取标注点。
  • 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;
    }

快速排序算法的复杂度分析

image.png

三路快速排序算法

  • 当i位置元素等于l位置元素 image.png

  • 当i位置元素小于l位置元素 image.png

  • 当i位置元素大于l位置元素

image.png

  • 循环结束的条件,i >= gt
  • 最后交换l 和 lt位置的元素。然后返回lt。 image.png

image.png

 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小的元素

image.png LeetCode相关习题。

  • 215
  • 剑指offer 40