Go语言实现十大排序算法超细节图片讲解

news2025/3/24 17:46:48

基础排序

冒泡排序

        将序列中的元素进行两两比较,将大的元素移动到序列的末尾。

        平均时间复杂度是O(n^2),最坏时间复杂度是O(n^2),最好时间复杂度是O(n),排序结果具有稳定性,空间复杂度是O(1)。

        这里所说的稳定性是针对相同元素而言的,比如5,5,3进行冒泡排序,第一个5由于并不大于第二个5,所以两个5的位置不会发生交换,如果排序的时候,两个5的位置发生了交换我们就说,这种排序是不稳定的。

func BubleSort(nums []int) {
	n := len(nums)
	finish := false
	for i := 0; i < n; i++ {
		for j := 0; j < n-1-i; j++ {
			if nums[j] > nums[j+1] {
				nums[j], nums[j+1] = nums[j+1], nums[j]
				finish = true
			}
		}
		if !finish {
			break
		}
		finish = false
	}
}

选择排序

        假设序列中的第一个元素为最小值,然后遍历后续的元素找到真正的最小值,每次在剩余元素中选择最小的元素与当前元素进行交换。

        平均时间复杂度是O(n^2),最好时间复杂度是O(n^2),最坏时间复杂度是O(n^2),排序结果不稳定,空间复杂度是O(1)。使用选择排序对5,5,3进行排序,会将第一个5和最后一个3进行交换,这样两个5的前后顺序就变了,所以选择排序是不稳定的。

func ChoiceSort(nums []int) {
	n := len(nums)
	for i := 0; i < n-1; i++ {
		minIndex := i
		for j := i + 1; j < n; j++ {
			if nums[j] < nums[minIndex] {
				minIndex = j
			}
		}
		nums[i], nums[minIndex] = nums[minIndex], nums[i]
	}
}

插入排序

        从第二个元素开始,把前面的元素当作有序的,然后在这些元素中找到小于等于当前元素的元素,在这个元素的后面插入当前元素。

        最坏时间复杂度和平均时间复杂度都是O(n^2),最好时间复杂度是O(n),排序结果稳定,对于趋于有序的数据,具有最高效率,空间复杂度是O(1)。

func InsertSort(nums []int) {
	n := len(nums)
	for i := 1; i < n; i++ {
		num := nums[i]
		j := i - 1
		for ; j >= 0; j-- {
			nums[j+1] = nums[j]
			if nums[j] < num {
				break
			}
		}
		nums[j+1] = num
	}
}

高级排序

希尔排序

        对插入排序的优化,其实就是多路的插入排序,插入排序本身只有一个分组,而希尔排序通过设置分组间隔的方式可以产生多个分组,对这些分组进行插入排序,分组的数据越趋于有序,则整体的数据越趋于有序。

        最坏时间复杂度是O(n^2),最好时间复杂度是O(n),平均时间复杂度是O(n^1.3),排序结果稳定,空间复杂度是O(1)。

func ShellSort(nums []int) {
	n := len(nums)
	for gap := n / 2; gap != 0; gap /= 2 {
		for i := gap; i < n; i++ {
			num := nums[i]
			j := i - gap
			for ; j >= 0; j -= gap {
				nums[j+gap] = nums[j]
				if nums[j] < num {
					break
				}
			}
			nums[j+gap] = num
		}
	}
}

        对比代码可以发现,希尔排序其实就是将插入排序的1换成了分组间隔gap。

快速排序

        选取一个基准数,把小于基准数的元素放到基准数的右边,将大于基准数的元素放到基准数的左边,然后以基准数作为划分,将序列划分为左右两个序列,再次将元素与基准数进行比对和移动,直到整个序列变为有序。
        快排一般使用递归实现,先选取基准数,一般就是序列的最左边的元素,然后从右边开始找第一个比基准数小的元素,令此时遍历到的左边的元素等于该元素,并且左索引向后移动1,再从左边寻找第一个比基准数大的元素,令此时遍历到的右边的元素等于该元素,右索引向前移动1,重复以上操作,直到序列有序。
        快排的最好和平均时间复杂度是O(nlogn),最坏时间复杂度是O(n^2),空间复杂度在O(logn)到O(n)之间,排序结果不稳定,一般来说序列越乱序,快排效率越高,与插入排序相反。

