Sorting Algorithms in Python (排序算法)

news2024/11/28 14:27:54

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

目录

一、冒泡排序

二、快速排序

三、选择排序

四、堆排序

五、插入排序

六、希尔排序

七、归并排序

八、桶排序

九、基数排序

一、冒泡排序

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

代码:

def bubbleSort(nums):
	for i in range(len(nums)):
		is_sort = True
		for 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 = False
		if is_sort:
			break
	return 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 -= 1
			nums[left] = nums[right]

			while left < right and nums[left] <= pivot:
				left += 1
			nums[right] = nums[left]
		nums[left] = pivot
		return left

	if 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 = i
		for j in range(i + 1, len(nums)):
			if nums[j] < nums[min_index]:
				min_index = j
		if 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 + 2
		largest = i
		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)
		return nums

	for 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], i
		while preIndex >= 0 and curNum < nums[preIndex]:
			nums[preIndex + 1] = nums[preIndex]
			preIndex -= 1
		nums[preIndex + 1] = curNum
	return 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) // 2
	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

解释:

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, 0
		result = []
		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

	if len(nums) <= 1:
		return nums

	mid = len(nums) // 2
	left = 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], i
			while preIndex >= 0 and curNum < nums[preIndex]:
				nums[preIndex + 1] = nums[preIndex]
				preIndex -= 1
			nums[preIndex + 1] = curNum
		return nums

	bucketSize = 4
	bucketCount = (max(nums) - min(nums)) // bucketSize + 1
	buckets = [[] 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, 1
	mostBit = len(str(max(nums)))
	buckets = [[] for _ in range(mod)]
	
	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
	return 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.coloradmin.cn/o/1586956.html

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

相关文章

Flutter使用小技巧四(持续更新...)

Flutter使用小技巧四(持续更新...) 解决依赖冲突Row、Column主轴对齐方式Future cathError 异常时添加默认返回值如何在Column中嵌入横着滚动的ListViewFlutter 渲染流程3大步骤Offset.zero & size含义MaterialPageRoute中maintainState作用重载运算符Completer的使用Sta…

Elasticsearch部署安装

环境准备 Anolis OS 8 Firewall关闭状态&#xff0c;端口自行处理 Elasticsearch&#xff1a;7.16.1&#xff08;该版本需要jdk11&#xff09; JDK&#xff1a;11.0.19 JDK # 解压 tar -zxvf jdk-11.0.19_linux-x64_bin.tar.gz# 编辑/etc/profile vim /etc/profile# 加入如下…

国内如何使用Suno-v3 AI音乐生成大模型?附SparkAi创作系统搭建部署教程

一、文章前言 SparkAi创作系统是基于ChatGPT进行开发的Ai问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧。已支持GPT…

Python+Django+Html网页版人脸识别考勤打卡系统

程序示例精选 PythonDjangoHtml人脸识别考勤打卡系统 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《PythonDjangoHtml网页版人脸识别考勤打卡系统》编写代码&#xff0c;代码整洁&#xf…

实用工具系列-ADB使用方式

作者持续关注 WPS二次开发专题系列&#xff0c;持续为大家带来更多有价值的WPS开发技术细节&#xff0c;如果能够帮助到您&#xff0c;请帮忙来个一键三连&#xff0c;更多问题请联系我&#xff08;WPS二次开发QQ群:250325397&#xff09;&#xff0c;摸鱼吹牛嗨起来&#xff0…

安装 windows 版 dash —— zeal

1、下载安装 下载地址&#xff1a;Download Zeal 选择 Protable 版 直接使用 zeal 下载文档比较慢甚至失败&#xff0c;可以设置代理&#xff0c;也可以使用下面两种方式。 2、手动下载 docset 文档后导入 这种方法不能够选择文档的版本 &#xff08;1&#xff09;在 http://…

前端和后端解决跨域问题的方法

目前很多java web开发都是采用前后端分离框架进行开发&#xff0c;相比于单体项目容易产生跨域问题。 一、跨域问题CORS 1.什么是跨域问题&#xff1f; 后端接收到请求并返回结果了&#xff0c;浏览器把这个响应拦截了。 2.跨域问题是怎么产生的&#xff1f; 浏览器基于同源…

将性能测试数据转换为图表格式

个人笔记&#xff08;整理不易&#xff0c;有帮助&#xff0c;收藏点赞评论&#xff0c;爱你们&#xff01;&#xff01;&#xff01;你的支持是我写作的动力&#xff09; 笔记目录&#xff1a;学习笔记目录_pytest和unittest、airtest_weixin_42717928的博客-CSDN博客 个人随笔…

IDE Eval Reset —— idea 重置试用期插件安装

idea 重置试用期插件安装 一、在线安装&#xff1a; 1、打开IntelliJ IDEA 2、file—> setting —> plugins 添加三方插件库 点击后&#xff0c;跳出弹框点击号&#xff0c;添加图中的网址 https://plugins.zhile.io3、搜索 IDE Eval Reset &#xff0c;安装插件 4…

PostgreSQL入门到实战-第十八弹

PostgreSQL入门到实战 PostgreSQL中表连接操作(二)官网地址PostgreSQL概述PostgreSQL中表别名命令理论PostgreSQL中表别名命令实战更新计划 PostgreSQL中表连接操作(二) 了解PostgreSQL表别名及其实际应用程序。 官网地址 声明: 由于操作系统, 版本更新等原因, 文章所列内容…

[2024最新]MySQL-mysql 8.0.11安装教程

网上的教程有很多&#xff0c;基本上大同小异。但是安装软件有时就可能因为一个细节安装失败。我也是综合了很多个教程才安装好的&#xff0c;所以本教程可能也不是普遍适合的。 安装环境&#xff1a;win 10 1、下载zip安装包&#xff1a; MySQL8.0 For Windows zip包下载地…

计算机网络——NAT技术

目录 前言 前篇 引言 SNAT&#xff08;Source Network Address Translation&#xff09;源网络地址转换 SNAT流程 确定性标记 DNAT&#xff08;Destination Network Address Translation&#xff0c;目标网络地址转换&#xff09; NAT技术重要性 前言 本博客是博主用于…

qemu源码解析(基于qemu9.0.0)

简介 QEMU是一个开源的虚拟化软件&#xff0c;它能够模拟各种硬件设备&#xff0c;支持多种虚拟化技术&#xff0c;如TCG、Xen、KVM等 TCG 是 QEMU 中的一个组件&#xff0c;它可以将高级语言编写的代码&#xff08;例如 C 代码&#xff09;转换为可在虚拟机中执行的低级代码…

ELK 日志分析系统(一)

一、概念 二、详解 2.1 Elasticsearch 核心概念 2.1.1 接近实时(NRT) 2.1.2 cluster集群 2.1.3 Node节点 2.1.4 index索引 2.1.5 类型&#xff08;type&#xff09; 2.1.6 文档&#xff08;document) 2.1.7 分片和副本(shards & replicas) 2.2 Logstash主要组件 …

网络安全JavaSE第六天

7. 数组 7.3.5 数组排序 7.3.5.1 冒泡排序 冒泡排序的思路&#xff1a;相邻两个元素进行比较&#xff0c;如果前面元素比后面元素大就交换位置&#xff0c;每一趟执行完后&#xff0c; 就会得到最大的数。 排序过程分析&#xff1a; package com.openlab; /** * 冒泡排序 */…

Java后端基础知识(String类型)

String类的创建方式 String的特点 1.引用数据类型 2.是final类&#xff0c;一旦创建内容不可修改 3.String类对象相等的判断用equals&#xff08;&#xff09;方法完成&#xff0c;是判断地址数值 String的创建方式 1.直接创建 String str"hello";注意&#xff…

LRU缓存结构【C语言】

#include <stdio.h> #include <stdlib.h>//双链表节点结构 typedef struct Node {int key;int value;struct Node* pre;struct Node* next; } Node;//LRU结构 typedef struct {int capacity;struct Node* head;struct Node* tail;struct Node** cache; }LRUCache;…

sonar搭建(linux系统)

前景 静态代码扫描是CI/CD中重要的一环&#xff0c;可以在代码提交到代码仓库之后&#xff0c;在CI/CD流程中加入代码扫描步骤&#xff0c;从而及时地对代码进行质量的检查。这可以有效地降低后期维护成本&#xff0c;优化产品质量&#xff0c;提高产品交付速度。同时&#xf…

世界需要和平--中介者模式

1.1 世界需要和平 "你想呀&#xff0c;国与国之间的关系&#xff0c;就类似于不同的对象与对象之间的关系&#xff0c;这就要求对象之间需要知道其他所有对象&#xff0c;尽管将一个系统分割成许多对象通常可以增加其可复用性&#xff0c;但是对象间相互连接的激增又会降低…

Capture One 23 Enterprise for Mac中文版 全面的图像处理工具

Capture One 23 Enterprise for Mac中文版一款专业的图像编辑和管理软件&#xff0c;具备强大的功能和工具&#xff0c;适用于摄影师、摄影工作室和专业用户。 软件下载&#xff1a;Capture One 23 Enterprise for Mac中文版下载 该软件为用户提供了全面的图像处理工具&#xf…