经典算法之快速排序

news2024/12/23 15:06:25

快速排序

【思想】选择一个元素作为标准,分别将小于该元素的元素放入该元素左边,大于该元素的元素放到该元素的右边,接下来分别对左右两边区间进行同样操作,直到整个数组有序。

【例子】

上述是一个未排序的数组,首先选择一个元素作为切分元素(下图中选择元素4),将小于切分元素的放到左边,大于切分元素的放到右边,最终我们确定了一个元素(4)排完序之后的最终位置,这个过程称为partition。
在这里插入图片描述
在这里插入图片描述
接下来就是分别对左边和右边的区间以此进行同样的操作,直到完成整个数组的排序。

【partition详细实现步骤】

PS: 在划分过程中,左边表示小于(等于)切分元素的区间,右边是大于(等于)切分元素的区间。
在这里插入图片描述
【step1】一般选择数组中的第一个元素为切分元素pivot,然后遍历数组进行比较。下图中i用来遍历数组,j用来表示第一个区间(小于切分元素的区间)的最后一个位置。
在这里插入图片描述
【step 2】当前元素(i指向的元素)小于等于pivot时,继续向前,i=i+1;当大于pivot时,j+1,然后交换位置j和i对应的元素。
在这里插入图片描述
在这里插入图片描述
【step3】进行到下图时,表示已经把6后面的元素分为两个区间,左边是小于(等于)该元素的nums[left, j],右边是大于该元素的nums[j+1, right]。最后将j指向的元素和left指向元素交换位置,完成区间的划分。
ps:j指向第一个区间(小于切分元素区间)的最后一个位置。
在这里插入图片描述

在这里插入图片描述

【代码实现】

import random
class Solution:
    def sortArray(self, nums: List[int]) -> List[int]:
        self.quickSort(nums, 0, len(nums)-1)
        return nums

    def quickSort(self, nums, left, right):
        if left >= right:
            return

        pivot_index = self.partition(nums, left, right)
        self.quickSort(nums, left, pivot_index-1)
        self.quickSort(nums, pivot_index +1, right)

    def partition(self, nums, left, right):
        index = left + random.randint(0, (right-left))
        self.swap(nums, left, index)
        pivot = nums[left]
        for i in range(left+1, right+1):
        	if nums[i] > pivot:
        		j += 1
        		self.swap(nums, i, j)
        self.swap(nums, left, j)#最终交换第一个元素和j指向元素
		return j    
    def swap(self, nums, index1, index2):
        nums[index1], nums[index2] = nums[index2], nums[index1]

PS:上述partition中,之所以随机选取一个元素和第一个元素进行交换,是因为当元素本身有序(顺序或者逆序)时,每次partition进行遍历的元素个数只比上次少了一个,时间复杂度为O(N^2)。即:partition在有序数组上表现很差,因此用随机选择来打破这种平衡。

快速排序之双路快排

【引入】当数组中重复元素较多时,如果随机选择元素与pivot相等的概率较大,相同元素进行交换是没有意义的。
在这里插入图片描述
极端的情况:当元素都相等时,随机选择pivot无效!
在这里插入图片描述
【解决方法】
(1)双路快排:把与pivot相等的元素平均分到数组的两侧。
在这里插入图片描述

(2)三路快排:把与pivot相等元素挤到中间。
在这里插入图片描述
【双路快排】
最终划分区间满足:左边区间nums[left, le)<=pivot:小于等于pivot,右边区间(ge, right]>=pivot
在遍历过程中,le指向左边区间右边界的后一个位置,用ge指向右边区间左边界的前一个位置
在这里插入图片描述
le遇到大于等于pivot元素停下来,ge遇到小于等于pivot元素停下来
然后交换对应元素
在这里插入图片描述
最后要注意partition遍历结束后left要和le还是ge交换位置?
在这里插入图片描述

跳出循环后,lege有两种情况:(1)le = ge(2)ge>le
在这里插入图片描述
根据上述可知,lege停下来分别指向大于等于pivot的元素,和小于等于pivot的元素。
(1)当两者重合时,lege指向同一个元素,pivot与交换lege均可。
(2)如果不重合,如果pivotle进行交换,就可能将大于pivot的元素交换到第一个区间里,这是错误的。
因此最终应与ge交换位置来适应以上两种情况。

