快速排序学习笔记

news2025/2/25 3:08:58

代码框架

    // 在数组nums将下标从left到right中进行从小到大排序
    // 原理是先将一个元素排好序,然后将其他的元素排好序
    void sort(int[] nums, int left, int right) {
        if (left >= right) {
            return;
        }
        // 对数组nums[left,right]进行切分,使得nums[left,p-1]<=nums[p]<=nums[p+1,right]
        int p = partition(nums, left, right);
        // 去左右数组进行切分
        sort(nums, left, p - 1);
        sort(nums, p - 1, right);
    }



    // 在数组中nums[left,right]中寻找到一个分界点p
    int partition(int[] nums, int left, int right) {
        // 将数组中最左边的元素放入正确的位置后,返回该位置
        int pivot = nums[left];
        // 最后数组被分为三个区间,[left,i)和i和(j,right]
        int i = left + 1, j = right;
        while (i <= j) {
            // i右移找大于pivot的数
            while (i < right && nums[i] <= pivot) {
                i++;
            }
            // j左移找到小于pivot的数
            while (j > left && nums[j] >= pivot) {
                j--;
            }
            // 判断此时的i和j是否越界
            if (i >= j) {
                break;
            }
            swap(nums, i, j);
        }
        // 最后将pivot和j进行交换
        swap(nums, left, j);
        return j;
    }

    // 将元素随机打乱
    void shuffle(int[] nums) {
        int len = nums.length;
        Random random = new Random();
        for (int i = 0; i < len; i++) {
            // 生成[i,len-1]之间的随机数
            int index = i + random.nextInt(len - i);
            swap(nums, i, index);
        }
    }

    void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }

为什么要用shuffle将数组进行乱序处理?

目的是消除对初始输入的依赖,使得算法更具有随机性。在快排算法中,选择分区点的方式可能会影响算法的性能。如果数组已经有序或者近似有序,选择第一个元素作为分区点可能导致算法性能下降,因为分区点选择的不好可能导致快速选择算法的退化为O(n^2)的时间复杂度

在快速排序中,`partition()` 会选择一个基准值(pivot),然后重新排列数组或列表的元素,使得小于基准值的元素都位于它的左侧,大于基准值的元素都位于它的右侧。通常,它返回一个索引值,表示基准值在排序后所在的位置,同时也将数组或列表划分成两个部分。

再这么看快排就很简单了,一直分割左右两块,直到所有都排序完为止。

注意base case是左应该<=右!

另外,其实对比可以发现出,快排和二叉树的前序遍历是很像的:

/* 二叉树遍历框架 */
void traverse(TreeNode root) {
    if (root == null) {
        return;
    }
    /****** 前序位置 ******/
    print(root.val);
    /*********************/
    traverse(root.left);
    traverse(root.right);
}

一句话总结

快速排序是先将一个元素排好序,然后再将剩下的元素排好序。

快速排序的核心无疑是 `partition` 函数, `partition` 函数的作用是在 `nums[lo..hi]` 中寻找一个切分点 `p`,通过交换元素使得 `nums[lo..p-1]` 都小于等于 `nums[p]`,且 `nums[p+1..hi]` 都大于 `nums[p]`:

一个元素左边的元素都比它小,右边的元素都比它大,不就是它自己已经被放到正确的位置上了吗?

所以 `partition` 函数干的事情,其实就是把 `nums[p]` 这个元素排好序了。然后呢?你再把剩下的元素排好序不就得了。

剩下的元素有哪些?左边一坨,右边一坨,去吧,对子数组进行递归,用 `partition` 函数把剩下的元素也排好序。

从二叉树的视角,我们可以把子数组 `nums[lo..hi]` 理解成二叉树节点上的值,`sort` 函数理解成二叉树的遍历函数。

排序数组

912. 排序数组 - 力扣(LeetCode)

class Solution {
    public int[] sortArray(int[] nums) {
        shuffle(nums);
        quickSort(nums, 0, nums.length - 1);
        return nums;
    }

    void shuffle(int[] nums) {
        Random random = new Random();
        for (int i = 0; i < nums.length; i++) {
            int p = i + random.nextInt(nums.length - i);
            swap(nums, i, p);
        }
    }

    void quickSort(int[] nums, int left, int right) {
        if (left < right) {
            int pivot = partition(nums, left, right);
            // 分治,分别对左右的数据开始递归
            quickSort(nums, left, pivot - 1);
            quickSort(nums, pivot + 1, right);
        }
    }

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

        while (i <= j) {
            while (i <= right && nums[i] <= pivot) {
                i++;
            }
            while (j >= left && nums[j] > pivot) {
                j--;
            }

            if (i <= j) {
                swap(nums, i, j);
            }
        }
        swap(nums, left, j);
        return j;
    }

    void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