func QuickSort(nums []int, left int, right int) {
	if left >= right {
		return
	}
	num := nums[left]
	i := left
	j := right
	for i < j {
		for i < j && nums[j] >= num {
			j--
		}
		if i < j {
			nums[i] = nums[j]
			i++
		}
		for i < j && nums[i] <= num {
			i++
		}
		if i < j {
			nums[j] = nums[i]
			j--
		}
	}
	nums[i] = num
	QuickSort(nums, left, i-1)
	QuickSort(nums, i+1, right)
}

        实际上快速排序的效率取决于左右序列的划分,也就是基准数的选取,要保证划分的序列可以组成一个平衡的二叉树,这时排序的效率通常是最好的,可以通过三数取中法的方式有效选取较好的基准数,每次较好地划分左右序列,当然也可以使用快排加插入排序结合的方式,提高快排的效率,毕竟快速排序的左右序列都是趋于有序的,适用于插入排序。

归并排序

        先递进(可以用二分递进)到序列的各个元素,然后回归将各个元素的顺序进行编排,并将编排好的左右序列进行合并,最终使得整个序列变得有序。

        归并排序的最好时间复杂度和最坏时间复杂度以及平均时间复杂度都是O(nlogn),因为归并排序无论序列是否有序,它都会进行二分递归将序列分为单个元素,再合并为有序的序列,所以没有最好和最坏时间复杂度的区别,空间复杂度是O(n+logn),也就是O(n)。

func MergeSort(nums []int, left int, right int) {
	if left >= right {
		return
	}
	mid := left + (right-left)/2
	// 递进
	MergeSort(nums, left, mid)
	MergeSort(nums, mid+1, right)
	// 回溯合并
	order_res := make([]int, right-left+1)
	i := left
	j := mid + 1
	index := 0
	// 合并两个已排序的子数组
	for i <= mid && j <= right {
		if nums[i] <= nums[j] {
			order_res[index] = nums[i]
			index++
			i++
		} else {
			order_res[index] = nums[j]
			index++
			j++
		}
	}
	// 如果左边还有元素
	for i <= mid {
		order_res[index] = nums[i]
		index++
		i++
	}
	// 如果右边还有元素
	for j <= right {
		order_res[index] = nums[j]
		index++
		j++
	}
	// 将排序后的结果放回原数组
	for i := 0; i < len(order_res); i++ {
		nums[left+i] = order_res[i]
	}
}

堆排序

        堆排序实际上是基于二叉堆进行的,而二叉堆就是一个数组,只是我们从逻辑的角度将这个给数组看作一颗完全二叉树,这个二叉树就是二叉堆,二叉堆又分为大根堆和小根堆,堆排序如果是基于大根堆实现的就是,从小到大排序,小根堆就是从大到小排序。

        进行堆排序的第一步就是从第一个非叶子节点开始到堆顶元素的每一个节点都进行下沉操作,将本来的二叉堆调整为大根堆,第一个非叶子节点的数组索引可以通过数组末尾元素的索引n得到,(n-1)/2。接着把堆顶元素和末尾元素进行交换,从0号位继续开始进行堆的下沉操作。实际上就是将每一个有孩子的节点所对应的子树都调整为大根堆。

        堆排序的平均时间复杂度和最好最坏时间复杂度都是nlogn,空间复杂度是O(1),排序的结果不稳定。

