什么是快速排序?时间复杂度多少?怎么优化?「终于解决」

编程文档 (56) 2023-08-06 11:12

Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说什么是快速排序?时间复杂度多少?怎么优化?「终于解决」,希望能够帮助你!!!。

快速排序是一种常用的基于比较的排序算法。它的基本思想是将待排序的序列分成两个子序列,一部分小于基准值,另一部分大于基准值,然后对子序列递归地进行排序,最终整个序列有序。

具体实现中,快速排序通常选择序列的第一个元素作为基准值,然后通过交换元素的方式将小于基准值的元素放在基准值的左边,大于基准值的元素放在基准值的右边。然后再对基准值左右两个子序列递归地进行排序,直到序列完全有序。

快速排序的时间复杂度为 O(nlogn),其中 n 是待排序序列的长度。在最坏情况下,即序列已经有序或者近乎有序时,快速排序的时间复杂度可能会达到 O(n^2)。为了避免最坏情况的发生,可以采用随机选择基准值的方法,或者使用三数取中法等方法来选择基准值。

以下是使用 Java 实现快速排序的示例代码:

public class QuickSort {
    public static void quickSort(int[] arr, int left, int right) {
        if (left < right) {
            int pivot = partition(arr, left, right);
            quickSort(arr, left, pivot - 1);
            quickSort(arr, pivot + 1, right);
        }
    }

    private static int partition(int[] arr, int left, int right) {
        int pivot = arr[left];
        int i = left + 1;
        int j = right;

        while (true) {
            while (i <= j && arr[i] < pivot) {
                i++;
            }
            while (i <= j && arr[j] > pivot) {
                j--;
            }
            if (i > j) {
                break;
            }
            swap(arr, i, j);
        }
        swap(arr, left, j);
        return j;
    }

    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

该算法的核心是 partition 方法,它通过选取一个基准值(本例中是序列的第一个元素)将序列分为两个部分,并返回基准值在序列中的位置。然后,将左右两部分分别进行递归调用快速排序算法即可。

在 partition 方法中,我们通过双指针的方式遍历序列,将小于基准值的元素交换到序列的左边,将大于基准值的元素交换到序列的右边,直到左右指针相遇。最后,将基准值交换到序列的正确位置上,并返回基准值的位置。

在 quickSort 方法中,我们首先判断序列是否需要排序,然后调用 partition 方法得到基准值的位置,再对基准值左右两部分递归调用 quickSort 方法即可。

该算法的时间复杂度为 O(nlogn),其中 n 是待排序序列的长度。

上面的代码可以进行如下优化:

  1. 随机选择基准值:为了避免最坏情况的发生,可以随机选择基准值。这样可以尽量避免序列已经有序或近乎有序的情况下时间复杂度退化到 O(n^2)。具体实现方式是,将选取的基准值与序列的第一个元素交换位置,然后继续进行快速排序即可。
  2. 插入排序:对于较小的子序列,快速排序可能会退化为 O(n^2) 的时间复杂度,此时可以采用插入排序的方法进行排序。具体实现方式是,对于长度小于某个阈值(例如 10)的子序列,采用插入排序的方式进行排序,而对于长度大于阈值的子序列,继续采用快速排序的方式进行排序。
  3. 双轴快排(Dual Pivot QuickSort):在一些情况下,双轴快排可以比单轴快排更快地进行排序。双轴快排的基本思想是,选取两个基准值将序列分为三部分,并递归地进行排序。具体实现方式可以参考 JDK 中的 java.util.DualPivotQuicksort。

下面是随机选择基准值和插入排序的示例代码:

public class QuickSort {
    private static final int INSERTION_THRESHOLD = 10;

    public static void quickSort(int[] arr, int left, int right) {
        if (left < right) {
            if (right - left < INSERTION_THRESHOLD) {
                insertionSort(arr, left, right);
            } else {
                int pivot = randomizedPartition(arr, left, right);
                quickSort(arr, left, pivot - 1);
                quickSort(arr, pivot + 1, right);
            }
        }
    }

    private static int randomizedPartition(int[] arr, int left, int right) {
        int i = left + (int) (Math.random() * (right - left + 1));
        swap(arr, left, i);
        return partition(arr, left, right);
    }

    private static void insertionSort(int[] arr, int left, int right) {
        for (int i = left + 1; i <= right; i++) {
            int key = arr[i];
            int j = i - 1;
            while (j >= left && arr[j] > key) {
                arr[j + 1] = arr[j];
                j--;
            }
            arr[j + 1] = key;
        }
    }

    private static int partition(int[] arr, int left, int right) {
        int pivot = arr[left];
        int i = left + 1;
        int j = right;

        while (true) {
            while (i <= j && arr[i] < pivot) {
                i++;
            }
            while (i <= j && arr[j] > pivot) {
                j--;
            }
            if (i > j) {
                break;
            }
            swap(arr, i, j);
        }
        swap(arr, left, j);
        return j;
    }

    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

在这个示例代码中,我们首先定义了一个阈值 INSERTION_THRESHOLD,用来判断是否采用插入排序。当右边界 right 和左边界 left 之间的元素个数小于 INSERTION_THRESHOLD 时,采用插入排序,否则采用随机选择基准值的快速排序。

在随机选择基准值时,我们通过 Math.random() 方法生成一个随机数 i,然后将序列的第一个元素 arr[left] 和 arr[i] 交换位置,然后将 arr[left] 作为基准值进行 partition 操作。这样可以尽量避免序列已经有序或近乎有序的情况下时间复杂度退化到 O(n^2)。

在插入排序中,我们采用了一般的插入排序实现方式,对于每一个元素,我们在已经排好序的子序列中找到它应该插入的位置,并将它插入到正确的位置。由于插入排序的时间复杂度为 O(n^2),因此我们只对长度小于 INSERTION_THRESHOLD 的子序列采用插入排序。

最后,我们定义了一个 swap 方法,用来交换数组中两个元素的位置,代码如下:

private static void swap(int[] arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

在快速排序的过程中,我们需要频繁地交换数组中的元素位置,因此使用一个独立的方法来实现交换操作可以让代码更加清晰简洁。

发表回复