快速排序QuickSort

快速排序QuickSort

快速排序

快速排序算法有两个核心点,分别为 哨兵划分递归

哨兵划分

以数组某个元素(一般选取首元素)为 基准数 ,将所有小于基准数的元素移动至其左边,大于基准数的元素移动至其右边。

如下图所示,为哨兵划分操作流程。通过一轮 哨兵划分 ,可将数组排序问题拆分为 两个较短数组的排序问题 (本文称之为左(右)子数组)。

递归

对 左子数组 和 右子数组 分别递归执行 哨兵划分,直至子数组长度为 1 时终止递归,即可完成对整个数组的排序。

如下图所示,为示例数组 2,4,1,0,3,5 的快速排序流程。观察发现,快速排序和 二分法 的原理类似,都是以
log 时间复杂度实现搜索区间缩小。

java 复制代码
void quickSort(int[] nums, int l, int r) {
    // 子数组长度为 1 时终止递归
    if (l >= r) return;
    // 哨兵划分操作
    int i = partition(nums, l, r);
    // 递归左(右)子数组执行哨兵划分
    quickSort(nums, l, i - 1);
    quickSort(nums, i + 1, r);
}

int partition(int[] nums, int l, int r) {
    // 以 nums[l] 作为基准数
    int i = l, j = r;
    while (i < j) {
        while (i < j && nums[j] >= nums[l]) j--;
        while (i < j && nums[i] <= nums[l]) i++;
        swap(nums, i, j);
    }
    swap(nums, i, l);
    return i;
}

void swap(int[] nums, int i, int j) {
    // 交换 nums[i] 和 nums[j]
    int tmp = nums[i];
    nums[i] = nums[j];
    nums[j] = tmp;
}

// 调用
int[] nums = { 4, 1, 3, 2, 5 };
quickSort(nums, 0, nums.length - 1);

优化

java 复制代码
package 快速排序;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: AlenXu
 * @Date: 2024/03/19/13:26
 * @Description:
 */
public class QuickSort {

    static void quickSort1(int[] nums, int l, int r) {
        // 子数组长度为 1 时终止递归
        if (l >= r) return;
        //哨兵划分操作
        int i = partition(nums, l, r);
        // 递归左(右)子数组执行哨兵划分
        quickSort(nums, l, i - 1);
        quickSort(nums, i + 1, r);
    }

//    由于普通快速排序每轮选取「子数组最左元素」作为「基准数」,因此在输入数组
//    完全倒序 时, partition() 的递归深度会达到N ,即 最差空间复杂度 为 O(N) 。
//    每轮递归时,仅对 较短的子数组 执行哨兵划分 partition() ,
//    就可将最差的递归深度控制在O(logN) (每轮递归的子数组长度都 ≤ 当前数组长度),
//    即实现最差空间复杂度O(logN) 。

    /**
     * Tail Call 优化
     * @param nums
     * @param l
     * @param r
     */
    static void quickSort(int[] nums, int l, int r) {
        // 子数组长度为 1 时终止递归
        while (l < r) {
            // 哨兵划分操作
            int i = partition(nums, l, r);
            // 仅递归至较短子数组,控制递归深度
            if (i - l < r - i) {
                quickSort(nums, l, i - 1);
                l = i + 1;
            } else {
                quickSort(nums, i + 1, r);
                r = i - 1;
            }
        }
    }


    static private int partition1(int[] nums, int l, int r) {
         以 nums[l] 作为基准数
        int i = l, j = r;
        while (i < j) {
            while (i < j && nums[j] >= nums[l]) j--;  //从右向左  找到小于基数的元素
            while (i < j && nums[i] <= nums[l]) i++;  //从左向右  找到大于基数的元素
            //交换 i 和 j 未知的元素
            swap(nums, i, j);
        }
        //交换基数的位置
        swap(nums, i, l);
        return i;
    }

    /**
     * 随机基准数 优化
     * @param nums
     * @param l
     * @param r
     * @return
     */
   // 同样地,由于快速排序每轮选取「子数组最左元素」作为「基准数」,
    // 因此在输入数组 完全有序 或 完全倒序 时, partition() 每轮只划分一个元素,
    // 达到最差时间复杂度O(N²) 。

//    可使用 随机函数 ,每轮在子数组中随机选择一个元素作为基准数,
//    这样就可以极大概率避免以上劣化情况
    static int partition(int[] nums, int l, int r) {
        // 在闭区间 [l, r] 随机选取任意索引,并与 nums[l] 交换
        int ra = (int) (l + Math.random() * (r - l + 1));
        swap(nums, l, ra);
        // 以 nums[l] 作为基准数
        int i = l, j = r;
        while (i < j) {
            while (i < j && nums[j] >= nums[l]) j--;
            while (i < j && nums[i] <= nums[l]) i++;
            swap(nums, i, j);
        }
        swap(nums, i, l);
        return i;
    }

    static private void swap(int[] nums, int i, int j) {
        //交换
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }

    public static void main(String[] args) {
        // 调用
        int[] nums = {4, 1, 3, 2, 5};
        quickSort(nums, 0, nums.length - 1);
        for (int num : nums) {
            System.out.print(num + " ");
        }
    }
}
相关推荐
coder!mq26 分钟前
说几个常见的语法糖?
java·开发语言·算法
LuminousCPP1 小时前
数据结构-双向循环链表
c语言·数据结构·笔记·链表
不爱学英文的码字机器1 小时前
推荐算法梳理,六种主流模型与九步训练流程
算法·机器学习·推荐算法
白狐_7982 小时前
408数据结构第5章:树与二叉树②——遍历、线索树、森林与哈夫曼
数据结构·算法·深度优先
Jasmine_llq4 小时前
《P15800 [GESP202603 六级] 选数》
数据结构·long long·逆序线性 dp·从后往前推导·转移严格遵循题意·防止总和溢出·复杂度on
疯狂打码的少年4 小时前
【数据结构】串(字符串):基本概念与存储
数据结构·笔记
致Great5 小时前
科研人的 AI,不该只回答问题:我用字节TraeWork 跑了一遍真实研究任务
算法
纵有疾風起5 小时前
线性表的定义与基本操作 — 从逻辑结构到 ADT 接口
数据结构·算法·408·线性表·adt
wuyk5556 小时前
1.栈:后进先出的线性数据结构
c语言·数据结构·stm32·单片机
测试_AI_一辰6 小时前
AI Agent 评测最隐蔽的坑-记忆
人工智能·算法·ai·自动化·ai编程