归并排序介绍、详解、案例

排序

  • 计数排序介绍、详解、案例
  • 快速排序介绍、详解、案例
  • 归并排序介绍、详解、案例

归并排序也是基于分治法的排序算法,为了排序长度为n的数组,需要先排序长度为n/2的字数组,然后合并这两个排序字数组于是整个数组也就排序完毕。

排序过程

在这里插入图片描述

以数组[4,1,5,6,2,7,8,3]为例子,进行排序说明。如上图所示:

  1. 首先排序合并相邻为1的字数组,得到四个排序长度为2的字数组[1,4,5,6,2,7,3,8]。
  2. 接着排序合并相邻长度为2的字数组,得到两个排序长度为4的字数组[1,4,5,6,2,3,7,8]。
  3. 最后排序合并相邻长度为4的字数组,得到最终的排序结果[1,2,3,4,5,6,7,8]。

归并排序需要创建一个和原数组相同大小的数组,用来保存排序之后的结果,所以其空间复杂度为O(n)。

代码表示

fun mergeIntoSort(array: IntArray): IntArray {
    val length = array.size
    var src = array
    var dst = IntArray(length)
    //合并相邻序列的数组,从1开始
    var sequence = 1
    while (sequence < length) {
        //循环数组 按照序列进行 子数组排序
        var start = 0
        while (start < length) {
            val middle = min(sequence + start, length)
            val end = min(sequence * 2 + start, length)
            var i = start
            var j = middle
            var k = start
            //字数组 具体的排序过程
            while (i < middle || j < end) {
                if (j == end || (i < middle && src[i] < src[j])) {
                    dst[k++] = src[i++]
                } else {
                    dst[k++] = src[j++]
                }
            }
            start += sequence * 2
        }
        //交换数组 主要是防止真正的排序是 dst和src指向同一个数组
        val temp = src
        src = dst
        dst = temp
        //下一个序列
        sequence += sequence
    }
    return src
}

时间复杂度:长度为n的数组每次都被拆分为n/2,所以归并排序的时间复杂度为O(nlogn)。

空间复杂度:需要长度为n的额外辅助数组,所以归并排序的空间复杂度为O(n)。

为什么快速排序比归并排序要快

从上述的时间复杂度我们可以看出,归并时间复杂度固定为O(nlogn),快排的平均时间复杂度才为O(nlogn)。为什么大家常用的是快速排序而不是归并排序呢?

主要是因为归并排序的不是原地排序算法。归并排序的合并过程需要对多个数组进行操作,而快速排序只需要进行简单的元素比较和交换操作。这意味着归并排序的常数复杂度可能比快速排序高。所以大部分情况下快排会更快,且不需要额外的空间。

示例:链表排序

输入一个链表的头节点,请将该链表排序。

例如输入的是:[3 -> 5 -> 1 -> 4 -> 2 -> 6] 输出的则是:[1 -> 2 -> 3 -> 4 -> 5 -> 6]。

如何实现

快排?快速排序算法首先随机生成一个下标,并以该下标对应的值作为中间值进行分区。如果输入的是数组,那么只需要O(1)的时间就能根据下标找到一个数字。但如果输入的是链表,那么需要O(n)的时间才能根据节点的编号找到对应的节点。

归并?归并排序的主要思想是将链表分成两个子链表,在对两个子链表排序后再将它们合并成一个排序的链表。所以可行

如下所示:

fun sortListNodeByMergeInto(node: ListNode?): ListNode? {
    if (node == null || node.next == null) {
        return node
    }

    val middleNode = findMiddleNodeAndSplit(node)
    val node1 = sortListNodeByMergeInto(node)
    val node2 = sortListNodeByMergeInto(middleNode)

    return merge(node1, node2)
}

上述代码中的函数findMiddleNodeAndSplit将链表分成两半并返回后半部分链表的头节点。再将链表分成两半后分别递归地将它们排序,然后调用函数merge将它们合并起来。接下来讨论函数findMiddleNodeAndSplitmerge的实现细节。

findMiddleNodeAndSplit用快慢双指针的思路将链表分成两半。如果慢指针一次走一步,快指针一次走两步,当快指针走到链表尾部时,慢指针只走到链表的中央,这样也就找到了链表后半部分的头节点。

fun findMiddleNodeAndSplit(node: ListNode): ListNode? {
    var slowPointer: ListNode? = node
    var quickPointer: ListNode? = node.next
    while (quickPointer != null && quickPointer.next != null) {
        slowPointer = slowPointer?.next
        quickPointer = quickPointer.next!!.next
    }

    val second = slowPointer?.next
    slowPointer?.next = null

    return second
}

