Sorting Algorithms in Python (排序算法)

本篇文章主要介绍几种经典排序算法:冒泡排序、快速排序、选择排序、堆排序、插入排序、希尔排序、归并排序、桶排序和基数排序。并给出用python实现的算法代码。

目录

一、冒泡排序

二、快速排序

三、选择排序

四、堆排序

五、插入排序

六、希尔排序

七、归并排序

八、桶排序

九、基数排序

一、冒泡排序

冒泡排序如名所见,最大元素像泡泡一样逐渐向上冒,直至顶端(此处指从小到大排序)。最好时间复杂度为O(n),即需排序数组已为有序数组;最坏时间复杂度为O(n²),即需排序数组与要求顺序相反;平均复杂度为O(n²),如下代码所示,两层循环每层时间复杂度均为O(n)。空间复杂度为O(1),无需额外空间消耗,算法稳定。

代码:

def bubbleSort(nums):for i in range(len(nums)):is_sort = Truefor j in range(len(nums) - i - 1):if nums[j] > nums[j + 1]:nums[j], nums[j + 1] = nums[j + 1], nums[j]is_sort = Falseif is_sort:breakreturn nums

解释:

1)设置is_sort判断元素是否已经有序,若元素已完成该轮排序则直接跳出该轮循环进行新一轮排序(即i+1) 

2)每轮排序结束,最后一个元素已为最大值(按从小到大顺序排列),下一轮排序则只需对除此之外的元素进行排序:

        for j in range(len(nums) - i - 1):
            if nums[j] > nums[j + 1]:
                nums[j], nums[j + 1] = nums[j + 1], nums[j]

        其中i为轮次,-1是因为在元素比较时为向后比较(nums[j], nums[j + 1])。

3)算法图解:

图1 冒泡排序算法图解(图源@独影月下酌酒) 

二、快速排序

快速排序通过选择“哨兵”结合递归实现排序。最好时间复杂度为O(nlogn),最坏时间复杂度为O(n*n),平均复杂度为O(n*n)。空间复杂度为O(nlogn),算法不稳定。

代码:

def quickSort(nums, left, right):def partition(nums, left, right):pivot = nums[left]while left < right:while left < right and nums[right] >= pivot:right -= 1nums[left] = nums[right]while left < right and nums[left] <= pivot:left += 1nums[right] = nums[left]nums[left] = pivotreturn leftif left < right:pivotIndex = partition(nums, left, right)quickSort(nums, left, pivotIndex - 1)quickSort(nums, pivotIndex + 1, right)return nums

解释:

1)快速排序思想实质上是先找到哨兵,然后对哨兵左右边的元素再进行快速排序。partiton函数即对初始数组进行一次快排并返回哨兵元素的下标,接着主函数调用快排函数本身实现哨兵左右元素的排序。

2)需要注意在partition函数中,先对数组右边元素进行判断,若右边元素大于等于哨兵(pivot),则说明无需移动该元素,右指针左移,直到发现一个哨兵右侧元素小于pivot,此时需要把该元素移到左边,此时将该值赋给左指针指向的位置元素。当pivot右侧元素全都大于等于pivot且右指针仍然在左指针右边时,开始判断pivot左侧元素,当左侧元素小于等于pivot时,左指针右移直到发现某元素大于pivot,将该值赋给右指针指向的位置元素,直到左右指针重合,说明已找到哨兵pivot应放的位置,此时left=right=pivot下标,返回left或right即为pivot位置。

3)在得到哨兵位置后能确定pivot左侧元素均小于哨兵,右侧元素均大于哨兵,此时只需对哨兵左右元素再递归进行快速排序即可。

4)注意大前提left<right

5)算法图解:

图2 快速排序算法图解(图源@独影月下酌酒)  

三、选择排序

选择排序即每轮选取一个最小(大)元素放置起始位置,直到所有元素均排序完成。最好时间复杂度为O(n²); 平均时间复杂度为O(n²); 最差时间复杂度为O(n²)。

代码:

def selectSort(nums):for i in range(len(nums)):min_index = ifor j in range(i + 1, len(nums)):if nums[j] < nums[min_index]:min_index = jif min_index != i:nums[min_index], nums[i] = nums[i], nums[min_index]return nums

解释:

1)选择排序没有特别需要注意的地方,弄清算法逻辑就能很快实现。 

2)算法图解:

图3 选择排序算法图解(图源@独影月下酌酒)  