func siftDown(nums []int, n, i int) {
	for {
		l := 2*i + 1
		r := 2*i + 2
		max := i
		if l < n && nums[l] > nums[max] {
			max = l
		}
		if r < n && nums[r] > nums[max] {
			max = r
		}
		if max == i {
			break
		}
		nums[i], nums[max] = nums[max], nums[i]
		i = max
	}
}
func HeapSort(nums []int) {
	//构建完全二叉树
	for i := len(nums)/2 - 1; i >= 0; i-- {
		siftDown(nums, len(nums), i)
	}
	for i := len(nums) - 1; i > 0; i-- {
		nums[0], nums[i] = nums[i], nums[0]
		siftDown(nums, i, 0)
	}
}

         需要注意的是,尽管快排和归并排序和堆排序,它们的时间复杂度基本相差不大,但是实际使用进行排序的时候,堆排序的效率要比快排和归并排序差的多主要是因为快排和归并排序是顺序遍历数组元素的,我们知道,CPU在执行一段指令或者使用一段数据的时候,它不是一条一条从内存中获取的,而是将一块相邻的内存都加载到自己的空间中,如果是当前要使用的数据或者要执行的指令就放入到CPU寄存器中,如果暂时不需要使用的数据和不执行的指令则放入到缓存中,但是所有的指令和数据都是相邻一块内存中的,所以快排和归并排的机制对于CPU的缓存使用相当友好,可以提高CPU的缓存命中率,快排和归并派使用到的数据大部分都是从缓存中拿取的,而不是从进程的内存中拿取的,自然效率要更高,而堆排序就不一样了,它访问元素是通过上浮和下沉操作,并不是顺序访问元素,因此更多是从内存取数据,因此效率要低的多,而且堆排序在下沉操作中的无效交换次数较多,因为每次进行下沉的时候,都会直接将数组末尾元素也就是堆尾部的元素直接覆盖堆顶部的元素,然后再对堆顶元素进行下沉,本身数组末尾元素在二叉堆里面的属性就比较极端,如果是在大根堆,那这个元素极有可能是最小的,这时候直接放到堆顶,后面又要下沉回来,因此极其浪费时间。

        但是归并排序也有自己独特的优势,那就是它是八大排序算法中唯一的外排序,其他排序都是内排序,只能对内存上的数据进行排序,但是归并排序可以对磁盘里面的数据进行排序,比如我D盘里面一个test.txt中有1024MB的数据序列,但是我的内存只有100MB,这时候只有归并排序的思想可以在使用100MB内存的情况下,完成磁盘上1024MB数据的排序,实际上就是将test.txt中的数据序列分为多个文件存储,比如test1.txt、test2.txt等,这些.txt文件中只有100MB的文件序列,然后分别对它们进行归并排序,最后从这些已经排好序的文件中不断选取最小值,组成一个有序的1024MB的序列,这就是归并排序的先分再合的思想。

桶排序

        按照数据的某种性质将一组数组序列映射分配到数量有限的桶里面,每个桶再分别排序,然后将每个桶里面排序好序的序列重新组合为一个新的有序序列。

        这里说的某种性质,就是例如数据的大小范围、分布情况等,如果我们知道数据的范围在[0,1)之间,我们可以将数据乘以桶的数量来映射到不同的桶中;另外也可以根据数据的分布情况,将数据按照一定的映射规则划分到桶中。

        桶排序在对每个桶中的元素进行排序时,通常依赖于其他的排序算法,比如快排或者插入排序等,因此桶排序的时间复杂度也取决于所使用到的排序算法的时间复杂度,一般来说,如果使用Go标准库自带的sort函数对各个桶里面的元素进行排序,桶排序的最好时间复杂度是O(n),平均时间复杂度也是O(n+k),最坏时间复杂度是O(n^2),空间复杂度是O(n+k),需要k个桶和n个元素的额外空间,排序结果稳定。

func BucketSort(nums []float64) {
	n := len(nums)
	var bucket [][]float64 = make([][]float64, n)
	var index []int = make([]int, n)
	for i := 0; i < len(nums); i++ {
		index[i] = int(float64(n) * nums[i])
		bucket[index[i]] = append(bucket[index[i]], nums[i])
	}
	for i := 0; i < len(nums); i++ {
		sort.Float64s(bucket[i])
	}
	j := 0
	for _, v1 := range bucket {
		for _, v2 := range v1 {
			nums[j] = v2
			j++
		}
	}
}

        需要注意的是,桶排序适用于均匀分布的数据,主要用于特定范围内的浮点数排序,按照上面的映射方式,如果是对整数进行排序,数据产生的映射下标,直接就超出桶的大小了,这时需要转换映射关系。

