数据结构与算法----复习Part 7 (链表排序)

本系列是算法通关手册LeeCode的学习笔记

算法通关手册(LeetCode) | 算法通关手册(LeetCode) (itcharge.cn)

本系列为自用笔记,如有版权问题,请私聊我删除。

目录

一,冒泡排序

二,选择排序

三,插入排序

四,归并排序

五,快速排序

六,基数排序

七,桶排序

八,基数排序

总结


一,冒泡排序

        因为链表只能顺序访问,因此需要一个尾节点 tail 用于记录排好的位置。其余思想与数组排序大同小异。

class Solution:def bubbleSort(self, head: L.ListNode):node_i = headtail = None# 外层循环为 链表节点个数while node_i:node_j = headwhile node_j and node_j.next != tail:if node_j.val > node_j.next.val:node_j.val, node_j.next.val = node_j.next.val, node_j.valnode_j = node_j.next# 此时链表最后一个元素为最大元素,将尾指针改变位置tail = node_jnode_i = node_i.nextreturn headdef sortList(self, head):return self.bubbleSort(head)

        时间复杂度 O(n ** 2)

        空间复杂度 O(1)

二,选择排序

        设置 min_node 用于记录无序区中,最小元素存储的位置

        遍历无序区间,若当前节点元素小于 min_node 中的元素,则交换

    def selectSort(self, head: L.ListNode):node_i = headwhile node_i and node_i.next:min_node = node_inode_j = node_i.nextwhile node_j:if node_j.val < min_node.val:min_node = node_jnode_j = node_j.nextif node_i != min_node:node_i.val, min_node.val = min_node.val, node_i.valnode_i = node_i.nextreturn headdef sortList(self, head):return self.selectSort(head)

        时间复杂度 O(n ** 2)

        空间复杂度 O(1)

三,插入排序

        构造一个哨兵节点 dummy ,使得可以从 head 开始遍历;

        维护一个 selected_list 指向已排序部分的最后一个节点;

        cur 为待插入元素,用 pre 记录插入的位置,即插入位置的前一个节点。

    def insertSort(self, head: L.ListNode):if not head or not head.next:return headdummy = L.LinkedList(-1)dummy.next = headsorted_list = headcur = head.nextwhile cur:if sorted_list.val <= cur.val:# 有序中最大的元素仍小于待插入元素,将有序区扩展一位sorted_list = sorted_list.nextelse:pre = dummywhile pre.next.val <= cur.val:pre = pre.nextsorted_list.next = cur.nextcur.next = pre.nextpre.next = curcur = sorted_list.nextreturn dummy.next

        时间复杂度 O(n ** 2)

        空间复杂度 O(1)

四,归并排序

        分割:

                用快慢指针找到链表的中心节点,从中心节点将链表断开,并递归分割:

                        让 fast 每次移动 2 步, slow 每次移动 1 步,使 slow 停在链表中间位置;

                        对左右链表进行递归分割,直到每个链表中只包含一个链节点。

        归并:

                将递归后的链表进行两两归并,直到得到完整链表:

                        使用哨兵节点构造一个头节点;

                        比较像个链表头节点的 left 和right 值的大小,较小的节点加入合并后的链表中;

                        出现空链表后,将另一个链表接到合并链表中。

    def merge(self, left, right):# 归并环节dummy = L.LinkedList(-1)cur = dummywhile left and right:if left.val < right.val:cur.next = leftleft = left.nextelse:cur.next = rightright = right.nextcur = cur.nextif left:cur.next = leftif right:cur.next = rightreturn dummy.nextdef mergeSort(self, head: L.ListNode):# 分割环节if not head or not head.next:return head# 快慢指针找到中心链节点slow, fast = head, head.nextwhile fast and fast.next:slow = slow.nextfast = fast.next.next# 断开左右链表,用于后续分割left_head = headright_head = slow.nextslow.next = Nonereturn self.merge(self.mergeSort(left_head), self.mergeSort(right_head))

        时间复杂度 O(n * logn)

        空间复杂度 O(1)

