当前位置: 首页 > news >正文

排序算法:归并排序,golang实现

目录

前言

归并排序

代码示例

1. 算法包

2. 归并排序代码

3. 模拟程序

4. 运行程序

5. 从大到小排序

归并排序主要操作

1. 合并

2. 分割(Divide)与递归排序(Conquer)

总体思想

循环次数测试

假如 10 条数据进行排序

假如 20 条数据进行排序

假如 30 条数据进行排序

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

归并排序的适用场景

1. 大数据集

2. 链表排序

3. 外部排序

4. 稳定性需求


前言

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

归并排序

归并排序(Merge Sort)是一种分而治之的排序算法。它将一个大列表分成两个小列表,分别对这两个小列表进行排序,然后将排序好的小列表合并成一个最终的排序列表。归并排序的关键在于合并(Merge)过程,它确保了在合并的过程中,两个已排序的序列被合并成一个新的、有序的序列。

代码示例

下面我们使用Go语言实现一个归并排序

1. 算法包

创建一个 pkg/algorithm.go

touch pkg/algorithm.go

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

2. 归并排序代码

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

从小到大 排序

package pkg// BubbleSort 冒泡排序
...// SelectionSort 选择排序
...// InsertionSort 插入排序
...// QuickSort 快速排序
...// partition 分区操作
...// HeapSort 堆排序
...// heapify 将以 i 为根的子树调整为最大堆
...// MergeSort 归并排序
func MergeSort(arr []int) []int {if len(arr) <= 1 {return arr}// 找到中点,分割数组mid := len(arr) / 2left := MergeSort(arr[:mid])right := MergeSort(arr[mid:])// 合并两个已排序的切片return merge(left, right)
}// merge 函数用于合并两个已排序的切片
func merge(left, right []int) []int {var result []inti, j := 0, 0for i < len(left) && j < len(right) {if left[i] < right[j] {result = append(result, left[i])i++} else {result = append(result, right[j])j++}}// 如果左侧有剩余,则追加到结果切片result = append(result, left[i:]...)// 如果右侧有剩余,则追加到结果切片result = append(result, right[j:]...)return result
}

3. 模拟程序

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

package mainimport ("demo/pkg""fmt"
)func main() {// 定义一个切片,这里我们模拟 10 个元素arr := []int{98081, 27887, 31847, 84059, 2081, 41318, 54425, 22540, 40456, 3300}fmt.Println("arr 的长度:", len(arr))fmt.Println("Original data:", arr) // 先打印原始数据newArr := pkg.MergeSort(arr)       // 调用归并排序fmt.Println("New data:  ", newArr) // 后打印排序后的数据
}

4. 运行程序

go run main.go

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

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

5. 从大到小排序

如果需要 从大到小 排序也是可以的,在代码里,需要将两个 if 判断比较的 符号 进行修改。

修改 pkg/algorithm.go 文件:

package pkg// BubbleSort 冒泡排序
...// SelectionSort 选择排序
...// InsertionSort 插入排序
...// QuickSort 快速排序
...// partition 分区操作
...// HeapSort 堆排序
...// heapify 将以 i 为根的子树调整为最大堆
...// MergeSort 归并排序
func MergeSort(arr []int) []int {if len(arr) <= 1 {return arr}// 找到中点,分割数组mid := len(arr) / 2left := MergeSort(arr[:mid])right := MergeSort(arr[mid:])// 合并两个已排序的切片return merge(left, right)
}// merge 函数用于合并两个已排序的切片
func merge(left, right []int) []int {var result []inti, j := 0, 0for i < len(left) && j < len(right) {if left[i] > right[j] {result = append(result, left[i])i++} else {result = append(result, right[j])j++}}// 如果左侧有剩余,则追加到结果切片result = append(result, left[i:]...)// 如果右侧有剩余,则追加到结果切片result = append(result, right[j:]...)return result
}

只需要一丁点的代码即可

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

归并排序主要操作

主要操作包括 分割合并


1. 合并

合并操作由 merge 函数实现,它接收两个已排序的切片 left 和 right,并返回一个新的、包含两个切片所有元素且已排序的切片。

  • 初始化:首先,创建一个空的切片 result 用于存储合并后的结果。同时,使用两个索引 i 和 j 分别指向 left 和 right 的起始位置
  • 比较与合并:然后,使用一个循环,比较 left[i] 和 right[j] 的大小。将较小的元素追加到 result 中,并移动相应的索引。这个过程一直持续到任一切片中的所有元素都被添加到 result 中
  • 追加剩余元素:如果 left 和 right 中还有剩余的元素(即某个切片的索引没有遍历完),则直接将剩余的元素追加到 result 的末尾。这是因为在循环结束时,剩余的元素一定是已排序的(它们来自原始的已排序切片)

2. 分割(Divide)与递归排序(Conquer)

分割与递归排序操作由 mergeSort 函数实现。

  • 基本情况:如果输入的切片 arr 的长度小于或等于 1,则不需要排序,直接返回该切片。因为单个元素或空切片都可以被认为是已排序的
  • 分割:找到切片的中点 mid,将切片分为两部分:arr[:mid] 和 arr[mid:]
  • 递归排序:对着两部分分别调用 mergeSort 函数进行递归排序。这会将问题分解成更小的子问题,直到子问题小到满足基本情况
  • 合并:最后,使用 merge 函数将这两个递归排序后的切片合并成一个有序的切片,并返回该切片