计数排序

        由桶排序衍生出来的排序,同样是根据数据的某种性质将数据分配到不同的桶中,然后进行排序,但是它是根据原始数据序列中最小元素和最大元素的值开辟桶的大小,那么这个桶实际上是一个一维的数组,并且的索引的范围是从0到原始数据序列中的最大值,将原始数据序列中每一个元素对应在桶里的位置都+1,原始数据序列中同一个元素出现几次,对应桶中这个元素索引的值就是几,比如我原始序列是{1,2,5,9,1,3,8,10},显然桶的大小是10,其中桶的第0号索引对应的值显然是0,因为0没有出现在原始数据序列中,第1号索引对应的值自然就是2,因为1在原始序列中出现了2两次,就是按照这种方式,将原始序列映射到了桶中,然后从桶的0索引开始依次将所有元素取出放入元素序列中,这样就能达到一个排序的效果。

        计数排序从机制就可以看出来,非常的损耗空间,假如我上面那个序列的1改为99,那么就要开辟一个99大小的桶数组出来,计数排序的平均时间复杂度和最好最坏时间复杂度都是O(n+k),空间复杂度是O(k),排序结果稳定。

       假设原始序列如下:

        计数排序过程如下:

func CountingSort(nums []int) {
	maxElement := 0
	for _, value := range nums {
		if maxElement < value {
			maxElement = value
		}
	}
	var bucket []int = make([]int, maxElement+1)
	for _, value := range nums {
		bucket[value]++
	}
	index := 0
	for i := 0; i < maxElement+1; i++ {
		for bucket[i] > 0 {
			nums[index] = i
			index++
			bucket[i]--
		}
	}
}

基数排序

        同样是桶排序衍生出来的排序,首先找到整个原始序列中位数最长的数字,确定桶排序的趟数,第一次将原始序列中的数据按照个位数字,依次放入到0~19的20个桶中进行分类,然后依次从这个0~19个桶中将数组拿出重新拼凑为一个新的序列,再按照十位的数字进行同样的操作,直到完成所有的趟数。

        基数排序的最坏时间复杂度和平均时间复杂度以及最好时间复杂度都是O(n*k),空间复杂度是O(n),排序结果是稳定的。

func RadixSort(nums []int) {
	n := len(nums)
	maxElement := nums[0]
	for i := 1; i < n; i++ {
		abs_num := func(num int) int {
			if num < 0 {
				return -num
			}
			return num
		}(nums[i])
		if abs_num > maxElement {
			maxElement = abs_num
		}
	}
	turns := len(strconv.Itoa(maxElement))
	mod := 10
	dev := 1
	for i := 0; i < turns; i++ {
		var bucket [][]int = make([][]int, 20)
		for j := 0; j < n; j++ {
			//获取当前元素的第i位数
			index := nums[j]%mod/dev + 10 //+10是为了处理负数
			bucket[index] = append(bucket[index], nums[j])
		}
		dex := 0
		for _, v1bucket := range bucket {
			for _, v2bucket := range v1bucket {
				nums[dex] = v2bucket
				dex++
			}
		}
		mod *= 10
		dev *= 10
	}
}

        需要注意的是,这里选用20个桶,是为了基数排序可以处理负数排序,用20个桶就可以容下-9~9这20个数字,除此之外,从基数排序的位数处理方式来看,就可以很明显看出,基数排序不适合浮点数排序。 

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

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

相关文章

【鸿蒙Next】写入沙箱的日志文件如何查看

demo案例&#xff1a;https://gitee.com/pengyoucongcode/TxtEdit 文章参考&#xff1a;https://blog.csdn.net/qq_42896653/article/details/144782468