【代码实现】

def partition(self, nums, left, right):
        index = left + random.randint(0, (right-left))
        self.swap(nums, left, index)
        pivot = nums[left]
        le = left + 1
        ge = right
        while True:
        	#此处注意下方and左右两个条件顺序不能换,否则会出现数组越界。
            while le <= ge and nums[le] < pivot:
                le += 1
            while le <= ge and nums[ge] > pivot:
                ge -= 1
            #此时le来到第一个大于等于pivot位置
            #此时ge指向第一个小于等于pivot位置
            if le >= ge:
                break
            self.swap(nums, le, ge)
            le += 1
            ge -= 1
        self.swap(nums, left, ge)
    #返回第一个区间最后一个元素的位置(经过partion后进行确定的位置)
        return ge

快速排序之三路快排

【思想】将与切分元素pivot相等的值挤到数组中间。(下图中假设pivot=6)
在这里插入图片描述
【实现细节】最终划分为三个区间:第一个区间:严格小于pivot区间,第二个区间:等于pivot区间,第三个区间:严格大于pivot区间。
为实现上述区间划分,用i进行将切分元素pivot后面元素进行遍历,根据当前元素与pivot大小分为三种情况:
(1)nums[i]<pivot,应划分到第一个区间,即将等于pivot区间的第一个位置进行交换(此时第一个区间多了一个单位)。
在这里插入图片描述

(2)nums[i]=pivot, 继续向前遍历。
(3)nums[i] > pivot, 应划分到第三个区间,将严格大于pivot区间的第一个位置的前一个位置进行交换(此时第三个区间多了一个单位)。
nums[i] > pivot情况

继续看当前元素位置的值与 pivot大小。
PS: 最后将切分元素和第一个区间的最后一个位置进行交换。
【概括】经过三路快排的数组如下图,将元素值相同的元素挤到中间确定了更多元素的位置,接下来递归处理的区间大大减小。
在这里插入图片描述
【例子】

(1)初始化

leftright分别表示整个区间的开始和结束。

pivot = nums[left], 选择第一个元素为切分元素。
lt = left + 1
gt = right
i = left + 1
nums[left+1, lt) < pivot, lt是第一个区间最后位置的下一个位置,也是第二个区间的第一个位置
nums[lt, i) == pivot,i表示当前元素,还未进行比较。
nums(gt, right] > pivot, gt表示第三个区间第一个位置的前一个位置。

在这里插入图片描述

(2)交换过程示例

nums[i] < pivot时,

应该在第一个区间,因此将pivot与第一个区间的下一个位置(lt)进行交换。
在这里插入图片描述

交换后:lt向前移一位,i也向前移一位。
在这里插入图片描述

nums[i] > pivot,

nums[i]应该属于第三个区间,因此,将第三个区间第一个位置的前一个位置(gt)进行交换。
在这里插入图片描述

交换后:gt向前移动一位,i不移动。
在这里插入图片描述

(3)循环终止

此时,循环终止,表示i看完了left后面的所有元素。
在这里插入图片描述

最后将切分位置元素与第一个区间里的最后一个位置(lt-1)进行交换,完成区间划分
在这里插入图片描述

【三路快排实现代码】

import random
class Solution:
    def sortArray(self, nums: List[int]) -> List[int]:
        self.quickSort(nums, 0, len(nums)-1)
        return nums

    def quickSort(self, nums, left, right):
        if left >= right:
            return
        #三路快排每次确定两个位置 ,将遍历空间划分为三个区间,
        #三个区间需要有两个分界位置,因此每次partition返回两个元素
        index = left + random.randint(0, (right-left))
        self.swap(nums, left, index)
        pivot = nums[left]
        #nums[left, lt) < pivot
        #lt是第一个区间最后位置的下一个位置,也是第二个区间的第一个位置
        #nums[lt, i) = pivot
        #nums(gt, right] > pivot gt是最后一个区间第一个位置的前一个位置
        lt = left + 1
        gt = right
        i = left + 1
        while i <= gt:
            if nums[i] < pivot:
                #将nums[i]与第二个区间的第一个元素进行交换
                self.swap(nums, i, lt)
                lt += 1
                i += 1
            elif nums[i] > pivot:
                self.swap(nums, i, gt)
                gt -= 1
                #此时i不移动,因为交换后的元素需要让下一轮看到
            else:
                i += 1

        #将切分元素交换到对应位置
        self.swap(nums, left, lt - 1)

        self.quickSort(nums, left, lt-2)
        self.quickSort(nums, gt + 1, right)    
        
    def swap(self, nums, index1, index2):
        nums[index1], nums[index2] = nums[index2], nums[index1]

