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 = 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博客

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大厂Java笔试题之判断一个数是否自守数

题目&#xff1a;自守数是指一个数的平方的尾数等于该数自身的自然数。例如&#xff1a;25^2 625&#xff0c;76^2 5776&#xff0c;9376^2 87909376。 请求出n(包括n)以内的自守数的个数 数据范围&#xff1a; 1≤n≤10000 输入描述&#xff1a; int型整数 输出描述&…

推荐3个yyds的AI开源项目!

在这个数字化飞速发展的时代&#xff0c;有一堆人工智能开源工具&#xff0c;它们正悄悄地改变着我们的生活和工作方式。今天&#xff0c;我就带大家一起来深度了解几款近期大火的人工智能工具&#xff0c;看看它们是怎样为我们的生活带来便利和创新的。 马赛克杀手APISR 首先…

怎样关闭谷歌浏览器自动更新,亲测ok

步骤一 在服务中禁用Google更新 步骤二 Chrome更新是利用Update文件夹里的升级程序来升级的&#xff0c;需要要删除里面的文件&#xff0c;再让Chrome没法在Update文件夹里继续自动生成更新程序。所以还要清空Update文件夹并设置权限&#xff0c;让Chrome没有权限修改这个文件…

分享|为什么说Temu项目是蓝海项目?

在当今日新月异的互联网行业中&#xff0c;Temu项目以其独特的商业模式和前瞻性的市场布局&#xff0c;迅速崛起成为一颗耀眼的新星。它被业内普遍认为是一片尚未被完全开发的蓝海&#xff0c;具有巨大的市场潜力和发展空间。那么&#xff0c;为什么说Temu项目是蓝海项目呢? 首…

爬虫的目的是做什么

通过网站域名获取HTML数据解析数据&#xff0c;获取想要的信息存储爬取的信息如果有必要&#xff0c;移动到另一个网页重复过程 这本书上的代码的网址是 &#xff1a; GitHub - REMitchell/python-scraping: Code samples from the book Web Scraping with Python http://shop.…

Linux sed

文章目录 1. 基本功能2.sed替换ssed配合grep和管道操作符的例子 3.sed中的删除和添加3.1 d删除3.2 a i添加添加多行 4.sed行替换替换包含某字符的行 5.单字符替换 y6. p打印命令打印含有目标字符的行sed中包含多个指令&#xff0c;使用{} 7.sed w 写入文件8.sed r 读取文件9.se…

【数据结构】第三节:单链表

前言 本篇要求掌握的C语言基础知识&#xff1a;指针、结构体 目录 前言 单链表 概念 对比链表和顺序表 创建链表 实现单链表 准备工作 打印链表 创建节点并初始化 尾插 二级指针的调用 尾插代码 头插 尾删 头删 查找&#xff08;返回节点&#xff09; 在指定位…

Blender表面细分的操作

在使用Blender的过程中,刚开始创建的模型,都会比较少面,这样操作起来比较流畅,减少电脑的计算量,当设计快要完成时,就会增加表面细分,这样更加圆滑,看起来更加顺眼。 比如创建一个猴头,它会默认显示如下: 从上图可以看到,有一些表面会比较大,棱角很多。 这时候你…

java:Java类与对象(继承篇)

前言&#xff1a; 为什么要继承&#xff1f; 如&#xff1a;创建两个类&#xff0c;一个猫类&#xff0c;一个狗类&#xff0c;猫与狗都有眼睛&#xff0c;嘴巴&#xff0c;鼻子这些相同的属性&#xff0c;为了避免代码重复我们可以通过创建一个动物类&#xff0c;包含这些重复…

更改android 安装的sdk版本

打开sdk manager 勾选show details 就可以选择了。

CTF之comment

网站的登录框里有提示 账号&#xff1a;zhangwei 密码&#xff1a;zhangwei***&#xff08;后三位要自己猜&#xff09; 用burpsuit抓包爆破发现密码为zhangwei666 进去后就一个留言榜&#xff08;目前没发现怎么用&#xff09; 扫一下网站发现git泄露 1.下载 进入root用户&…

【讲解下常见的分类算法】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

ELK企业级日志分析系统(elasticsearch+logstash+kibana)

目录 一.ELK概述 1.定义 &#xff08;1&#xff09;ElasticSearch &#xff08;2&#xff09;Kiabana &#xff08;3&#xff09;Logstash &#xff08;4&#xff09;Filebeat 2.filebeat结合logstash带来好处 3.为什么要是用ELK&#xff1f; 4.完整日志系统基本特征 …

快速寻找可以构建出网通信隧道的计算机

点击星标&#xff0c;即时接收最新推文 本文选自《内网安全攻防&#xff1a;红队之路》 扫描二维码五折购书 为加强内网的安全防范&#xff0c;安全管理员往往会限制内网计算机访问互联网&#xff0c;当然不同机构的限制策略是不一样的&#xff0c;有的完全阻断了内网计算机访问…

企业中台技术架构解决方案(中台建设指南Word原件2024)

通过中台建设实现企业能力复用&#xff0c;包括能力整合、业务创新、业务和数据闭环、组织模式演进等。 数字能力整合 企业的数字能力一般包括数字化营销、数字化产品、数字化供应链、数字化生产、数字化运营等。企业的数字化能力的充分利用&#xff0c;从而达到可持续发展。数…

面试算法-177-二叉搜索树中第K小的元素

题目 给定一个二叉搜索树的根节点 root &#xff0c;和一个整数 k &#xff0c;请你设计一个算法查找其中第 k 个最小元素&#xff08;从 1 开始计数&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,1,4,null,2], k 1 输出&#xff1a;1 解 class Solution…

Stable Diffusion教程:LoRA模型

LoRA模型是一种微调模型&#xff0c;它不能独立生成图片&#xff0c;常常用作大模型的补充&#xff0c;用来生成某种特定主体或者风格的图片。 下载模型 在模型下载网站&#xff0c;如果模型是LoRA模型&#xff0c;网站会特别标识出来。以 liblib.ai为例&#xff1a; 模型左…

操作系统(第五周 第一二堂总结)

目录 回顾 前景知识 概述 定义 进程和线程的关系 进程和线程的区别 线程优缺点 优点&#xff1a; 缺点&#xff1a; 易混概念 线程实现方式 线程的类型&#xff1a; ​编辑 多线程模型&#xff1a; 线程函数 头文件&#xff1a; 线程创建函数&#xff1a; 线…

【高录用-快速见刊】2024年数字化经济与金融创新国际学术会议(ICDEFI 2024)

会议简介 2024年数字经济与金融创新国际学术会议即将召开。此次会议旨在汇集全球数字经济与金融创新领域的专家学者&#xff0c;共同探讨数字经济的发展趋势以及金融创新的路径。与会者将分享前沿研究成果&#xff0c;讨论数字技术在金融领域的应用与创新&#xff0c;并推动数…

CSS导读 (元素显示模式 上)

&#xff08;大家好&#xff0c;今天我们将继续来学习CSS的相关知识&#xff0c;大家可以在评论区进行互动答疑哦~加油&#xff01;&#x1f495;&#xff09; 目录 三、CSS的元素显示模式 3.1 什么是元素显示模式 3.2 块元素 3.3 行内元素 3.4 行内块元素 3.5 元素…