网页五子棋——通用模块

目录 项目创建 通用功能模块 错误码 自定义异常类 CommonResult jackson 加密工具 项目创建 使用 idea 创建 SpringBoot 项目&#xff0c;并引入相关依赖&#xff1a; 配置 MyBatis&#xff1a; 编辑 application.yml&#xff1a; spring:datasource: # 数据库连接配…

第6章 6.2使用ASP.NET Core 开发WebAPI ASP.NET Core Web API

6.2.1 Web API项目的搭建 进入VS&#xff0c;【创建新项目】&#xff0c;选择【ASP.NET Core Web API】模板&#xff0c;【下一步】&#xff0c;编辑项目名称及项目位置&#xff0c;【下一步】&#xff0c;选择框架&#xff0c;其他选项默认即可&#xff0c;【创建】。 进入项…

[MFC] 使用控件

介绍如何使用控件&#xff0c;以及如何获取控件中的数值 check Box 添加点击事件&#xff0c;即选中和取消选中触发的事件 第一种方式是按照如下方式第二种方式是直接双击点击进去 void CMFCApplication1Dlg::OnBnClickedCheckSun() {// TODO: 在此添加控件通知处理程序代…

景联文科技:以精准标注赋能AI未来,打造高质量数据基石

在人工智能蓬勃发展的时代&#xff0c;数据已成为驱动技术革新的核心燃料&#xff0c;而高质量的数据标注则是让AI模型从“感知”走向“认知”的关键桥梁。作为深耕数据服务领域的创新者&#xff0c;景联文科技始终以“精准、高效、安全”为核心理念&#xff0c;为全球AI企业提…

2月14(信息差)

&#x1f30d;杭州&#xff1a;全球数贸港核心区建设方案拟出台 争取国家支持杭州在网络游戏管理给予更多权限 &#x1f384;Kimi深夜炸场&#xff1a;满血版多模态o1级推理模型&#xff01;OpenAI外全球首次&#xff01;Jim Fan&#xff1a;同天两款国产o1绝对不是巧合&#x…

web集群(LVS-DR)

LVS是Linux Virtual Server的简称&#xff0c;也就是Linux虚拟服务器, 是一个由章文嵩博士发起的自由软件项 目&#xff0c;它的官方站点是 www.linuxvirtualserver.org。现在LVS已经是 Linux标准内核的一部分&#xff0c;在 Linux2.4内核以前&#xff0c;使用LVS时必须要重新编…

多媒体软件安全与授权新范例,用 CodeMeter 实现安全、高效的软件许可管理

背景概述 Reason Studios 成立于 1994 年&#xff0c;总部位于瑞典斯德哥尔摩&#xff0c;是全球领先的音乐制作软件开发商。凭借创新的软件产品和行业标准技术&#xff0c;如 ReWire 和 REX 文件格式&#xff0c;Reason Studios 为全球专业音乐人和业余爱好者提供了一系列高质…

DeePseek结合PS!批量处理图片的方法教程

​ ​ 今天我们来聊聊如何利用deepseek和Photoshop&#xff08;PS&#xff09;实现图片的批量处理。 传统上&#xff0c;批量修改图片尺寸、分辨率等任务往往需要编写脚本或手动处理&#xff0c;而现在有了AI的辅助&#xff0c;我们可以轻松生成PS脚本&#xff0c;实现自动化处…

2.14寒假作业

web&#xff1a;[SWPUCTF 2021 新生赛]PseudoProtocols 打开环境给了提示要我们找 hint.php url是给了后缀的&#xff0c;不单纯是地址&#xff0c;直接用为协议看一下目标文件&#xff0c;得到base64加密的文字 解密&#xff0c;提示我们访问一个文件 跟着思路走访问文件之后…

【鱼眼镜头12】Scaramuzza的鱼眼相机模型实操,不依赖于具体的相机几何结构,直接从图像数据出发,因此更具灵活性。