merge也可以用两个指针分别指向两个排序子链表的节点,然后每次选择其中值较小的节点。与合并数组不同的是,不需要另外一个链表来保存合并之后的节点,而只需要调整指针的指向。

fun merge(node1: ListNode?, node2: ListNode?): ListNode? {
    val dummy = ListNode()
    var newNode: ListNode? = dummy
    var leftNode = node1
    var rightNode = node2
    while (leftNode != null && rightNode != null) {
        if (leftNode.value < rightNode.value) {
            newNode!!.next = leftNode
            leftNode = leftNode.next
        } else {
            newNode!!.next = rightNode
            rightNode = rightNode.next
        }
        newNode = newNode.next
    }
    newNode!!.next = leftNode ?: rightNode
    return dummy.next
}

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

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

相关文章

浅谈JVM(五):虚拟机栈帧结构

上一篇&#xff1a; 浅谈JVM(一)&#xff1a;Class文件解析 浅谈JVM(二)&#xff1a;类加载机制 浅谈JVM(三)&#xff1a;类加载器和双亲委派 浅谈JVM(四)&#xff1a;运行时数据区 5.虚拟机栈帧结构 ​ 方法是程序执行的最小单元&#xff0c;每个方法被执行时都会创建一个栈帧…

驱动开发:内核使用IO/DPC定时器

本章将继续探索驱动开发中的基础部分&#xff0c;定时器在内核中同样很常用&#xff0c;在内核中定时器可以使用两种&#xff0c;即IO定时器&#xff0c;以及DPC定时器&#xff0c;一般来说IO定时器是DDK中提供的一种&#xff0c;该定时器可以为间隔为N秒做定时&#xff0c;但如…

内卷?焦虑?35岁?找不到工作?端正态度激励一下正在挣扎的Android程序员

前言 亲爱的各位Android程序员&#xff0c;您们好&#xff1a; 我理解您们的焦虑和困惑&#xff0c;但我想告诉您的是&#xff1a;作为一名Android程序员&#xff0c;您依然是非常有前途和市场需求的职业人才。 首先&#xff0c;您要知道&#xff0c;移动互联网时代的普及率…

【数据结构】时间复杂度和空间复杂度

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习C和算法ing ✈️专栏&#xff1a;【数据结构】 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点…

1662_MIT 6.828 JOS check_page_free_list实现分析以及boot_alloc问题修复

全部学习汇总&#xff1a; GreyZhang/g_unix: some basic learning about unix operating system. (github.com) 继续尝试完善分析JOS的代码中存储管理的部分。 上次看到了这里&#xff0c;本来想先去看看这两个函数实现。但是缺失了调用场景&#xff0c;感觉理解也不一定准确。…

对拍程序 并查集专题 (C++ | 洛谷 | acwing | 蓝桥)

文章目录【蓝桥杯专题】 &#xff08;C | 洛谷 | acwing | 蓝桥&#xff09;1249. 亲戚836. 合并集合837. 连通块中点的数量238. 银河英雄传说 【带权并查集】145. 超市 【并查集 贪心】4793. 危险程度 (连通块并查集 &#xff09;普通oi 读文件对拍程序【蓝桥杯专题】 &#…

树和二叉树相关的练习(选择题)

目录 一、二叉树 二、堆 三、遍历二叉树 一、二叉树 某二叉树共有 399 个结点&#xff0c;其中有 199 个度为 2 的结点&#xff0c;则该二叉树中的叶子结点数为&#xff08; &#xff09;。 A. 不存在这样的二叉树 B. 200 C. 198 D. 199 下列数据结构中&#xff0c;不适合…

C++ Primer Plus 学习笔记(八)——输入、输出和文件

1 流和缓冲区 C程序把输入和输出看作字节流。输入时&#xff0c;程序从输入流中抽取字节&#xff1b;输出时&#xff0c;程序将字节插入到输出流中。 缓冲区是用作中介的内存块&#xff0c;它是将信息从设备传输到程序或从程序传输给设备的临时存储工具&#xff0c;通过使用缓…

HTTP协议:当下最主流的应用层协议之一,你确定不了解一下吗?

一.HTTP协议的含义http是什么&#xff1f;超文本传输协议&#xff08;Hyper Text Transfer Protocol&#xff0c;HTTP&#xff09;是一个简单的请求-响应协议&#xff0c;它通常运行在TCP之上。‘超’可以理解为除了文本之外的图片&#xff0c;音频和视频&#xff0c;和一些其他…

STM32基于HAL工程FREERTOS读取DS18B20数据+串口输出

