排序算法:快速排序,golang实现

news2025/1/10 20:37:32

目录

前言

快速排序

代码示例

1. 算法包

2. 快速排序代码

3. 模拟程序

4. 运行程序

5. 从大到小排序

快速排序的思想

快速排序的实现逻辑

1. 选择基准值 (Pivot)

2. 分区操作 (Partition)

3. 递归排序

循环次数测试

假如 10 条数据进行排序

假如 20 条数据进行排序

假如 30 条数据进行排序

假设 5000 条数据,对比 冒泡、选择、插入、堆、归并

快速排序的适用场景

1. 大数据集

2. 随机数据

3. 缓存友好的


前言

在实际场景中,选择合适的排序算法对于提高程序的效率和性能至关重要,本节课主要讲解"快速排序"的适用场景及代码实现。

快速排序

快速排序(Quick Sort) 是一种非常高效的排序算法,采用分治法的策略来把一个序列分为较小和较大的两个子序列,然后递归地排序两个子序列。其基本思想是:选择一个基准值(pivot),通过一趟排序将待排序的数据分割成独立的两部分,其中一部分的所有数据都比另一部分的所有数据要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以达到整个数据变成有序序列。

代码示例

下面我们使用Go语言实现一个快速排序

1. 算法包

创建一个 pkg/algorithm.go

touch pkg/algorithm.go

(如果看过上节课的插入排序,则已存在该文件,我们就不需要再创建了)

2. 快速排序代码

打开 pkg/algorithm.go 文件,代码如下

从小到大 排序

package pkg

// BubbleSort 冒泡排序
...

// SelectionSort 选择排序
...

// InsertionSort 插入排序
...

// QuickSort 快速排序
func QuickSort(arr []int, low, high int) {
	if low < high {
		// partitionIndex 是分区操作后基准的索引
		partitionIndex := partition(arr, low, high)

		// 分别对基准左侧和右侧的子数组进行快速排序
		QuickSort(arr, low, partitionIndex-1)
		QuickSort(arr, partitionIndex+1, high)
	}
}

// partition 分区操作
func partition(arr []int, low, high int) int {
	pivot := arr[high] // 选择最后一个元素作为基数
	i := low - 1

	for j := low; j < high; j++ {
		// 如果当前元素小于或等于基数
		if arr[j] <= pivot {
			i++
			// 交换 arr[i] 和 arr[j]
			arr[i], arr[j] = arr[j], arr[i]
		}
	}
	// 交换 arr[i+1] 和 arr[high] (基准值)
	arr[i+1], arr[high] = arr[high], arr[i+1]
	return i + 1
}

3. 模拟程序

打开 main.go 文件,代码如下:

package main

import (
	"demo/pkg"
	"fmt"
)

func main() {
	// 定义一个切片,这里我们模拟 10 个元素
	arr := []int{51, 224, 67, 322, 825, 103, 50, 965, 789, 601}
	fmt.Println("Original data:", arr) // 先打印原始数据
	pkg.QuickSort(arr, 0, len(arr)-1)  // 调用快速排序
	fmt.Println("New data:  ", arr)    // 后打印排序后的数据
}

4. 运行程序

go run main.go

能发现, Original data 后打印的数据,正是我们代码中定义的切片数据,顺序也是一致的。

New Data 后打印的数据,则是经过快速排序后的数据,是从小到大的。

5. 从大到小排序

如果需要 从大到小 排序也是可以的,在代码里,将两个元素比较的 小于等于符号 改成 大于等于符号 即可。

修改 pkg/algorithm.go 文件:

package pkg

// BubbleSort 冒泡排序
...

// SelectionSort 选择排序
...

// InsertionSort 插入排序
...

// QuickSort 快速排序
func QuickSort(arr []int, low, high int) {
	if low < high {
		// partitionIndex 是分区操作后基准的索引
		partitionIndex := partition(arr, low, high)

		// 分别对基准左侧和右侧的子数组进行快速排序
		QuickSort(arr, low, partitionIndex-1)
		QuickSort(arr, partitionIndex+1, high)
	}
}