参考:B站https://space.bilibili.com/236935093/channel/collectiondetail?sid=378477。

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

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

相关文章

前端三个小妙招

整理下本人在工作中撸代码遇到的一些刚看时一脸懵逼&#xff0c;实则很简单就能解决的小妙招&#xff0c;希望对大家有所帮助哟~ 伪元素动态改变其样式 我们都用过伪元素&#xff0c;什么::before,::after啊这些等等&#xff0c;但是他们都不会直接在代码里html中生成标签&am…

使用MASA全家桶从零开始搭建IoT平台(一)环境准备

前言 本系列文章以IoT开发小白的角度&#xff0c;从零开始使用.Net为主要技术搭建一个简单的IoT平台&#xff0c;由于是入门系列&#xff0c;所有代码以围绕IoT基础业务场景为主&#xff0c;不会涉及DDD等设计思想。 架构图 这里是我们整个IoT平台的架构图。 一、设备接入…

深入了解Synchronized同步锁的优化

大家好&#xff0c;我是易安&#xff01;今天我们来聊一下Synchronized同步锁的优化。 在并发编程中&#xff0c;多个线程访问同一个共享资源时&#xff0c;我们必须考虑如何维护数据的原子性。 在JDK1.5之前&#xff0c;Java是依靠Synchronized关键字实现锁功能来做到这点的。…

Java核心技术 卷1-总结-10

Java核心技术 卷1-总结-10 通配符类型通配符概念通配符的超类型限定无限定通配符通配符捕获 通配符类型 通配符概念 通配符类型中&#xff0c;允许类型参数变化。 例如&#xff0c;通配符类型Pair<? extends Employee>表示任何泛型Pair类型&#xff0c;它的类型参数是…

LeetCode_动态规划_中等_1105.填充书架

目录 1.题目2.思路3.代码实现&#xff08;Java&#xff09; 1.题目 给定一个数组 books &#xff0c;其中 books[i] [thicknessi, heighti] 表示第 i 本书的厚度和高度。你也会得到一个整数 shelfWidth。 按顺序将这些书摆放到总宽度为 shelfWidth 的书架上。 先选几本书放…

机器学习基本模型与算法在线实验闯关

机器学习基本模型与算法在线实验闯关 文章目录 机器学习基本模型与算法在线实验闯关一、缺失值填充二、数据标准化三、支持向量机分类模型及其应用四、逻辑回归模型及其应用五、神经网络分类模型及其应用六、线性回归模型及其应用七、神经网络回归模型及其应用八、支持向量机回…

AIGC跨过奇点时刻,亚马逊云科技展露新峥嵘

AIGC是云计算的Game changer&#xff0c;将从根本上改变云计算乃至整个科技行业的游戏规则&#xff0c;作为云计算行业的Game Rulemaker&#xff0c;亚马逊云科技也展露出新的峥嵘。4月13日&#xff0c;亚马逊云科技宣布推出生成式AI新工具&#xff0c;包括Amazon Bedrock和Ama…

Java核心技术 卷1-总结-11

Java核心技术 卷1-总结-11 Java 集合框架将集合的接口与实现分离Collection接口迭代器泛型实用方法集合框架中的接口 Java 集合框架 将集合的接口与实现分离 Java集合类库将接口&#xff08;interface&#xff09;与实现&#xff08;implementation&#xff09;分离。 例如队…

把Windows装进内存条里,提前感受超越PCIe 6.0固态的顶级体验

这两年电脑内存条是越来越白菜价了&#xff0c;看到大伙儿慢慢富足起来的内存容量&#xff0c;小忆是由衷地感到高兴。 不过话说&#xff0c;动不动 32G、64G 内存你真能用得完吗&#xff1f;为了榨干大家真金白银买来的空闲内存价值。 咱这期整个骚操作——将 Windows 11 系统…

虚拟机安装linux系统centos(保姆级)