四、堆排序

堆排序分小顶堆和大顶堆,前者根节点小于左右结点,后者根节点大于左右结点,本文用大顶堆排序。最好时间复杂度为O(nlogn),平均时间复杂度为O(nlogn),最差时间复杂度为O(nlogn)。

代码:

def heap(nums):def adjustHeap(nums, i, length):lchild, rchild = 2 * i + 1, 2 * i + 2largest = iif lchild < length and nums[lchild] > nums[largest]:largest = lchildif rchild < length and nums[rchild] > nums[largest]:largest = rchildif largest != i:nums[largest], nums[i] = nums[i], nums[largest]adjustHeap(nums, largest, length)return numsfor i in range(len(nums) // 2)[::-1]:adjustHeap(nums, i, len(nums))for i in range(len(nums))[::-1]:nums[0], nums[i] = nums[i], nums[0]adjustHeap(nums, 0, i)return nums

解释:

1)拿到一个乱序数组,先将其初始为一个堆(即一棵二叉树),然后调整该堆(adjustHeap函数),使得根节点为最大值 :

        for i in range(len(nums) // 2)[::-1]:
                adjustHeap(nums, i, len(nums))

        堆初始化,从最后一个非叶子节点创建大顶堆

2)堆初始化完成且调整为大顶堆后,交换堆顶元素和需要排序的数组的最后一个元素,调整新数组为大顶堆,如此一来,每一轮调整都能找出此数组中的最大元素,并将其放在数组末端:

        for i in range(len(nums))[::-1]:
                nums[0], nums[i] = nums[i], nums[0]
                adjustHeap(nums, 0, i)

        注意:每完成完一轮调整堆,下一次需要调整的数组将不会包括已排序好的元素。

3)调整堆函数(adjustHeap)需要不断判断当前结点的左右结点是否均小于该结点,若出现某一结点大于父结点,则需要将更大的元素调整至父结点,形成一个小型的大顶堆,不断调整至整棵二叉树为一个大顶堆。

        if lchild < length and nums[lchild] > nums[largest]:
            largest = lchild
        if rchild < length and nums[rchild] > nums[largest]:
            largest = rchild
        if largest != i:
            nums[largest], nums[i] = nums[i], nums[largest]
            adjustHeap(nums, largest, length)

        需要注意的是,在交换完新的更大结点后要重新进行堆调整,以确保每一个小堆均为一个大顶堆。 

4)算法图解:

图4 堆排序算法图解(图源@独影月下酌酒)

五、插入排序

插入排序思想很简单,取出一个元素,在已排好序的数组中找到自己要插入的位置放入即可。最好时间复杂度为O(n),平均时间复杂度为O(n²),最差时间复杂度为O(n²)。

代码:

def insertSort(nums):for i in range(len(nums) - 1):curNum, preIndex = nums[i + 1], iwhile preIndex >= 0 and curNum < nums[preIndex]:nums[preIndex + 1] = nums[preIndex]preIndex -= 1nums[preIndex + 1] = curNumreturn nums

解释:

1)curNum和preIndex分别为当前需要插入的元素和其前一个元素下标,若当前元素小于前一个元素,说明该元素应该插在前一个元素之前,即前一个元素需要后移一位,后移完成,preIndex下标需向前移动一位,继续判断当前元素和前一个元素的大小关系,直到与第一个元素比较完成后确定curNum最终应该插入的位置 。

        while preIndex >= 0 and curNum < nums[preIndex]:
            nums[preIndex + 1] = nums[preIndex]
            preIndex -= 1
        nums[preIndex + 1] = curNum

        需要注意的是,在最后放curNum时,由于最后一次判断已将preIndex前移了一位,故此时应插入的位置为preIndex + 1。

2)算法图解:

图5 插入排序算法图解(图源@独影月下酌酒) 

六、希尔排序

希尔排序本质上是插入排序的一种,思想同插入排序一样,不同的的是,希尔排序会先分组,接着组内快速排序,随后不断缩小组间距直至所有元素排序完成。最好时间复杂度为O(n),平均时间复杂度为O(n²),最差时间复杂度为O(n²)。

代码:

def shellSort(nums):gap = len(nums) // 2while gap:for i in range(gap, len(nums)):curNum, preIndex = nums[i], i - gapwhile preIndex >= 0 and curNum < nums[preIndex]:nums[preIndex + gap] = nums[preIndex]preIndex -= gapnums[preIndex + gap] = curNumgap //= 2return nums