// partition 分区操作
func partition(arr []int, low, high int) int {
	pivot := arr[high] // 选择最后一个元素作为基数
	i := low - 1

	for j := low; j < high; j++ {
		// 如果当前元素大于或等于基数
		if arr[j] >= pivot {
			i++
			// 交换 arr[i] 和 arr[j]
			arr[i], arr[j] = arr[j], arr[i]
		}
	}
	// 交换 arr[i+1] 和 arr[high] (基准值)
	arr[i+1], arr[high] = arr[high], arr[i+1]
	return i + 1
}

只需要一丁点的代码即可

从 package pkg 算第一行,上面示例中在第三十一行代码中,我们将 "<=" 改成了 ">=" ,这样就变成了 从大到小排序了

快速排序的思想

  • 分而治之:将大问题分解为小问题,然后递归地解决小问题,最后将小问题的解合并成原问题的解
  • 原地排序:快速排序是原地排序算法,它只需要一个很小的栈空间(用于递归)来进行排序,不需要额外的存储空间
  • 不稳定性:快速排序在某些情况下可能不是稳定的排序算法,因为相同元素的相对位置可能会在排序过程中改变

快速排序的实现逻辑

1. 选择基准值 (Pivot)

  • 在快速排序中,首先需要选择一个基准值(pivot)。基准值的选择对排序的效率有很大的影响,但在本文的示例代码中,我们简单地选择了数组的最后一个元素作为基准值

2. 分区操作 (Partition)

  • 分区操作是快速排序的核心。它的目的是将数组重新排列,使得所有比基准值小的元素都移到基准值的左边,所有比基准值大的元素都移到右边。分区操作完成后,基准值就处于其最终排序位置
  • 在 partition 函数中,我们使用两个指针 i 和 j,其中 i 指向小于基准值的最后一个元素的下一个位置(初始化为 low - 1), j 用于遍历数组 (从 low 开始到 high - 1)。当 arr[j] 小于或等于基准值时,我们将其与 arr[i+1] 交换,并将 i 增加 1。这样,所有小于或等于基准值的元素都被交换到了基准值的左边
  • 最后,我们将基准值 (原本在 arr[high] ) 与 arr[i + 1] 交换,此时 i + 1 就是基准值的最终位置,也是分区操作的返回值

3. 递归排序

  • 分区操作完成后,我们得到了基准值的正确位置,并且数组被分成了两部分:一部分是基准值左边的所有元素 (都比基准值小),另一部分是基准值右边的所有元素 (都比基准值大)
  • 然后,我们递归地对这两部分分别进行快速排序。这是通过调用 QuickSort 函数,并传入适当的参数 (基准值左侧和右侧的子数组的范围) 来实现的

循环次数测试

按照上面示例进行测试

假如 10 条数据进行排序

[]int{51, 224, 67, 322, 825, 103, 50, 965, 789, 601}

总计循环了 30

假如 20 条数据进行排序

[]int{997, 387, 461, 530, 979, 502, 36, 459, 99, 60, 454, 37, 182, 273, 529, 130, 315, 351, 975, 497}

总计循环了 83 

假如 30 条数据进行排序

[]int{755, 247, 642, 652, 38, 587, 387, 284, 476, 924, 339, 830, 614, 534, 832, 450, 8, 641, 768, 788, 472, 750, 169, 479, 386, 124, 868, 259, 550, 613}

总计循环了 138 

上面我们说到,"基准值的选择对排序的效率有很大的影响",我们修改一条,依旧使用 30 条数据,我们将其最后一位数据 613,改成其他数 120 (这个值可随便改,这里示例 120)

通过调试,循环了 151

如果将 120 改成 700

通过调试,循环了 131

假设 5000 条数据,对比 冒泡、选择、插入、堆、归并

  • 冒泡排序:循环次数 12,502,499 次
  • 选择排序:循环次数 12,502,499 次
  • 插入排序:循环次数 6,323,958 次
  • 快速排序:循环次数 74,236 次
  • 堆排序:循环次数 59,589 次
  • 归并排序:循环次数 60,288 次

快速排序的适用场景

快速排序在多种情况下都是非常高效的,特别是以下几种情况:

1. 大数据集

对于大数据集,快速排序通常比简单的排序算法 (如 冒泡排序、插入排序) 更快

2. 随机数据

当输入数组中的数据是随机分布的时,快速排序的平均时间复杂度是 O(n log n),这是非常高效的