数组中的第k个最大元素

215. 数组中的第K个最大元素 - 力扣(LeetCode)

class Solution {
  public int findKthLargest(int[] nums, int k) {
        shuffle(nums);
        int left = 0, right = nums.length - 1;
        // 因为是找第 k 大的元素,而不是找第 k 小的元素,所以要从右边开始数 k 个
        k = nums.length - k;
        while (left <= right) {
            int p = partition(nums, left, right);
            // 缩小查找范围
            if (p < k) {
	            // 说明第k大的元素在分区右边
                left = p + 1;
            } else if (p > k) {
	            // 说明第k大的元素在分区左边
                right = p - 1;
            } else {
                return nums[p];
            }
        }
        // 未找到
        return -1;
    }
    
    void shuffle(int[] nums) {
        Random random = new Random();
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            int r = i + random.nextInt(n-i);
            swap(nums, i, r);
        }
    }
    
    int partition(int[] nums, int left, int right) {
        int p = nums[left], i = left + 1, j = right;
        while (i <= j) {
	        // `i` 向右移动,找到第一个大于 `p` 的元素
            while (i <= right && nums[i] <= p) i++;
            // `j` 向左移动,找到第一个小于等于 `p` 的元素
            while (j >= left && nums[j] > p) j--;
            if (i >= j) break;
            // 如果左区间有比 p 大的数,右区间有比 p 小的数,且下标左小于右,交换i与j
            swap(nums, i, j);
        }
        // 最后,将 `nums[left]`(即分区点原始位置)与 `nums[j]` 交换,将分区点放到正确的位置。
        swap(nums, left, j);
        // 返回分区点索引
        return j;
    }

    void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

补充:

优先级队列做法