解释:

1)gap即为组间距,希尔排序主体仍是快速排序,不断调整组间距直至组间距为0:

        while gap:
                for i in range(gap, len(nums)):
                    curNum, preIndex = nums[i], i - gap
                    while preIndex >= 0 and curNum < nums[preIndex]:
                        nums[preIndex + gap] = nums[preIndex]
                        preIndex -= gap
                    nums[preIndex + gap] = curNum
                gap //= 2
       return nums

        注意:与简单的插入排序不同的是,希尔排序的前一个元素下标为 i - gap。

2)算法图解:

图6 希尔排序算法图解(图源@独影月下酌酒)  

七、归并排序

归并排序的核心思想即是分而治之,先将数组从中间分为左右两个数组,对左右两个数组进行归并排序后合并左右两个数组即可。

代码:

def mergeSort(nums):def merge(left, right):i, j = 0, 0result = []while i < len(left) and j < len(right):if left[i] <= right[j]:result.append(left[i])i += 1else:result.append(right[j])j += 1result = result + left[i:] + right[j:]return resultif len(nums) <= 1:return numsmid = len(nums) // 2left = mergeSort(nums[:mid])right = mergeSort(nums[mid:])return merge(left, right)

解释:

1)若数组长度<=1则无需排序返回数组本身即可。

2)将需要排序的数组一分为二,分别进行归并排序,即左右子数组分别进行递归(mergeSort)得到两个排序好的数组,最后合并这两个数组即可。

3)合并左右两个数组即设置两个指针分别遍历两个数组,取两者中最小值加入结果数组即可,直到某个数组被遍历完,只需把另一个数组的剩余元素加入结果数组即可(因为两个数组都已事先被排序好了):

        while i < len(left) and j < len(right):
            if left[i] <= right[j]:
                result.append(left[i])
                i += 1
            else:
                result.append(right[j])
                j += 1
        result = result + left[i:] + right[j:]
        return result

4)算法图解:

图7 归并排序算法图解(图源@独影月下酌酒)   

八、桶排序

桶排序即将数组元素根据某种映射放在各个桶里,先在桶内进行排序,然后将排序好的元素拼接起来即可。最好时间复杂度为O(n+k),最差时间复杂度为O(n²),平均时间复杂度为O(n+k)。

def bucketSort(nums):def insertSort(nums):for i in range(len(nums) - 1):curNum, preIndex = nums[i + 1], iwhile preIndex >= 0 and curNum < nums[preIndex]:nums[preIndex + 1] = nums[preIndex]preIndex -= 1nums[preIndex + 1] = curNumreturn numsbucketSize = 4bucketCount = (max(nums) - min(nums)) // bucketSize + 1buckets = [[] for _ in range(bucketCount)]for num in nums:buckets[(num - min(nums)) // bucketSize].append(num)nums.clear()for bucket in buckets:insertSort(bucket)nums.extend(bucket)return nums

解释:

1)根据桶大小和元素分布确定桶个数,初始化桶内元素:

        bucketSize = 4

        bucketCount = (max(nums) - min(nums)) // bucketSize + 1        

        buckets = [[] for _ in range(bucketCount)] 

2)判断数组元素应该放在哪个桶,将元素全都装桶后清空数组方便放后续排序好的元素:

        for num in nums:
                buckets[(num - min(nums)) // bucketSize].append(num)
        nums.clear()

3)元素装桶完成后进行桶内元素排序,本文桶内元素用的插入排序,然后将排序好的元素全部一起extend到原数组中:

        for bucket in buckets:
                insertSort(bucket)
                nums.extend(bucket)

4)算法图解:

 图8 桶排序算法图解(图源@独影月下酌酒)   

九、基数排序

基数排序是特殊的桶排序,其根据元素每位数字来分配桶(桶个数为10,分别对应各位取值0~9),按照先低位排序后高位排序的顺序依次排序直到元素有序即可,排序轮次即为数组中最大元素的位数。最好时间复杂度为O(n×k),最差时间复杂度为O(n×k),平均时间复杂度为O(n×k)。

代码:

def radixSort(nums):mod, div = 10, 1mostBit = len(str(max(nums)))buckets = [[] for _ in range(mod)]while mostBit:for num in nums:buckets[num // div % mod].append(num)i = 0for bucket in buckets:while bucket:nums[i] = bucket.pop(0)i += 1div *= 10mostBit -= 1return nums

解释:

1)mod和div用于取元素各个位数的取值,mostBit是轮次(即元素最大值的位数),buckets个数为10,分别对应各位取值0~9。