五,快速排序

        选择一个基准值 pivot;

        通过快慢指针,找到基准值在链表中的正确位置;

        将基准值归位并把链表分为两段;

        递归地使基准值归位;

    def patition(self, left: L.ListNode, right: L.ListNode):# 左闭右开,区间没有元素或只有一个元素if left == right or left.next == right:return left# 选择当前第一个节点作为基准节点pivot = left.val# 使用node_i 、node_j 双指针,保证 node_i 之前的元素都严格小于基准节点的值, # node_i 、 node_j 之间的元素都大于等于基准结点的值# 这样 node_i 记录了基准节点 pivot 最后的位置# 将 pivot 与 node_i 节点的值交换后,实现了 patition 功能node_i, node_j = left, left.nextwhile node_j != right:if node_j.val < pivot:# 因为 node_i 节点之前的值都小于 pivot 因此先将 node_i 后移一位# 此时 node_i 的值大于等于 pivotnode_i = node_i.next# 判断语句表明此时 node_j 的值小于 pivot,# 交换 i,j 的值,保证node_i 的值小于pivotnode_i.val, node_j.val = node_j.val, node_i.valnode_j = node_j.next# 循环结束后,node_i 的值指向最后一个严格小于 pivot 的位置,将 pivot 归位node_i.val, left.val = left.val, node_i.valreturn node_idef quickSort(self, left: L.ListNode, right: L.ListNode):if left == right or left.next == right:return leftmid = self.patition(left, right)self.quickSort(left, mid)self.quickSort(mid.next, right)

        时间复杂度 O(n * logn)

        空间复杂度 O(1)

六,基数排序

        先遍历一遍链表,找到链表中的最大值 list_max 和最小值 list_min;

        使用 counts 数组 存储节点出现的次数;

        遍历列表,将 val 出现的次数存入 count 数组中,数组的下标为 cur.val - list_min;

        建立哨兵节点 dummy ,遍历 counts 并建立值为 count[ i ]+ list_min 的节点,连接到尾部;

        直到 counts 所有元素为 0

    def countingSort(self, head: L.ListNode):if not head:return headlist_max = float('-inf')list_min = float('inf')cur = headwhile cur:if cur.val < list_min:list_min = cur.valif cur.val > list_max:list_max = cur.valcur = cur.nextsize = list_max - list_min + 1counts = [0 for _ in range(size)]cur = headwhile cur:counts[cur.val - list_min] += 1cur = cur.nextdummy = L.ListNode(-1)cur = dummyfor i in range(size):while counts[i]:cur.next = L.ListNode(i + list_min)counts[i] -= 1cur = cur.nextreturn dummy.next

        时间复杂度 O(n + k);

        空间复杂度 O(k)  ,k 为待排序链表中所有元素的值域。

七,桶排序

        遍历一遍链表,找到最大值 list_max 和最小值 list_min;

        计算出桶的大小;

        再遍历一遍元素,将每个元素装入对应的桶中;

        对每个桶内的元素进行排序;

        按照顺序将桶内元素拼成新链表,并返回。

    # 将链表节点值插入到对应的桶中def insertion(self, buckets, index, val):# 如果子链为空,则建立一个子链的头节点if not buckets[index]:buckets[index] = L.ListNode(val)return# 如果子链非空,则将建立新节点并接入子链# 保持buckets[index]指向子链的头部位置,便于排序的传入node = L.ListNode(val)node.next = buckets[index]buckets[index] = nodedef bucketSort(self, head: L.ListNode, bucket_size = 5):if not head:return headlist_max = float('-inf')list_min = float('inf')cur = headwhile cur:if cur.val < list_min:list_min = cur.valif cur.val > list_max:list_max = cur.valcur = cur.next# 计算桶的个数bucket_count = (list_max - list_min) // bucket_size + 1buckets = [None for _ in range(bucket_count)]# 将链表节点值依次添加到对应桶中cur = headwhile cur:index = (cur.val - list_min) // bucket_sizeself.insertion(buckets, index, cur.val)cur = cur.nextdummy = L.ListNode(-1)cur = dummy# 将元素依次出桶,并拼接成有序列表for bucket_head in buckets:bucket_cur = self.mergeSort(bucket_head)# 沿着每个桶内的元素,到达每个新链表的末尾节点,以连接下一个桶while bucket_cur:cur.next = bucket_curcur = cur.nextbucket_cur = bucket_cur.nextreturn dummy.next

        时间复杂度 O(n)

        空间复杂度 O(n + m) ,m 为桶个数