总体思想

归并排序通过递归地将数组分解成越来越小的半子表,对半子表排序,然后再将排好序的半子表合并成有序的表来工作。这个过程需要额外的存储空间来存放合并后的数组,因此其空间复杂度为 O(n)。然而,归并排序的时间复杂度是稳定的 O(n log n),并且由于其分治特性,它在实际应用中非常有效,尤其是在处理大数据集时。

循环次数测试

参照上面示例进行测试(因考虑到每次手动输入 10 条、20 条、30 条数据太繁琐,所以我写了一个函数,帮助我自动生成 0到100000 的随机整数)

假如 10 条数据进行排序

总计循环了 32 

假如 20 条数据进行排序

总计循环了 84 

假如 30 条数据进行排序

总计循环了 137 

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

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

归并排序的适用场景

归并排序在以下场景表现良好

1. 大数据集

对于非常大的数据集,归并排序通常比快速排序或插入排序更有效,因为归并排序的时间复杂度是 O(n log n),并且它的性能相对稳定,不会因数据集的不同而大幅度变化

2. 链表排序

由于归并排序在合并过程中不需要额外的空间(除了递归栈),所以在链表排序时非常高效。链表数据结构的特性使得分割和合并操作相对简单

3. 外部排序

当数据集太大,无法全部加载到内存时,可以使用归并排序的外部版本。在这个版本中,数据被分割成多个块,每块单独排序后存储在磁盘上,然后通过归并操作将它们合并成一个有序的文件

4. 稳定性需求

归并排序是稳定的排序算法,这意味着相等的元素在排序后仍然保持原来的顺序。这在需要保持元素原始顺序的某些应用中非常有用

尽管归并排序在很多场景下都很有用,但它也有缺点,主要是需要额外的空间 O(n) 来存储临时数组。这在内存受限的情况下可能是一个问题。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • C语言自定义类型联合体与枚举超详解
  • Python中的“@”,有什么用?
  • 【Tessent】【Command】set_design_level Design Level
  • 【系统架构设计师】二十四、安全架构设计理论与实践⑤
  • 双算法https证书获取指南
  • win10自带dll修复丢失的几种方法,快速修复错误dll文件的方式
  • c++初阶-----STL---list
  • 人力资源杂志人力资源杂志社人力资源编辑部2024年第13期目录
  • 白骑士的PyCharm教学高级篇 3.1 性能分析与优化
  • SQL各种注入详解加案例--持续更新
  • 【数据结构】mapset详解
  • 结构开发笔记(一):外壳IP防水等级与IP防水铝壳体初步选型
  • 六点建议有效防止晶振老化
  • 武汉流星汇聚:亚马逊助力领航跨境蓝海,品牌影响力跃上新台阶
  • YoloV10 论文翻译(Real-Time End-to-End Object Detection)
  • [iOS]Core Data浅析一 -- 启用Core Data
  • 【跃迁之路】【477天】刻意练习系列236(2018.05.28)
  • CAP理论的例子讲解
  • Elasticsearch 参考指南(升级前重新索引)
  • Javascript基础之Array数组API
  • java架构面试锦集:开源框架+并发+数据结构+大企必备面试题
  • Laravel 中的一个后期静态绑定
  • linux安装openssl、swoole等扩展的具体步骤
  • supervisor 永不挂掉的进程 安装以及使用
  • Theano - 导数
  • 编写高质量JavaScript代码之并发
  • 互联网大裁员:Java程序员失工作,焉知不能进ali?
  • 记录:CentOS7.2配置LNMP环境记录
  • 漂亮刷新控件-iOS
  • 使用common-codec进行md5加密
  • 树莓派 - 使用须知
  • 腾讯优测优分享 | 你是否体验过Android手机插入耳机后仍外放的尴尬?
  •  一套莫尔斯电报听写、翻译系统
  • 移动端唤起键盘时取消position:fixed定位
  • - 语言经验 - 《c++的高性能内存管理库tcmalloc和jemalloc》
  • 原生JS动态加载JS、CSS文件及代码脚本
  • 原生js练习题---第五课
  • Mac 上flink的安装与启动
  • ​Z时代时尚SUV新宠:起亚赛图斯值不值得年轻人买?
  • ### Error querying database. Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException
  • (7) cmake 编译C++程序(二)
  • (poj1.3.2)1791(构造法模拟)
  • (Ruby)Ubuntu12.04安装Rails环境
  • (二)丶RabbitMQ的六大核心
  • (汇总)os模块以及shutil模块对文件的操作
  • (四)JPA - JQPL 实现增删改查
  • (四)事件系统
  • ****Linux下Mysql的安装和配置
  • **登录+JWT+异常处理+拦截器+ThreadLocal-开发思想与代码实现**
  • .【机器学习】隐马尔可夫模型(Hidden Markov Model,HMM)
  • .equal()和==的区别 怎样判断字符串为空问题: Illegal invoke-super to void nio.file.AccessDeniedException
  • .htaccess 强制https 单独排除某个目录
  • .Net Remoting常用部署结构
  • .net 中viewstate的原理和使用
  • .Net(C#)自定义WinForm控件之小结篇