3. 缓存友好的

快速排序通过递归地在数组的不同部分上工作,倾向于产生良好的缓存局部性,特别是在处理大数据集时

然后,快速排序在某些情况下可能不是最佳选择,例如:

  • 小数据集:对于非常小的数据集,快速排序的递归开销可能使得它不如简单的排序算法 (如 插入排序) 快
  • 几乎已经排序的数据:在这种情况下,快速排序的性能可能退化为 O(n^2),因为它依赖于分区操作来减少问题的规模。如果分区操作不能有效地减少数组的大小 (例如,基准值总是最大或最小的元素),则会导致性能下降。在这种情况下,可以考虑使用归并排序或堆排序等算法
  • 不稳定的排序需求:虽然快速排序在大多数情况下是稳定的 (如果实现得当),但在某些特定实现中可能不是。如果稳定性是排序算法的一个关键要求,可能需要考虑其他算法

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

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

相关文章

从入门到自动化:一篇文章掌握Python的80%

Python作为一种高级编程语言&#xff0c;以其简洁明了的语法和强大的功能性&#xff0c;在全球编程社区内享有极高的声誉。本文将带领你从Python的基础语法入手&#xff0c;介绍其常用库的应用&#xff0c;以及如何将Python用于数据分析、网络爬虫和简单的自动化任务&#xff0…

模板(c++)part2

目录 1.非类型模板参数 2.特化 2.1函数模板特化 2.2类模板特化 2.2.1全特化 2.2.2偏特化 3.模板分离编译 1.非类型模板参数 注意&#xff0c;假如 #define N 10 template<class T> class A { private:T a[N]; }; 这样的一个类模板&#xff0c;a数组的大小是定死的 …

canvas绘制表格

canvas绘制表格 最近在为公司产品做技术预研&#xff0c;经理让用canvas做一个表格&#xff0c;于是就有了这篇博客。 我们的数据是后端通过MQTT推送过来的 我在代码中也直接使用了 具体MQTT的实现代码&#xff0c;可见博客 在vue使用MQTT 在这里为了方便实用我直接封装成组件…

【中项第三版】系统集成项目管理工程师 | 第 11 章 规划过程组⑥ | 11.15 - 11.17

前言 第11章对应的内容选择题和案例分析都会进行考查&#xff0c;这一章节属于10大管理的内容&#xff0c;学习要以教材为准。本章上午题分值预计在15分。 目录 11.15 规划资源管理 11.15.1 主要输入 11.15.2 主要工具与技术 11.15.3 主要输出 11.16 估算活动资源 11.1…

安装jdk和tomcat

安装nodejs 1.安装nodejs&#xff0c;这是一个jdk一样的软件运行环境 yum -y list installed|grep epel yum -y install nodejs node -v 2.下载对应的nodejs软件npm yum -y install npm npm -v npm set config .....淘宝镜像 3.安装vue/cli command line interface 命令行接…

轻松搞定 Nginx 在 CentOS 和 Ubuntu 上的安装与配置

注&#xff1a;这是对我以前博客进行优化后再次发布的&#xff0c;博客中的截图为以前的。原博客已删除。 如何安装nginx nginx是一款开源、高性能的Web和反向代理服务器&#xff0c;支持HTTP、HTTPS、SMTP、POP3和IMAP协议。由于其轻量级、资源占用少和强大的并发能力&#…

时空预测又爆火了!新SOTA实现零样本精准预测

时空预测又有新突破啦&#xff01;港大、华南理工等提出了时空大模型UrbanGPT&#xff0c;在性能上猛超现有SOTA&#xff0c;实现零样本即可时空预测&#xff01; 另外还有清华的首个通用城市时空预测模型UniST、能即插即用快速适配的时空提示调整机制FlashST...这些效果非常ni…

探索计算器存储器的奥秘:数字记忆的科学

在日常生活中&#xff0c;我们经常使用计算器来执行各种数学运算。但你是否曾想过&#xff0c;当按下每个按键时&#xff0c;计算器是如何记住数字和运算符的&#xff1f;本文将深入探讨计算器存储器的工作原理&#xff0c;揭示其背后的科学原理。 引言&#xff1a;数字世界的…

家庭出游新风尚!格瑞维亚改装大赛创意实用并存