八,基数排序

        遍历链表,得到最长位数 size;

        建立十个桶,存储 0 - 9 十位数字;

        以每个节点对应位数上的数字,放入桶中;

        建立一个哨兵节点 dummy,将桶中元素依次取出,接在其后。

    def radixSort(self, head: L.ListNode):# 计算最长的位数size = 0cur = headwhile cur:val_len = len(str(cur.val))if val_len > size:size = val_lencur = cur.next# 从个位到高位遍历,按照从低到高位排序 size 次for i in range(size):buckets = [[] for i in range(10)]cur = headwhile cur:# 将每个节点的值放入对应的桶buckets[cur.val // (10 ** i) % 10].append(cur.val)cur = cur.nextdummy = L.ListNode(-1)cur = dummyfor bucket in buckets:for num in bucket:cur.next = L.ListNode(num)cur = cur.nextreturn dummy.next

        时间复杂度 O(n * k);

        空间复杂度 O(n + k) ,其中 n 是待排序元素的个数,k 是数字位数。

总结

        由于希尔排序涉及到随机访问,因此不适用于链表的排序。

        而堆排序使用到的完全二叉树结构更适合顺序存储的方式。

        以上算法重点掌握 插入排序 和 归并排序即可,其余更多的去体会使用节点遍历链表的方法与过程,并能理解对某个链节点操作时,指针的处理顺序。

本篇文章只给了链表排序的实现代码和简洁思路,更详细的图解请移步:

数据结构与算法----复习Part 4(数组排序)-CSDN博客

算法通关手册(LeetCode) | 算法通关手册(LeetCode)

原文内容在这里,如有侵权,请联系我删除。

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

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

相关文章

代码随想录算法训练营第50天 | 70.爬楼梯(进阶) + 322.零钱兑换 + 279.完全平方数

今日任务 70. 爬楼梯 &#xff08;进阶&#xff09; 322. 零钱兑换 279.完全平方数 70.爬楼梯(进阶) - Easy 题目链接&#xff1a;题目页面 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 思路&a…

【ASP.NET Core 基础知识】--安全性--防范常见攻击

在现实网络中即存在着安全的流量&#xff0c;又存在着不安全的流量在&#xff0c;这些不安全的流量常常会对我们的网站服务造成威胁&#xff0c;严重的甚至会泄露用户的隐私信息。这篇文章我们通过对常见的网络攻击跨站脚本攻击、跨站请求伪造(CSRF)、SQL注入、敏感数据泄露、身…

前沿技术期刊追踪——以电机控制为例

一、背景 前沿技术期刊追踪是指科研人员、学者或专业人士通过关注和阅读各类顶级科技期刊&#xff0c;了解并跟踪相关领域的最新研究成果和发展动态。以下是一些常见的前沿技术期刊以及追踪方法&#xff1a; 1. **知名科技期刊**&#xff1a; - 自然&#xff08;Nature&#…

片上网络NoC(6)——路由算法

目录 一、概述 二、路由算法的类型 三、避免死锁 四、实现 4.1 源路由实现 4.2 基于节点查找表的路由实现 4.3 组合电路实现 五、总结 一、概述 路由算法&#xff08;routing algorithm&#xff09;&#xff0c;即决定数据包在网络拓扑中从起点到终点路径的算法。路由算…

算法训练day30回溯算法总结

文章链接 代码随想录 (programmercarl.com) 回溯是递归的副产品&#xff0c;只要有递归就会有回溯&#xff0c;所以回溯法也经常和二叉树遍历&#xff0c;深度优先搜索混在一起&#xff0c;因为这两种方式都是用了递归。 回溯法就是暴力搜索&#xff0c;并不是什么高效的算法…

第十九篇【传奇开心果系列】Python的OpenCV库技术点案例示例:文字识别与OCR

传奇开心果短博文系列 系列短博文目录Python的OpenCV库技术点案例示例系列 短博文目录前言一、OpenCV 文字识别介绍二、图像预处理示例代码三、文字区域检测示例代码四、文字识别示例代码五、文字后处理示例代码六、OpenCV结合Tesseract OCR库实现文字识别示例代码七、OpenCV结…

算法沉淀——栈(leetcode真题剖析)

算法沉淀——栈 01.删除字符串中的所有相邻重复项02.比较含退格的字符串03.基本计算器 II04.字符串解码05.验证栈序列 栈&#xff08;Stack&#xff09;是一种基于先进后出&#xff08;Last In, First Out&#xff0c;LIFO&#xff09;原则的数据结构。栈具有两个主要的操作&am…

【王道数据结构】【chapter5树与二叉树】【P159t12】

设一棵二叉树的结点结构为(LLINK,INFO,RLINK)&#xff0c;ROOT为指向该二叉树根结点的指针&#xff0c;p和q分别为指向该二叉树中任意两个节点的指针&#xff0c;试编写算法ANCESTOR(ROOT,p,q,r)&#xff0c;找到p和q的最近公共祖先结点r #include <iostream> #include &…

