本系列是算法通关手册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)
原文内容在这里,如有侵权,请联系我删除。