8、快速排序

内容来自刘宇波老师算法与数据结构体系课

为什么快速排序需要随机化

image

1、单路快速排序

image

/**
 * 单路快速排序: O(N * logN)
 * 当数组中的元素一致时退化为 O(n^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);
    }

    /**
     * 快速排序 arr[l, r]
     */
    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;

        // arr[l + 1, j] < v, arr[j + 1, r] >= v
        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、双路快速排序

image

/**
 * 双路快速排序: O(N * logN)
 */
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);
    }

    /**
     * 快速排序 arr[l, r]
     */
    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;

        // arr[l, p1 - 1] <= v, arr[p2 + 1, r] >= v
        // 循环结束的 2 种情况: p1 > p2 和 p1 == p2
        while (true) {
            while (p1 <= p2 && arr[p1].compareTo(v) < 0) p1++; // arr[p1] >= v
            while (p1 <= p2 && arr[p2].compareTo(v) > 0) p2--; // arr[p2] <= v

            if (p1 >= p2) break; // 也可以理解为当 p1 >= p2 时, 就没必要 swap(arr, p1, p2) 了

            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、三路快速排序

image

/**
 * 三路快速排序: O(N * logN)
 * 所有元素都相同的数组 O(n)
 */
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;

        // arr[l + 1, p1] < v, arr[p2, r] > v
        // for (int i = l + 1; i < p2; i++) {
        //    if (arr[i].compareTo(v) < 0) swap(arr, i, ++p1);
        //    else if (arr[i].compareTo(v) > 0) {
        //        swap(arr, i, --p2);
        //        i--;
        //    }
        // }

        // arr[l + 1, p1] < v, arr[p2, r] > v
        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、复杂度分析

image

image

6、标定点为 mid 的快速排序

public class QuickSortMid {

    private QuickSortMid() {
    }

    /**
     * 快速排序
     */
    public static <E extends Comparable<E>> void sort(E[] arr) {
        sort(arr, 0, arr.length - 1);
    }

    /**
     * 快速排序 arr[l, r]
     */
    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;

        // arr[l + 1, j] < v, arr[j + 1, r] >= v
        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;
    }
}
/**
 * 针对以中间点为标定点的快速排序, 生成一个长度为 length 的特殊数组, 使得快速排序退化
 */
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 问题

image

7.1、Select K 问题

215 - 数组中的第 K 个最大元素

/**
 * 求数组升序排序好后, 从右往左数第 K 个元素, 它的索引是 length - K
 * 复杂度: O(n)
 */
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 个数

/**
 * 求数组升序排序好后, 从左往右数共 K 个元素
 * 复杂度: O(n)
 */
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;
    }
}
posted @ 2023-04-10 14:59  lidongdongdong~  阅读(16)  评论(0编辑  收藏  举报