在创新浪潮翻涌的当下&#xff0c;汽车已蜕变为个性化生活的璀璨舞台&#xff0c;格瑞维亚改装共创大赛便是这一变革的推动者。这场大赛&#xff0c;不仅汇聚了400余支创意团队的心血结晶&#xff0c;更将汽车改装的魅力推向了新的高度。它不仅仅是对机械与美学的重塑&#xff…

STM32——EXIT外部中断

一、中断系统 以上就是中断的概念&#xff0c;简单理解就是&#xff1a; 当程序运行过程中&#xff0c;如果有中断源向CPU打报告&#xff0c;CPU就会暂停手下的事情去处理中断源提交的事情&#xff0c;然后处理完了在返回到CPU原来的位置继续处理手上的事情。如果同时有多个中…

浏览器指纹技术:如何更改浏览器指纹?

“指纹信息”是一个人独有的身份象征&#xff0c;而“浏览器指纹”&#xff0c;就是网站和在线平台使用浏览器指纹来收集有关您的浏览器、设备和网络的详细信息&#xff0c;它可以说是你上网的身份象征&#xff0c;可让网站跟踪您的在线行为。 下面我们简单科普浏览器指纹的工…

tomato 靶场

1.主机发现 扫描ip及端口 2.端口扫描 nmap192.168.233.131 有三个开放的端口nmap -sC -sV -O 192.168.233.131 -sC常见漏洞脚本扫描 -sV开放端口服务/版本号 -O操作系统探测 3.目录扫描 DIRECTORY: http://192.168.233.131/antibot_image/ http://192.168.233.131/index.h…

单调队列与单调栈<1>——单调队列

单调队列&#xff0c;即队列里的元素单调递增或递减。一般用于求区间内的最值问题。 模板题:P1886 暴力的话很简单&#xff0c;搞定。但是对于来说肯定TLE。所以我们要用单调队列来解决这道题。因为单调队列中元素大小单调递增或递减&#xff0c;所以&#xff0c;队首必定是最…

TwinCAT3 新建项目教程

文章目录 打开TwinCAT 新建项目&#xff08;通过TcXaeShell&#xff09; 新建项目&#xff08;通过VS 2019&#xff09;

案例精选 | 聚铭网络助力石家庄市栾城区中医院防护体系焕新升级

石家庄市栾城区中医院&#xff0c;坐落于栾城这块历史悠久的热土上&#xff0c;占地广阔&#xff0c;达4200平方米&#xff0c;作为一家享有盛誉的二级甲等综合性中医医疗机构&#xff0c;它不仅是传统医学与现代科技融合的典范&#xff0c;更是区域医疗卫生服务的重要支柱。 …

【JavaScript】详解数组方法 fill()

文章目录 一、fill()方法简介二、fill()方法的基本用法三、fill()方法的进阶用法四、实际应用案例五、注意事项六、总结 在JavaScript中&#xff0c;fill()方法是数组操作中一个非常有用的方法。它可以快速地用特定值填充数组的全部或部分内容。理解fill()方法的工作机制和使用…

深度对话安天王小丰:我们应该从微软“蓝屏”事件学到什么?

ITValue 国内厂商和CrowdStrike的差距&#xff0c;相比于国外厂商和CrowdStrike的差距更大。 作者&#xff5c;张帅 编辑&#xff5c;盖虹达 首发&#xff5c;钛媒体APP ITValue 我们赖以生存的数字世界&#xff0c;可能比想象得还要脆弱。 近日&#xff0c;全球范围内的微软Wi…

基于Gitlab CI+Argo CD的Gitops实践

项目简介 项目说明 本项目构建了一个基于GitOps理念的完整CI/CD管道&#xff0c;旨在实现软件开发与运维的高度自动化和一致性。通过GitLab、GitLab Runner&#xff08;部署于Kubernetes&#xff09;、Maven、Java、SonarQube、Harbor以及Argo CD等工具的紧密协作&#xff0c…

二叉树的存储

二叉树的存储 满二叉树或者完全二叉树可以采用顺序存储&#xff0c;普通二叉树一般采用链式存储 节点的结构体原型 typedef int DataType typedef struct node { DataType data&#xff1b; struct node *L&#xff1b; struct node *R&#xff1b; }twotree&#xff…