快速排序算法

一、算法概述

快速排序是一种不稳定的原地排序算法,其核心特点是"分治+分区":通过选择一个"基准元素"(pivot),将待排序序列分割成两个子序列,其中左子序列的所有元素均小于等于基准元素,右子序列的所有元素均大于等于基准元素(具体分区规则可灵活调整),然后分别对这两个子序列递归执行快速排序,直到所有子序列长度为1(此时子序列已天然有序),排序完成。

二、核心原理与分区逻辑

快速排序的整个执行流程可分为"分区"和"递归排序"两个核心步骤,其中"分区"是快速排序的灵魂,直接决定了算法的效率;"递归排序"则是对分区后的子序列重复执行相同的逻辑,直至排序完成。

1. 核心思想(分而治之)

快速排序的分而治之思想可拆解为以下三个步骤,形成一个递归闭环:

分解:选择一个基准元素,将待排序序列 ​ arr[low...high] 分区为左子序列 ​ arr[low...pivotIndex−1] 、基准元素 ​ arr[pivotIndex] 和右子序列 ​ arr[pivotIndex+1...high] ,满足左子序列所有元素≤基准元素,右子序列所有元素≥基准元素;​

解决:递归调用快速排序,分别对左子序列 ​ arr[low...pivotIndex−1] 和右子序列arr[pivotIndex+1...high] 进行排序;​

合并:由于左、右子序列的排序是在原数组上进行的,且基准元素已处于最终的正确位置,因此无需额外的合并操作,递归结束后整个序列即有序。

2. 分区操作(Partition)

分区操作是快速排序的核心,其目的是找到基准元素的最终位置,并将序列分割为符合要求的左右两个子序列。常用的分区方式有"霍尔分区法"(Hoare Partition Scheme)和" Lomuto 分区法"(Lomuto Partition Scheme),其中霍尔分区法是快速排序的原始分区方式,效率更高; Lomuto 分区法逻辑更简单,易于实现,但效率略低。本文重点讲解霍尔分区法,并简要介绍 Lomuto 分区法。

(1)霍尔分区法

霍尔分区法的核心逻辑的是:选择一个基准元素(通常选择序列的第一个元素、最后一个元素或中间元素),设置两个指针(left和right)分别指向序列的起始位置和终止位置,然后通过指针移动,将小于基准元素的元素移到左侧,大于基准元素的元素移到右侧,最终确定基准元素的位置。具体步骤如下:

  1. 选择基准元素pivot,本文暂选序列的第一个元素(arr[low]);

  2. 初始化指针left = low + 1(指向基准元素的下一个位置),right = high(指向序列的最后一个位置);

  3. 移动left指针,直到找到第一个大于pivot的元素(arr[left] > pivot),停止移动;

  4. 移动right指针,直到找到第一个小于pivot的元素(arr[right] < pivot),停止移动;

  5. 若left < right,交换arr[left]和arr[right],然后继续执行步骤3和步骤4;

  6. 若left ≥ right,交换arr[low](基准元素)和arr[right],此时基准元素pivot的位置即为right,分区完成。

需要注意的是,霍尔分区法中,基准元素最终会与right指针指向的元素交换,因为right指针停止时,指向的必然是小于基准元素的元素,交换后可保证基准元素左侧均为小于它的元素,右侧均为大于它的元素。

(2)Lomuto 分区法

Lomuto 分区法的逻辑更简洁,核心是选择一个基准元素(通常选择序列的最后一个元素),设置一个指针i(指向小于基准元素的区域的右边界),然后遍历序列从low到high-1,将小于基准元素的元素与i指针指向的元素交换,并移动i指针,遍历结束后,将基准元素与i指针指向的元素交换,确定基准元素的位置。具体步骤如下:

  1. 选择序列的最后一个元素作为基准元素pivot = arr[high];

  2. 初始化指针i = low - 1(表示小于基准元素的区域初始为空);

  3. 遍历j从low到high-1:

    1. 若arr[j] ≤ pivot,将i指针加1,交换arr[i]和arr[j];
  4. 遍历结束后,将i指针加1,交换arr[i]和arr[high](基准元素),此时基准元素的位置即为i,分区完成。

Lomuto 分区法的优势是逻辑简单,易于理解和实现,但由于每次都选择最后一个元素作为基准,在序列有序时会出现最坏情况,且交换次数略多于霍尔分区法。

三、算法实现(以Java为例)

本文分别实现霍尔分区法和 Lomuto 分区法的快速排序,均采用递归方式实现,同时补充非递归实现(避免递归栈溢出),并附带详细注释,便于读者理解和调试。

(1)霍尔分区法​

java 复制代码
/**
 * 快速排序(霍尔分区法)
 * @param arr 待排序数组
 * @param low 排序起始索引
 * @param high 排序终止索引
 */
