面试官:如何优化快排?

254 阅读2分钟

对于快速排序我们这里有三种优化的方式, 这三种优化方式所针对的是分割(即尽可能的均匀分割)

  1. 随机选取基准值
  2. 三数取中法
  3. 把和基准相同的数据, 从两边移到跟前来

三数取中法是我们最常用的一个方法,也是比较简单好用的方法,因此这里主要介绍三数取中法~

随机选取基准法

基本思路:

​ 以左边做 key , 在 [left + 1 , right] 这个区间来随机获取一个下标, 和 left 下标交换. 所以本质上还是以 left 下标的值作为 key 的, 这种方式在大多情况下, 是能够均匀分割的, 但还是有一定的可能性是会出现单分支的情况的, 因此这种方法不予考虑

三数取中法

​ 对于快排的优化我们可以结合直接插入排序和三数取中法来优化~ 在一定区间内使用直接插入排序,优化区间内的比较. 使用三数取中法,使所找的基准能够均匀分割, 从而降低了递归的深度, 防止栈溢出~

基本思路:

​ 在 [left + 1 , right] 这个区间来比较 left 下标, right 下标和 mid 下标的值, 取值为中间大的数来作为基准值

代码

public void quickSort(int[] arr) {
    if (arr == null || arr.length > 2) {
        return;
    }
    qSort(arr, 0, arr.length - 1);
}

private void qSort(int[] arr, int left, int right) {
    // left == right 说明就只剩下一个元素了,不需要再找基准了, 但这里不能取 ==
    // 因为 left 可能会走到 right 的右边, 因此要取 >=
    if (left >= right) {
        return;
    }

    // 在一定区间内使用直接插入排序,优化区间内的比较
    // 这里的区间可根据情况自己设置
    if (right - left + 1 < 1000) {
        // 当这组数据只有 1000 个数据时就使用直接插入排序
        insertSortRang(arr, left, right);
    }
    // 使用三数取中法,使所找的基准能够均匀分割
    int index = medianOfThree(arr, left, right);
    // 使用挖坑法找基准
    int pivot = partition(arr, left, right);

    qSort(arr, left, pivot - 1);
    qSort(arr, pivot + 1, right);
}

private void insertSortRang(int[] arr, int left, int right) {
    for (int i = left + 1; i <= right; i++) {
        int temp = arr[i];
        int j = left - 1;
        for (; j >= 0; j--) {
            if (arr[j] > temp) {
                arr[j + 1] = arr[j];
            } else {
                break;
            }
        }
        arr[j + 1] = temp;
    }
}

private int medianOfThree(int[] arr, int left, int right) {
    int mid = (right - left) >>> 1;
    if (arr[left] < arr[right]) {
        if (arr[mid] < arr[left]) {
            return left;
        } else if (arr[mid] > arr[right]) {
            return right;
        } else {
            return mid;
        }
    } else {
        if (arr[mid] > arr[left]) {
            return left;
        } else if (arr[mid] < arr[right]) {
            return right;
        } else {
            return mid;
        }
    }
}

private int partition(int[] arr, int left, int right) {
    int temp =  arr[left];
    while (left < right) {
        // 这里的 arr[right] >= temp 一定要写 >= 不能写 >
        // 否则两个相等的数就会一直交换, 进入死循环~
        while (left < right && arr[right] >= temp) {
            right--;
        }
        arr[left] = arr[right];
        // 同理,这里也是一样的
        while (left < right && arr[left] <= temp) {
            left++;
        }
        arr[right] = arr[left];
    }
    arr[left] = temp;
    return left;
}