2)先将数组中每个元素放到对应桶中,然后按序弹出元素完成第一轮低位排序,接着进行第二轮高位排序,直到所有位均排序完成,此时数组排序完成:    

        while mostBit:
                for num in nums:
                    buckets[num // div % mod].append(num)

                i = 0
                for bucket in buckets:
                    while bucket:
                        nums[i] = bucket.pop(0)
                        i += 1
                div *= 10
                mostBit -= 1

3)算法图解:

 图9 基数排序算法图解(图源@独影月下酌酒)

附上@独影月下酌酒的博客以供参考学习:

Python实现十大排序算法_python排序-CSDN博客

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

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

相关文章

(非技术) 基因遗传相关知识学习笔记

目录 一、基因遗传名词解释 二、什么叫显性遗传和隐性遗传&#xff1f; 三、如何确定遗传性质呢&#xff1f;是显性还是隐性&#xff1f; 四、常规例子1&#xff1a; 五、常规例子2&#xff1a; 六、实际案例&#xff1a; 七、思考题&#xff1a; 八、参考&#xff1a; …

智慧InSAR专题———模拟数据实现现实场景异常形变点识别(项目讲解)

续上篇 文章目录 &#xff08;一项技术的复现&#xff0c;我们应该有打破砂锅问到底的态度&#xff0c;我找到了这篇文章的一些灵感来源&#xff0c;包括算法和编程以及专业知识等&#xff0c;对我而言也是受益匪浅&#xff09;1. 数据准备1.1 A deep learning approach to de…

MySQL选择普通索引还是唯一索引(2/16)

普通索引和唯一索引 基本概述 MySQL中可以创建普通索引与唯一索引&#xff0c;这两种索引的区别是&#xff1a; 普通索引&#xff08;Non-Unique Index&#xff09;&#xff0c;也称为非唯一索引&#xff0c;它允许索引中的条目具有重复的键值。普通索引的主要目的是加快查询…

Android MTK 屏下指纹的调试过程记录

Demo链接 -----> https://download.csdn.net/download/u011694328/89118346 一些品牌手机都有了屏下指纹的功能&#xff0c;还算是个比较新颖的功能&#xff0c;最近有项目需要使用屏下指纹&#xff0c; 使用的是汇顶&#xff08;Goodix&#xff09;的指纹方案&#xff0c…

架构设计-订单系统之业务的设计与实现

一、背景简介 订单业务一直都是系统研发中的核心模块&#xff0c;订单的产生过程&#xff0c;与系统中的很多模块都会高度关联&#xff0c;比如账户体系、支付中心、运营管理等&#xff0c;即便单看订单本身&#xff0c;也足够的复杂&#xff1b; 业务在发展的过程中&#xff…

Go gorm库(详细版)

目录 01. 什么是ORM 02. 环境搭建 03. 连接数据库 高级设置 gorm 的命名策略 创建表 日志显示 04. 模型定义 定义一张表 自动生成表结构 修改表字段大小 字段标签 05. 单表查询 5.1 表结构 5.2 添加单条记录 5.3 批量插入 5.4 单条数据查询 5.5 根据主键查询…

Vue3学习03 pinia

Vue3学习 pinia pinia一个简单效果搭建 pinia 环境存储读取数据示例 修改数据 (三种方式)storeToRefsgetters$subscribestore组合式写法 pinia 在vue2中使用vuex&#xff0c;在vue3中使用pinia。 集中式状态管理&#xff0c;&#xff08;状态数据&#xff09;。多个组件共享数…

多态【C/C++复习版】

目录 一、多态是什么&#xff1f;如何实现&#xff1f; 二、 什么是重写&#xff1f;有什么特点&#xff1f; 三、什么是协变&#xff1f; 四、析构函数能实现多态吗&#xff1f;为什么要实现&#xff1f; 五、override和final的作用是什么&#xff1f; 六、 多态的原理是…

Linux下网络编程基础知识--协议

网络基础 这一个课程的笔记 相关文章 协议 Socket编程 高并发服务器实现 线程池 协议 一组规则, 数据传输和数据的解释的规则。 比如说依次发送文件的文件名, 文件的大小, 以及实际的文件, 这样规定发送一个文件的顺序以及发送的每一个部分的格式等可以算是一种协议 型协议 …