public static void quickSortHoare(int[] arr, int low, int high) {
    // 递归终止条件:当起始索引大于等于终止索引时,子序列长度为1或0,已有序
    if (low < high) {
        // 执行分区操作,获取基准元素的最终位置
        int pivotIndex = partitionHoare(arr, low, high);
        // 递归排序左子序列(基准元素左侧)
        quickSortHoare(arr, low, pivotIndex - 1);
        // 递归排序右子序列(基准元素右侧)
        quickSortHoare(arr, pivotIndex + 1, high);
    }
}

/**
 * 霍尔分区法:找到基准元素的最终位置,分割数组
 * @param arr 待分区数组
 * @param low 分区起始索引
 * @param high 分区终止索引
 * @return 基准元素的最终索引
 */
private static int partitionHoare(int[] arr, int low, int high) {
    // 选择序列第一个元素作为基准元素(可优化,后续讲解)
    int pivot = arr[low];
    int left = low + 1; // 左指针,从基准元素下一个位置开始
    int right = high; // 右指针,从序列末尾开始

    while (true) {
        // 移动左指针,找到第一个大于基准元素的元素
        while (left <= right && arr[left] <= pivot) {
            left++;
        }
        // 移动右指针,找到第一个小于基准元素的元素
        while (left <= right && arr[right] > pivot) {
            right--;
        }
        // 若左指针大于右指针,分区结束
        if (left > right) {
            break;
        }
        // 交换左、右指针指向的元素
        swap(arr, left, right);
        // 交换后,移动指针继续遍历
        left++;
        right--;
    }
    // 交换基准元素和右指针指向的元素,确定基准元素最终位置
    swap(arr, low, right);
    return right; // 返回基准元素索引
}

/**
 * 辅助方法:交换数组中两个索引位置的元素
 * @param arr 数组
 * @param i 索引1
 * @param j 索引2
 */
private static void swap(int[] arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

// 快速排序调用入口(简化用户调用)
public static void quickSortHoare(int[] arr) {
    // 边界校验:数组为空或长度小于2,无需排序
    if (arr == null || arr.length < 2) {
        return;
    }
    quickSortHoare(arr, 0, arr.length - 1);
}

(2)Lomuto 分区法​

java 复制代码
/**
 * 快速排序(Lomuto 分区法)
 * @param arr 待排序数组
 * @param low 排序起始索引
 * @param high 排序终止索引
 */
public static void quickSortLomuto(int[] arr, int low, int high) {
    if (low < high) {
        int pivotIndex = partitionLomuto(arr, low, high);
        quickSortLomuto(arr, low, pivotIndex - 1);
        quickSortLomuto(arr, pivotIndex + 1, high);
    }
}

/**
 * Lomuto 分区法:找到基准元素的最终位置,分割数组
 * @param arr 待分区数组
 * @param low 分区起始索引
 * @param high 分区终止索引
 * @return 基准元素的最终索引
 */
private static int partitionLomuto(int[] arr, int low, int high) {
    // 选择序列最后一个元素作为基准元素
    int pivot = arr[high];
    int i = low - 1; // 小于基准元素的区域右边界

    // 遍历序列,将小于等于基准元素的元素移到左侧区域
    for (int j = low; j < high; j++) {
        if (arr[j] <= pivot) {
            i++;
            swap(arr, i, j);
        }
    }
    // 交换基准元素和左侧区域的下一个元素,确定基准元素位置
    swap(arr, i + 1, high);
    return i + 1; // 返回基准元素索引
}

// 快速排序调用入口
public static void quickSortLomuto(int[] arr) {
    if (arr == null || arr.length < 2) {
        return;
    }
    quickSortLomuto(arr, 0, arr.length - 1);
}
相关推荐
吃好睡好便好4 小时前
在Matlab中绘制横直方图
开发语言·学习·算法·matlab
仰泳之鹅4 小时前
【C语言】自定义数据类型2——联合体与枚举
c语言·开发语言·算法
x_yeyue7 小时前
三角形数
笔记·算法·数论·组合数学
Mr. zhihao7 小时前
深入解析redis基本数据结构
数据结构·数据库·redis
念何架构之路8 小时前
Go语言加密算法
数据结构·算法·哈希算法
AI科技星8 小时前
《数学公理体系·第三部·数术几何》(2026 年版)
c语言·开发语言·线性代数·算法·矩阵·量子计算·agi
失去的青春---夕阳下的奔跑8 小时前
560. 和为 K 的子数组
数据结构·算法·leetcode
黎阳之光8 小时前
黎阳之光:以视频孪生重构智慧医院信息化,打造高标项目核心竞争力
大数据·人工智能·物联网·算法·数字孪生
丷丩9 小时前
三级缓存下MVT地图瓦片服务性能优化策略
算法·缓存·性能优化·gis·geoai-up
m0_629494739 小时前
LeetCode 热题 100-----25.回文链表
数据结构·算法·leetcode·链表