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

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

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

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

目录

一,冒泡排序

二,选择排序

三,插入排序

四,归并排序

五,快速排序

六,基数排序

七,桶排序

八,基数排序

总结


一,冒泡排序

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

class Solution:
    def bubbleSort(self, head: L.ListNode):
        node_i = head
        tail = None
        # 外层循环为 链表节点个数
        while node_i:
            node_j = head
            while 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.val
                node_j = node_j.next
            # 此时链表最后一个元素为最大元素,将尾指针改变位置
            tail = node_j
            node_i = node_i.next

        return head

    def sortList(self, head):
        return self.bubbleSort(head)

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

        空间复杂度 O(1)

二,选择排序

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

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

    def selectSort(self, head: L.ListNode):
        node_i = head
        while node_i and node_i.next:
            min_node = node_i
            node_j = node_i.next

            while node_j:
                if node_j.val < min_node.val:
                    min_node = node_j
                node_j = node_j.next

            if node_i != min_node:
                node_i.val, min_node.val = min_node.val, node_i.val

            node_i = node_i.next

        return head
    
    def 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 head
        dummy = L.LinkedList(-1)
        dummy.next = head
        sorted_list = head
        cur = head.next
        while cur:
            if sorted_list.val <= cur.val:
            # 有序中最大的元素仍小于待插入元素,将有序区扩展一位
                sorted_list = sorted_list.next
            else:
                pre = dummy
                while pre.next.val <= cur.val:
                    pre = pre.next
                sorted_list.next = cur.next
                cur.next = pre.next
                pre.next = cur
            cur = sorted_list.next
        return dummy.next

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

        空间复杂度 O(1)

四,归并排序

        分割:

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

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

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

        归并:

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

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

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

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

    def merge(self, left, right):
        # 归并环节
        dummy = L.LinkedList(-1)
        cur = dummy
        while left and right:
            if left.val < right.val:
                cur.next = left
                left = left.next
            else:
                cur.next = right
                right = right.next
            cur = cur.next

        if left:
            cur.next = left
        if right:
            cur.next = right

        return dummy.next

    def mergeSort(self, head: L.ListNode):
        # 分割环节
        if not head or not head.next:
            return head

        # 快慢指针找到中心链节点
        slow, fast = head, head.next
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next

        # 断开左右链表,用于后续分割
        left_head = head
        right_head = slow.next
        slow.next = None

        return 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.next

        while node_j != right:
            if node_j.val < pivot:
                # 因为 node_i 节点之前的值都小于 pivot 因此先将 node_i 后移一位
                # 此时 node_i 的值大于等于 pivot
                node_i = node_i.next
                # 判断语句表明此时 node_j 的值小于 pivot,
                # 交换 i,j 的值,保证node_i 的值小于pivot
                node_i.val, node_j.val = node_j.val, node_i.val

            node_j = node_j.next
        # 循环结束后,node_i 的值指向最后一个严格小于 pivot 的位置,将 pivot 归位
        node_i.val, left.val = left.val, node_i.val
        return node_i

    def quickSort(self, left: L.ListNode, right: L.ListNode):
        if left == right or left.next == right:
            return left

        mid = 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 head
        list_max = float('-inf')
        list_min = float('inf')
        cur = head
        while cur:
            if cur.val < list_min:
                list_min = cur.val
            if cur.val > list_max:
                list_max = cur.val
            cur = cur.next

        size = list_max - list_min + 1
        counts = [0 for _ in range(size)]

        cur = head
        while cur:
            counts[cur.val - list_min] += 1
            cur = cur.next

        dummy = L.ListNode(-1)
        cur = dummy
        for i in range(size):
            while counts[i]:
                cur.next = L.ListNode(i + list_min)
                counts[i] -= 1
                cur = cur.next

        return 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] = node

    def bucketSort(self, head: L.ListNode, bucket_size = 5):
        if not head:
            return head

        list_max = float('-inf')
        list_min = float('inf')
        cur = head
        while cur:
            if cur.val < list_min:
                list_min = cur.val
            if cur.val > list_max:
                list_max = cur.val
            cur = cur.next

        # 计算桶的个数
        bucket_count = (list_max - list_min) // bucket_size + 1
        buckets = [None for _ in range(bucket_count)]

        # 将链表节点值依次添加到对应桶中
        cur = head
        while cur:
            index = (cur.val - list_min) // bucket_size
            self.insertion(buckets, index, cur.val)
            cur = cur.next

        dummy = L.ListNode(-1)
        cur = dummy
        # 将元素依次出桶,并拼接成有序列表
        for bucket_head in buckets:
            bucket_cur = self.mergeSort(bucket_head)
            # 沿着每个桶内的元素,到达每个新链表的末尾节点,以连接下一个桶
            while bucket_cur:
                cur.next = bucket_cur
                cur = cur.next
                bucket_cur = bucket_cur.next
        return dummy.next

        时间复杂度 O(n)

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

八,基数排序

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

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

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

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

    def radixSort(self, head: L.ListNode):
        # 计算最长的位数
        size = 0
        cur = head
        while cur:
            val_len = len(str(cur.val))
            if val_len > size:
                size = val_len
            cur = cur.next

        # 从个位到高位遍历,按照从低到高位排序 size 次
        for i in range(size):
            buckets = [[] for i in range(10)]
            cur = head
            while cur:
                # 将每个节点的值放入对应的桶
                buckets[cur.val // (10 ** i) % 10].append(cur.val)
                cur = cur.next

            dummy = L.ListNode(-1)
            cur = dummy
            for bucket in buckets:
                for num in bucket:
                    cur.next = L.ListNode(num)
                    cur = cur.next

        return dummy.next

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

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

总结

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

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

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

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

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

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

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

  • 25
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值