文章目录 Scaramuzza相机模型标定效果2、原理和代码代码1、 2D映射到3D&#xff0c;函数输入为2D点坐标OCAM参数代码功能详解2、3D --> 2D 3、总结Scaramuzza 模型的核心思想Scaramuzza 模型的核心思想与 Kannala-Brandt 模型的对比Scaramuzza 模型的独特之处Scaramuzza 的意…

(Windows | Linux)ssh访问服务器报错:no matching key exchange method found

问题现象 ssh user1192.168.1X.XX Unable to negotiate with 192.168.1X.XX port 22: no matching key exchange method found. Their offer: gss-group1-sha1-toWM5Slw5Ew8Mqkayal2g,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1,diffie-hellman-group1-…

达梦分布式集群DPC_架构详解_yxy

达梦分布式集群DPC_架构详解 1 DPC核心架构介绍1.1 架构图1.2 DPC核心架构组件 2 多副本2.1 多副本架构图2.2 多副本示例2.3 RAFT组概念2.4 表空间与RAFT组的关系 1 DPC核心架构介绍 1.1 架构图 1.2 DPC核心架构组件 DMDPC 架构由三部分组成 SP&#xff08;SQL Processor&…

51单片机独立按键的扩展应用

提示&#xff1a; 按键S7和S6为选择键&#xff0c;确定控制键控制那组LED指示灯。按键S5和S4为控制键&#xff0c;按键该键点亮指定的LED指示灯&#xff0c;松开后熄灭。按下S7点亮L1指示灯&#xff0c;L1点亮后&#xff0c;S6不响应操作&#xff0c;S5控制L3&#xff0c;S4控…

Golang GORM系列:GORM事务及错误处理

在数据库管理领域&#xff0c;确保数据完整性至关重要。GORM是健壮的Go对象关系映射库&#xff0c;它为开发人员提供了维护数据一致性和优雅地处理错误的基本工具。本文是掌握GORM事务和错误处理的全面指南。我们将深入研究如何使用事务来保证原子性&#xff0c;并探索有效处理…

如何实现对 ELK 各组件的监控?试试 Metricbea

上一章基于 Filebeat 的日志收集使用Filebeat收集文件中的日志&#xff0c;而Metricbeat则是收集服务器存活性监测和系统指标的指标。 1. Filebeat和Metricbeat的区别 特性FilebeatHeartbeat作用收集和转发日志监测服务可用性数据来源服务器上的日志文件远程主机、API、服务主…

【微服务学习二】nacos服务发现与负载均衡

nacos服务发现 想要开启服务发现&#xff0c;需要在main函数上添加 EnableDiscoveryClient 注解 然后我们编写一个controller类来查询nacos中注册的所有微服务以及对应的ip端口号 Controller public class DiscoveryController {AutowiredDiscoveryClient discoveryClient;//…

深入剖析推理模型:从DeepSeek R1看LLM推理能力构建与优化

著名 AI 研究者和博主 Sebastian Raschka 又更新博客了。原文地址&#xff1a;https://sebastianraschka.com/blog/2025/understanding-reasoning-llms.html。这一次&#xff0c;他将立足于 DeepSeek 技术报告&#xff0c;介绍用于构建推理模型的四种主要方法&#xff0c;也就是…

Kafka分区管理大师指南:扩容、均衡、迁移与限流全解析

#作者&#xff1a;孙德新 文章目录 分区分配操作(kafka-reassign-partitions.sh)1.1 分区扩容、数据均衡、迁移(kafka-reassign-partitions.sh)1.2、修改topic分区partition的副本数&#xff08;扩缩容副本&#xff09;1.3、Partition Reassign场景限流1.4、节点内副本移动到不…

AIoT时代来临,物联网技术如何颠覆未来生活?

在这个万物互联的时代&#xff0c;“物联网”&#xff08;IoT&#xff09;正以前所未有的速度改变我们的生活&#xff0c;而“AIoT”则是在物联网基础上融入人工智能技术&#xff0c;赋予设备更高的智能和自主决策能力。随着5G、边缘计算和云技术的不断发展&#xff0c;物联网正…