class Solution {
    public int findKthLargest(int[] nums, int k) {
        // 利用优先级队列,自动是小根堆
        PriorityQueue<Integer> queue = new PriorityQueue<>();
        for (int i = 0; i < k; i++) {
            queue.add(nums[i]);
        }
        for (int i = k; i < nums.length; i++) {
            if (nums[i] > queue.peek()) {
                queue.poll();
                queue.add(nums[i]);
            }
        }
        return queue.peek();
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1383668.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Pandoc:markdown转word

简介&#xff1a;Pandoc是由John MacFarlane开发的标记语言转换工具&#xff0c;可实现不同标记语言间的格式转换&#xff0c;堪称该领域中的“瑞士军刀”。Pandoc使用Haskell语言编写&#xff0c;以命令行形式实现与用户的交互&#xff0c;可支持多种操作系统&#xff1b;Pand…

A connection was successfully established with the server but then an error

在使用EFCore生成数据库的时候&#xff0c;报上面的错误&#xff01; 解决方法&#xff1a; 加&#xff08;EncryptTrue;TrustServerCertificateTrue;&#xff09;即可&#xff1a; "ConnectionStrings": { "DefaultConnection": "Data SourceLAP…

大厂咋做支付系统的核对?

核对是保障资金安全的重要机制。 时效角度&#xff0c;主要有&#xff1a; &#xff08;准&#xff09;实时核对 准确性不如离线核对&#xff0c;且需相应实时核对平台建设 离线核对&#xff08;如 T1 核对&#xff09; 主要问题是发现问题的时机较为后置&#xff0c;部分场景…

软件测试|教你使用Python下载图片

前言 我一直觉得Windows系统默认的桌面背景不好看&#xff0c;但是自己又没有好的资源可以进行替换&#xff0c;突然我一个朋友提醒了我&#xff0c;网络上的图片这么多&#xff0c;你甚至可以每天换很多个好看的背景&#xff0c;但是如果让我手动去设置的话&#xff0c;我觉得…

Consider defining a bean of type ‘XXXX‘ in your configuration.

今天学习尚硅谷的SpringCloud时&#xff0c;发现支付模块无法启动&#xff0c;控制台输出下面的错误: 看起来可能是dao层没有被注入。 然后根据我已有的知识&#xff0c;我检查了注解Mapper Mapper public interface PaymentDao {public int create(Payment payment);public…

Nacos下载与安装【Linux】

&#x1f95a;今日鸡汤&#x1f95a; 我不是天生的王者&#xff0c;但我骨子里流动着不让我低头的血液。 ——《海贼王》 目录 &#x1f32d;1.官网下载 &#x1f37f;2.根据需求下载Linux版本 &#x1f953;3.上传到Linux &#x1f9c2;4.解压Nacos &#x1f9c8; 5.…

C++力扣题目701--二叉搜索树中的插入操作

给定二叉搜索树&#xff08;BST&#xff09;的根节点 root 和要插入树中的值 value &#xff0c;将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 &#xff0c;新值和原始二叉搜索树中的任意节点值都不同。 注意&#xff0c;可能存在多种有效的插入方式&a…

温和去油去黑头,只需敷上一刻钟的泥膜就够了

冬季天气干燥&#xff0c;很多朋友脸部更容易出油&#xff0c;连带着黑头也变多了。这是因为干燥的环境会刺激皮脂腺分泌&#xff0c;导致皮肤油脂分泌过多&#xff0c;容易堵塞毛孔形成黑头。因此&#xff0c;在冬季特别需要注意控油去黑头的工作。 控油去黑头需要清洁毛孔&am…

Sentinel微服务保护

文章目录 Sentinel微服务保护1.初识Sentinel1.1.雪崩问题及解决方案1.1.1.雪崩问题1.1.2.解决方案1.1.3.总结 1.2.服务保护技术对比1.3.Sentinel介绍和安装1.3.1.初识Sentinel1.3.2.安装Sentinel 1.4.微服务整合Sentinel 2.流量控制2.1.簇点链路2.1.快速入门2.2.流控模式2.2.1.…

docker应用:vocechat

简介&#xff1a;VoceChat是一款超轻量级的Rust聊天应用程序、API和SDK&#xff0c;优先考虑私人托管。使用VoceChat建立您自己的聊天功能&#xff01;作为一款非常好用的通讯应用程序&#xff0c;它可以让你与朋友、家人和同事进行即时消息聊天&#xff0c;支持图片视频的分享…

Python相对导入和绝对导入

目录结构&#xff1a; 在 en_de_model_CDDD.py 文件有两种导入方式可以导入utils.py&#xff0c;分别是 相对导入&#xff1a; from ...public_utils.utils import canonicalize_smiles 绝对导入&#xff1a; from public_utils.utils import canonicalize_smiles 这里推荐使…

Fluent 动网格应用:2.5D 网格重构

1 概述 2.5D 网格重构是一种快速网格重构方法&#xff0c;主要应用于涡旋压缩机等存在复杂平面运动且无法简化为二维计算的问题。 涡旋压缩机工作原理&#xff08;视频源&#xff1a;维基百科&#xff09; 适用于 2.5D 动网格的问题特点&#xff1a; 计算域几何形状为柱体类形…

WorkPlus企业内部即时通信新选择,打造高效协作新格局

在企业内部&#xff0c;快速、高效的沟通与协作是推动工作进程的关键。而即时通信工具成为了企业内部沟通的重要工具。作为一款优秀的企业内部即时通信工具&#xff0c;WorkPlus通过其出色的性能和独特的功能&#xff0c;为企业打造高效协作的新格局。 为什么选择WorkPlus作为企…

算法训练营Day43(背包问题)

1049. 最后一块石头的重量 II 1049. 最后一块石头的重量 II - 力扣&#xff08;LeetCode&#xff09; class Solution {public int lastStoneWeightII(int[] stones) {int sum 0;for(int num:stones){sumnum;}int target sum/2;//1 dp数组&#xff1a;背包容量为j&#xf…

MATLAB Deep learning

文章目录 Chapter 1: Machine Learning存在的问题过拟合Overfitting解决过拟合 regularization and validationregularization 正则化validation 验证 机器学习的类型有监督学习分类Classification回归Regression 无监督学习聚类 强化学习 Chapter 2: Neural Network神经网络的…

MySQL之多表连接查询、AS别名、扩展内容(information_schema的基本应用)

文章目录 前言一、引入多表连接查询二、多表连接查询案例1.准备对应的库表2.案例 三、AS别名用法示例 四、扩展内容1、information_schema的基本应用2、创建视图示例3、information_schema.tables视图的应用3.1、示例 五、show命令总结总结 前言 第三章内容主要描述了mysql使用…

力扣-盛最多水的容器

11.盛最多水的容器 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量。 说明&#xff1a;你不能倾斜…

HTML如何设置背景图片?有几种设置背景图片的办法?

我们在编辑网页时&#xff0c;如果觉得网页过于单调&#xff0c;这时便可以加上一张自己喜欢的背景图。这篇文章中&#xff0c;W3Cschool 小编给大家介绍下 HTML 中如何设置背景图片&#xff0c;分别有哪几种设置背景图片的方法。 方法一、HTML中设置背景图片 HTML中的<bo…

vue.js-3

#前后端交互

CSS3中多列布局详解

多列布局 概念&#xff1a;在CSS3之前&#xff0c;想要设计类似报纸那样的多列布局&#xff0c;有两种方式可以实现&#xff1a;一种是"浮动布局"&#xff0c;另一种是“定位布局”。 这两种方式都有缺点&#xff1a;浮动布局比较灵活&#xff0c;但不容易控制&…