防火墙用户管理技术——AAA

目录 一.AAA功能 (1).认证方式 (2).授权方式 (3).计费方式 二.RADUIUS协议 三.用户组织架构及管理 管理员认证登录方式 1.console 2.web 3.telnet 4.ssh 5.ftp 四.认证方式 五.AAA远程登录 1.端口添加IP地址达到互通 2.AAA视图进行配置 3.结果​编辑 一.AAA功能…

Android源码解析之截屏事件流程

今天这篇文章我们主要讲一下Android系统中的截屏事件处理流程。用过android系统手机的同学应该都知道&#xff0c;一般的android手机按下音量减少键和电源按键就会触发截屏事件&#xff08;国内定制机做个修改的这里就不做考虑了&#xff09;。那么这里的截屏事件是如何触发的呢…

【Redis深度解析】揭秘Cluster(集群):原理、机制与实战优化

Redis Cluster是Redis官方提供的分布式解决方案&#xff0c;通过数据分片与节点间通信机制&#xff0c;实现了水平扩展、高可用与数据容灾。本文将深入剖析Redis Cluster的工作原理、核心机制&#xff0c;并结合实战经验分享优化策略&#xff0c;为您打造坚实可靠的Redis分布式…

双数据库的安装

双MySQL的安装 【0】前言 ​ 本地已经安装过mysql5.1版本&#xff0c;应项目需求需要安装mysql5.7版本&#xff1b; ​ 官方网站下载对应版本&#xff1a;https://downloads.mysql.com/archives/community/ 【1】压缩包下载完成后解压至本地磁盘 【2】进入根目录下bin文件夹…

Flask基于flask_login实现登录、验证码

flask_login 是一个 Flask 扩展&#xff0c;用于在 Flask web 应用中实现用户会话管理。它允许你跟踪哪些用户已经登录&#xff0c;并管理他们的登录状态。flask_login 提供了用户认证的基础结构&#xff0c;但具体的用户验证&#xff08;如用户名和密码检查&#xff09;和存储…

泽众Testone自动化测试平台,测试用例支持单个调试执行,同步查看执行日志

泽众Testone自动化测试平台之前版本&#xff0c;测试用例批量和单个执行&#xff0c;必须要通过测试集操作执行&#xff0c;操作略繁琐&#xff0c;我们通过本轮优化升级&#xff0c;测试用例直接可以单个调试执行&#xff0c;同步查看执行日志&#xff0c;操作上去繁就简&…

OJ刷题日记:1、双指针(1)

目录 1、283.移动零 2、1089.复写零 3、202.快乐数 1、283.移动零 题目&#xff1a; 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 …

第十届 蓝桥杯 单片机设计与开发项目 省赛

第十届 蓝桥杯 单片机设计与开发项目 省赛 输入&#xff1a; 频率信号输入模拟电压输入 输出&#xff08;包含各种显示功能&#xff09;&#xff1a; LED显示SEG显示DAC输出 01 数码管显示问题&#xff1a;数据类型 bit Seg_Disp_Mode;//0-频率显示界面 1-电压显示界面 un…

电脑无法开机?原因分析与解决方案

电脑无法开机是一种常见的问题&#xff0c;可能会给用户带来诸多困扰。无法启动可能是由于硬件故障、软件问题或者其他未知原因引起的。在本文中&#xff0c;我们将介绍三种常见的方法来解决电脑无法开机的问题&#xff0c;以帮助用户尽快恢复正常使用。 方法1&#xff1a;检查…

什么是队列

队列是一种特殊类型的线性表&#xff0c;其只允许在一端进行插入操作&#xff0c;而在另一端进行删除操作。具体来说&#xff0c;允许插入的一端称为队尾&#xff0c;而允许删除的一端称为队头。这种数据结构遵循“先进先出”&#xff08;FIFO&#xff09;的原则&#xff0c;即…

ubuntu安装irtualbox注意事项

下载官网7.0版本&#xff0c;ubuntu22.04,安装一直出错误&#xff0c;查到了下面兄弟的办法&#xff0c;仍然不幸&#xff0c;最后使用apt重装&#xff0c;请按第二部分流程安装&#xff0c;安装6.1,可以用 第一部分&#xff0c;反正我是没搞通&#xff0c;7.0反复的出现相同的…