排序算法 - 快速排序

快速排序

介绍

快速排序是一种非常高效的排序算法,核心思想是分治:通过选择一个基准元素,将数组分割成两个子数组,其中一个子数组的所有元素都小于基准元素,而另一个子数组的所有元素都大于基准元素。然后,递归地对这两个子数组进行排序,最终实现整个数组的有序排列。

原理

快速排序算法的基本步骤:

  1. 选择一个基准元素(通常是数组中的第一个元素)。
  2. 将数组中小于基准元素的元素放在基准元素的左边,大于基准元素的元素放在右边。这一步通常称为分区操作。
  3. 递归地对基准元素左右两侧的子数组进行快速排序。
  4. 不断重复以上步骤,直到整个数组变为有序。

假设要对数组 [7, 0, 9, 5, 6, 8, 7, 3, 1, 2] 进行排序:

第一步:

  • 先选择一个元素做为基准元素(pivot),这里选择第一个元素。
  • 再设置指针 P 标记基准元素所在位置。
  • 最后设置两个指针分别指向数组最左侧和最右侧:
    • 指针 L 指向数组最左侧;
    • 指针 R 指向数组最右侧。

如下图:

image-20240307193733054

第二步:

  • 将指针 R 指向的元素与基准元素 pivot 进行比较。

  • 如果指针 R 指向的元素大于 pivot:

    • 指针 R 向左移动一个位置;
    • 循环此步骤,直至指针 R 指向的元素不大于 pivot。
  • 如果指针 R 指向的元素不大于 pivot:

    • 将指针 R 指向的元素填入指针 P 指向的位置;
    • 指针 L 向右移动一个位置;
    • 指针 P 移动到指针 R 的位置;
    • 进入 【第三步】。

当前,pivot 等于 7,指针 R 指向的元素为 2,7 > 2,因此数组情况变为:

image-20240307194851764

第三步:

  • 将指针 L 指向的元素与基准元素 pivot 进行比较。
  • 如果指针 L 指向的元素小于 pivot:
    • 指针 L 向右移动一个位置;
    • 循环此步骤,直至指针 L 指向的元素不小于 pivot。
  • 如果指针 L 指向的元素不小于 pivot:
    • 将指针 L 指向的元素填入指针 P 指向的位置;
    • 指针 R 向左移动一个位置;
    • 指针 P 移动到指针 L 的位置;
    • 返回【第二步】。

当前,pivot 等于 7,指针 L 指向的元素为 0,7 > 0,数组情况变为:

image-20240307195928719

此时,pivot 等于 7,指针 L 指向的元素为 9,7 < 9,数组情况变为:

image-20240307200245173

重返【第二步】…

第四步:

当指针 L 和指针 R 交叉后:

image-20240307210255640

将 pivot 的值填入指针 P 指向的位置:

image-20240307210508035

可以发现:

  • 指针 P 左侧的元素都比指针 P 指向的元素小;
  • 指针 P 右侧的元素都比指针 P 指向的元素大。

这是一个特殊情况,通常情况下,每次分区后的子数组都是无序的,都需要进一步排序。

做法是将 [2, 0, 1, 5, 6, 3] 看成一个新的数组,重新设置 pivot 的值(pivot=2),结构如下:

image-20240307211716184

然后按照之前的四步对这个子数组进行处理,直到整个数组都变为有序的即可。动态过程如下:

在这里插入图片描述

代码

/**
 * 快速排序算法的实现
 *
 * @author 孤诣
 */
public class QuickSort {
    public static void main(String[] args) {
        // 数据准备
        int[] dataArray = {7, 0, 9, 5, 6, 8, 4, 3, 1, 2};
        System.out.println("排序前: " + Arrays.toString(dataArray));

        // 利用快速排序算法进行排序
        quickSort(dataArray, 0, dataArray.length - 1);
        System.out.println("排序后: " + Arrays.toString(dataArray));
    }

    /**
     * 快速排序算法实现
     *
     * @param source    - 源数组
     * @param startIndex - 排序起始位置
     * @param endIndex  - 排序结束位置
     */
    public static void quickSort(int[] source, int startIndex, int endIndex) {
        if (startIndex > endIndex) {
            return;
        }

        // 分区
        int lastPivotIndex = partition(source, startIndex, endIndex);

        // 上一个基准元素位置已经确定, 后续排序就不需要考虑上一个基准元素了
        quickSort(source, startIndex, (lastPivotIndex - 1));
        quickSort(source, (lastPivotIndex + 1), endIndex);
    }

    /**
     * 分区, 本来操作的基准元素的最终位置将被确定。
     *
     * @param source     - 源数组
     * @param startIndex - 排序起始位置
     * @param endIndex   - 排序结束位置
     * @return 本轮操作完成后,基准元素所在位置
     */
    private static int partition(int[] source, int startIndex, int endIndex) {
        // 基准元素
        int pivot = source[startIndex];

        // 初始化相关指针
        int pivotIndex = startIndex;
        int leftIndex = startIndex;
        int rightIndex = endIndex;

        while (leftIndex < rightIndex) {
            // 右侧元素小于基准元素
            while (leftIndex < rightIndex) {
                if (source[rightIndex] < pivot) {
                    source[leftIndex] = source[rightIndex];
                    pivotIndex = rightIndex;
                    leftIndex++;
                    break;
                }
                rightIndex--;
            }

            // 左侧元素小于基准元素
            while (leftIndex < rightIndex) {
                if (source[leftIndex] > pivot) {
                    source[rightIndex] = source[leftIndex];
                    pivotIndex = leftIndex;
                    rightIndex--;
                    break;
                }
                leftIndex++;
            }
        }

        source[pivotIndex] = pivot;
        return pivotIndex;
    }
}

运行效果:

image-20240311220703992

评估

  • 时间复杂度:
    • 最好情况:O(nlogn)
    • 最坏情况:O(n^2):
    • 平均时间复杂度:O(nlogn)
  • 空间复杂度:
    • 平均空间复杂度:O(logn)
    • 最坏空间复杂度:O(n)
  • 稳定性:不稳定

有的文章认为快速排序的空间复杂度为 O(1),因为快速排序可以直接在原数组上进行操作。

出于以下原因,笔者并不认为快速排序的空间复杂度为 O(1):

  • 相关文章实现该算法的方式均为递归,而递归本身需要额外占用内存;
  • 笔者了解到的非递归实现快速排序方式需要借助 “栈”,仍然要占用额外内存。

快速排序是一种高效的排序算法,它的平均时间复杂度较低,适用于大规模数据的排序。

然而,在最坏的情况下(即每次划分的基准都是当前子数组中最大或最小的元素),快速排序的时间复杂度会变得很高,此时快速排序会退化为冒泡排序。此外,快速排序是不稳定的排序算法,所以在需要稳定排序的场合,我们应该选择其他算法。

总的来说,快速排序适用于大多数排序场景,但需要注意最坏情况下的性能问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值