虚拟机安装linux系统centos 1.软硬件准备2.虚拟机准备1.打开VMware选择新建虚拟机2.典型安装与自定义安装3.虚拟机兼容性选择4.选择稍后安装操作系统5.操作系统的选择7.处理器与内存的分配8.网络连接类型的选择&#xff0c;网络连接类型一共有桥接、NAT、仅主机和不联网四种。9…

ArcMap气温数据插值处理

一、插值数据处理 1.先把气温excel在excel中另存为气温.csv&#xff08;网盘链接中有转好的csv文件&#xff09;&#xff0c;导入数据江苏.shp和jiangsustation.shp 和气温.csv 数据在文末百度网盘链接中 2.打开jiangsustation.shp的属性表&#xff0c;连接与字段-连接-连接的…

RabbitMQ的五种工作模式

目录 前言介绍 &#xff08;1&#xff09;启动RabbitMQ &#xff08;2&#xff09;账户管理 一、简单模式 &#xff08;1&#xff09;概念 &#xff08;2&#xff09;生产者代码 &#xff08;3&#xff09;消费者代码 二、工作队列模式 &#xff08;1&#xff09;概念…

LLVM编译流程

一、LLVM 1.1 LLVM概述 LLVM是构架编译器(compiler)的框架系统,以C编写而成,用于优化以任意程序语言编写的程序的编译时间(compile-time)、链接时间(link-time)、运行时间(run-time)以及空闲时间(idle-time),对开发者保持开放,并兼容已有脚本.LLVM计划启动于2000年,最初由美国…

Dynamics 365 开启 Modern advanced find后如何创建个人视图

本篇是自己在新的环境被新的高级查找晃的没找到如何创建个人视图而发。如何在老的高级查找页面创建这里就不表了。 D365 2022 Wav1后有了新的feature叫Modern advanced find&#xff0c;开启方式如下&#xff0c;进Power Platform的管理中心&#xff0c;找到你对应的环境&#…

Java核心技术 卷1-总结-6

Java核心技术 卷1-总结-6 接口示例接口与回调Comparator接口对象克隆 lambda表达式为什么引入lambda表达式lambda表达式的语法 接口示例 接口与回调 回调&#xff08;callback&#xff09;是一种常见的程序设计模式。在这种模式中&#xff0c;可以指出某个特定事件发生时应该…

珍藏多年的MySQL函数大全笔记,掌握数据库真不难

做程序员的谁会离得开数据库呢&#xff1f;今天就来分享一下我整理的MySQL的常用函数&#xff0c;基本上囊括了平时要用的函数&#xff0c;它们已经陪我走过了不少年头了&#xff0c;风里来雨里去&#xff0c;缝缝补补又几年&#xff0c;希望能帮到你们&#xff01; 如果数据库…

短视频app开发:如何实现视频直播功能

短视频源码的实现 在短视频app开发中&#xff0c;实现视频直播功能需要借助短视频源码。短视频源码可以提供一个完整的视频直播功能模块&#xff0c;包括视频采集、编码、推流等。因此&#xff0c;我们可以选择一些开源的短视频源码&#xff0c;例如LFLiveKit、ijkplayer等&am…

Nacos简介 安装 配置

简介 什么是注册中心 注册中心在微服务项目中扮演着非常重要的角色&#xff0c;是微服务架构中的纽带&#xff0c;类似于通讯录&#xff0c;它记录了服务和服务地址的映射关系。在分布式架构中&#xff0c;服务会注册到这里&#xff0c;当服务需要调用其它服务时&#xff0c;…

RHCE(六)

目录 1.编写脚本for1.sh,使用for循环创建20账户&#xff0c;账户名前缀由用户从键盘输入&#xff0c;账户初始密码由用户输入&#xff0c;例如: test1、test2、test3、.....、 test10 &#xff08;1&#xff09;编写脚本 &#xff08;2&#xff09;运行脚本 &#xff08;3&…

机器学习——主成分分析法(PCA)概念公式及应用python实现

机器学习——主成分分析法&#xff08;PCA&#xff09; 文章目录 机器学习——主成分分析法&#xff08;PCA&#xff09;一、主成分分析的概念二、主成分分析的步骤三、主成分分析PCA的简单实现四、手写体识别数字降维 一、主成分分析的概念 主成分分析&#xff08;PCA&#x…