re:从0开始的CSS学习之路 9. 盒子水平布局

0. 写在前面 过年也不能停止学习&#xff0c;一停下就难以为继&#xff0c;实属不应 1. 盒子的水平宽度 当一个盒子出现在另一个盒子的内容区时&#xff0c;该盒子的水平宽度“必须”等于父元素内容区的宽度 盒子水平宽度&#xff1a; margin-left border-left padding-lef…

QT 工具栏 状态栏 停靠部件 核心部件

添加/删除工具栏 删除工具栏方法和删除菜单栏方法一样&#xff0c;不过工具栏可以有多个&#xff0c;所以每次右键MainWindow对象&#xff0c;都可以看到添加工具栏的选项。 工具栏添加动作 新添加的QAction对象会在动作编辑器里找到&#xff08;Action Editor&#xff09;&a…

计算机组成原理(1)----主存储器

目录 1.基本半导体元件及原理 2.寻址 1.基本半导体元件及原理 一个主存储器可以分为存储器&#xff0c;MAR&#xff08;地址寄存器&#xff09;和MDR&#xff08;数据寄存器&#xff09;&#xff0c;这三个部件由在时序控制逻辑的控制下工作 其中存储体用来存放二进制数据0和…

[字符串] KMP与字符哈希

KMP 首先&#xff0c;要知道在KMP算法里的 next 数组里&#xff0c;对操作的字符串到底存储了什么。 以当前字符为结尾的子串&#xff0c;真前缀与真后缀相同的最长长度。&#xff08;注意&#xff1a;不是说回文&#xff1b;而且是“真”&#xff0c;也就是说&#xff0c;不…

百万级并发分布式锁

需求是要支持春节百万并发高并发抢购红包商品。 架构师经过多方技术调研&#xff0c;整理开发以下几个核心步骤&#xff1a; 1.使用redis缓存icon支持高并发 2.商品数据量先存入缓存icon中 3.抢购商品锁定&#xff0c;并从缓存中读取数量减1 4.释放商品锁 代码 import org.r…

国产制造,欧美品质:爱可声助听器产品质量获国际认可

随着科技的发展和全球化的推进&#xff0c;越来越多的中国制造产品开始走向世界舞台。其中&#xff0c;爱可声助听器凭借其卓越的产品质量&#xff0c;成为了国产制造的骄傲。 国产制造指的是在中国境内生产的产品&#xff0c;欧美品质则是指产品在设计、生产、质量控制等方面…

基于RBF神经网络的自适应控制器simulink建模与仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 4.1自适应控制器 4.2 RBF神经网络模型 5.完整程序 1.程序功能描述 在simulink中&#xff0c;使用S函数编写基于RBF神经网络的自适应控制器&#xff0c;然后实现基于RBF神经网络的自适应控制…

手撕链表OJ

&#x1d649;&#x1d65e;&#x1d658;&#x1d65a;!!&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦ &#x1f44f;&#x1f3fb;‧✧̣̥̇:Solitary-walk ⸝⋆ ━━━┓ - 个性标签 - &#xff1a;来于“云”的“羽球人”。…

Vue CLI学习笔记

在看任何开源库的源码之前&#xff0c;必须先了解它有哪些功能&#xff0c;这样才能针对性地分模块阅读源码。 Vue CLI 简介 Vue CLI是Vue.js的官方命令行工具&#xff0c;它是一个基于Vue.js进行快速开发的完整系统。 通过Vue CLI&#xff0c;开发者可以快速搭建和开发Vue.js项…

VC++ 绘制折线学习

win32 有三个绘制折线的函数&#xff1b; Polyline&#xff0c;根据给定点数组绘制折线&#xff1b; PolylineTo&#xff0c;除了绘制也更新当前位置&#xff1b; PolyPolyline&#xff0c;绘制多条折线&#xff0c;第一个参数是点数组&#xff0c;第二个参数是一个数组、指…

QT学习事件

一、事件处理过程 众所周知 Qt 是一个基于 C 的框架&#xff0c;主要用来开发带窗口的应用程序&#xff08;不带窗口的也行&#xff0c;但不是主流&#xff09;。 我们使用的基于窗口的应用程序都是基于事件&#xff0c;其目的主要是用来实现回调&#xff08;因为只有这样程序…

Leetcode 392 判断子序列

题意理解&#xff1a; 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&#xff0c;"ace"是"abcde&quo…