STM32基于HAL工程FREERTOS读取DS18B20数据串口输出✨申明&#xff1a;本文章仅发表在CSDN网站&#xff0c;任何其他网站&#xff0c;未注明来源&#xff0c;见此内容均为盗链和爬取&#xff0c;请多多尊重和支持原创!&#x1f341;对于文中所提供的相关资源链接将作不定期更换。…

无需公网IP,远程连接SQL Server数据库【内网穿透】

文章目录1.前言2.本地安装和设置SQL Server2.1 SQL Server下载2.2 SQL Server本地连接测试2.3 Cpolar内网穿透的下载和安装2.3 Cpolar内网穿透的注册3.本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置4.公网访问测试5.结语1.前言 数据库的重要性相信大家都有所了解&#xf…

现代前端开发者的自我迷失,你还会前端基础知识吗?

通常来说&#xff0c;我认为情况并不算糟糕&#xff0c;熟练的手可以几乎做到一切。然而&#xff0c;最近我注意到一些事情改变了我对这个行业的看法。似乎在这些无尽的趋势、范式和新奇玩意中&#xff0c;我们忘记了前端开发的支柱&#xff08;意思是忘记了基础知识&#xff0…

【python】GIL全局锁

一、原理&#xff1a; 全局解释器锁&#xff08;Global Interpreter Lock&#xff0c;GIL&#xff09;规定全局范围内任意时候一个进程里只能同时执行一个线程。每一个线程在执行时&#xff0c;都会锁住GIL&#xff0c;以阻止别的线程执行&#xff1b;执行一段时间后&#xff…

OBCP第四章 SQL调优-SQL执行性能监控

(g)v$sql_audit 全局 SQL 审计表 基于虚拟表__all_virtual_sql_audit的视图&#xff0c; 该虚拟表对应的数据存放在一个可配置的内存空间中 由于存放这些记录的内存是有限的&#xff0c;因此到达一定内存使用量&#xff0c;会触发淘汰 可以用来查看每次请求客户端来源&…

【操作系统复习】第3章 处理机调度与死锁 3

死锁&#xff08;Deadlock&#xff09;&#xff1a;指多个进程在运行过程中因争夺资源而造成的一种僵局&#xff0c;当进程处于这种僵持状态时&#xff0c;若无外力作用&#xff0c;这些进程都将永远不能再向前推进。 对资源不加限制地分配可能导致进程间由于竞争资源而相互制约…

JavaSE学习总结(十三)Set集合HashSet集合LinkedHashSet集合TreeSet集合比较器的使用利用Set集合实现去重

JavaSE学习总结&#xff08;十三&#xff09;Set集合/HashSet集合/LinkedHashSet集合/TreeSet集合/比较器的使用/利用Set集合实现去重 一、Set集合 Set集合是Collection集合的一个子接口&#xff0c;实际上Set就是Collection&#xff0c;只是行为略有不同&#xff1a; Set集…

VUE3项目实现动态路由demo

文章目录1、创建vue项目2、安装常用的依赖2.1 安装elementUI2.2 安装axios2.3 安装router2.4 安装vuex2.5 安装store2.6 安装mockjs3、编写登录页面以及逻辑4、编写首页以及逻辑5、配置router.js6、配置store.js7、配置menuUtils.js&#xff08;动态路由重点&#xff09;8、配置…

树的前序遍历与中序遍历构造二叉树和树的中序遍历与后序遍历构造二叉树

目录 一.树的前序遍历与中序遍历构造二叉树 1.题目描述 2.问题分析 3.代码实现 二.树的中序遍历与后序遍历构造二叉树 1.题目描述 2.问题分析 3.代码实现 三.问题思考 一.树的前序遍历与中序遍历构造二叉树 1.题目描述 给定两个整数数组 preorder 和 inorder &#xf…

【机器学习】Logistic回归---学习笔记

Logistic回归学习笔记Logistic回归学习线路预备知识&#xff1a;建议先去B站学习一下信息量&#xff0c;熵&#xff0c;BL散度&#xff0c;交叉熵的概念。Logistic回归的函数模型损失最小化架构分类函数最大概率分类函数阈值分类函数Logistic回归的优化算法梯度下降随机梯度下降…

4.5--计算机网络之基础篇--2.网址到网页解析--(复习+深入)---好好沉淀,加油呀

1.浏览器做的第一步工作是解析 URL 对 URL 进行解析&#xff0c;从而生成发送给 Web 服务器的请求信息 URL? URL 实际上是请求服务器里的文件资源 当没有路径名时&#xff0c;就代表访问根目录下事先设置的默认文件&#xff0c;也就是 /index.html 或者 /default.html 这些文件…
最新文章