8.2.1 (python)排序算法之链表排序LeetCode题目(1) —— Sort List & Merge k Sorted Lists

首先,在排序的题目中,我们先总结、学习一下,链表排序的问题,顺便也回忆一下链表相关操作。对应于下面两道题

147. Insertion Sort List

Sort a linked list using insertion sort.

148. Sort List

Sort a linked list in O(n log n) time using constant space complexity.

链表排序算法解析:

对于链表这种无法靠索引获得值、指针无法逆序(可以递归写?)的数据结构,许多排序算法失效。我们先看基本的几类排序如何:

选择排序,每次遍历链表无序区,找无序区最小值,插入到有序区最后,保存好有序区末尾这个珍贵的指针,实现方法见下一。对于第一题来说,下面勉强可过,原因是有一个测试例子,链表完全倒序,这在选择排序中对应最坏情况。实际上选择和插入平均复杂度是一样的,只是选择排序有这种badcase。

插入排序,最适合链表了,无序区一个指针依次向后取,然后从头开始在有序区查找插入点。代码看下二。注意有一个特殊处理,由于链表无法二分查找,我们先和有序区最后一个比一下,防止最坏的情况~

对于插入排序的优化——希尔排序,需要依靠数组的索引做增量,明显无法实现了;选择排序的优化——堆排序,实际上是在有序区插入时查找更快,也依赖堆的数组实现,不可用。

冒泡排序鄙人没有实现,简单的冒泡就是前后两个节点的元素顺序不对,就调整过来(换节点或换节点值),是可行的。

至于链表的快排,快排的思想是设定一个pivot,在每趟排序中,让pivot左边都小,右边都大;左边依次查找,右边从后向前查找,交换,本质上也是基于交换的。窃以为可以实现,也不敢妄议,如何设计让指针倒着查找即可,有空再研究,而且实现了也不敢说链表上这种快排,时间复杂度还是不是O(nlogn)的。

class Solution:
    def selectSortList(self, head: ListNode) -> ListNode:
        if not head:
            return None
        new_h = ListNode(float("-inf"))
        new_h.next = head
        tail = new_h  # 有序区尾
        while tail.next:
            p = tail.next
            min_val = p.val
            pre = tail  # 最小值前一个节点
            temp = tail
            # 当前最小
            while p:
                if p.val < min_val:
                    pre = temp
                    min_val = p.val
                temp = p
                p = p.next
            # 最小值删除
            pre.next = pre.next.next
            # 最小值插入到有序区最后
            t = tail.next
            tail.next = ListNode(min_val)
            tail.next.next = t
            tail = tail.next  # 有序区加1
        return new_h.next
class Solution:
    def insertionSortList(self, head: ListNode) -> ListNode:
        if not head:
            return None
        new_h = ListNode("x")
        new_h.next = head
        # 有序区尾
        tail = new_h.next
        tail_val = tail.val
        while tail.next:
            # 要插入的值
            insert_val = tail.next.val
            # 找插入位置
            if insert_val >= tail_val:
                # 位置不变,尾部后移
                tail = tail.next
                tail_val = tail.val
            else:
                tail.next = tail.next.next
                pos = new_h
                while pos.next:
                    if pos.next.val > insert_val:
                        break
                    pos = pos.next
                temp = pos.next
                pos.next = ListNode(insert_val)
                pos.next.next = temp
        return new_h.next

对于第二道题,我们来看一下可行的 O(nlogn) 的排序方法。快排刚才质疑了,那么可行的只剩下归并大法了。我们看下面的代码。链表的归并和数组排序的归并,基本思路是一样的。在切分两个归并段时要注意链表尾部=None。

顺便说一下,此题是链表排序的通用方法,并没有对链表元素的数据分布钦定。可惜的是,测试案例中都是正整数?用基数排序的线性排序法也可解决此题。不再列出代码。

class Solution:
    def sortList(self, head: ListNode) -> ListNode:
        # print(head)
        if not head or not head.next:
            return head
        
        def merge(h1, h2) -> ListNode:
            head = ListNode("x")
            p = head
            while h1 and h2:                                    
                v1 = h1.val
                v2 = h2.val
                if v1 <= v2:
                    p.next = h1
                    h1 = h1.next
                else:
                    p.next = h2
                    h2 = h2.next
                p = p.next
            if h1:
                p.next = h1
            if h2:
                p.next = h2
            return head.next
        
        pslow = head
        pfast = head
        
        while pfast.next and pfast.next.next:
            pslow = pslow.next
            pfast = pfast.next.next
        head_2 = pslow.next
        pslow.next = None
        merge_left = self.sortList(head)
        merge_right = self.sortList(head_2)
        return merge(merge_left, merge_right)

熟练掌握链表的归并排序,下面这道题也迎刃而解的。

23. Merge k Sorted Lists

Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.

Example:

Input:
[
  1->4->5,
  1->3->4,
  2->6
]
Output: 1->1->2->3->4->4->5->6

题目解析:

将链表的list拆分再拆分,再两个两个归并。性能还不错。

优化方向:可借鉴外排序中介绍的多路归并、败者树等方案。

class Solution:
    def mergeKLists(self, lists: List[ListNode]) -> ListNode:
        if not lists:
            return None
        if len(lists) == 1:
            return lists[0]
        
        def merge_two(h1, h2):
            head = ListNode("x")
            p = head
            while h1 and h2:
                val1 = h1.val
                val2 = h2.val
                if val1 <= val2:
                    p.next = h1
                    h1 = h1.next
                else:
                    p.next = h2
                    h2 = h2.next
                p = p.next
            if h1:
                p.next = h1
            if h2:
                p.next = h2
            return head.next
        if len(lists) == 2:
            return merge_two(lists[0], lists[1])
        mid = len(lists) // 2        
        merge_l = self.mergeKLists(lists[:mid])
        merge_r = self.mergeKLists(lists[mid:])
        return merge_two(merge_l, merge_r)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值