数组和链表中的归并排序
题目
- 有一个数组,对其进行 O ( n log n ) O(n\log n) O(nlogn)时间复杂度的归并排序
- 有一个链表,对其进行 O ( n log n ) O(n\log n) O(nlogn)时间复杂度的归并排序
- 对链表的二路归并部分使用堆排序实现
1.归并排序的思路
- 递归基:数组或链表中的元素个数小于2,不用排序,返回数组
- 二分:找到数组或链表的中点,继而分为前后两部分
- 二分递归:对前后两部分数组或链表分别进行递归处理得到
la
和ra
- 二路归并:对二路递归得到的
la
和ra
的元素进行归并处理
2.数组的归并排序
按照归并排序的思路,默写下数组归并排序的过程:
def mergesort(nums:"List")->"List":
#递归基
if len(nums)<2: return nums
#二分
mid = len(nums)//2
#二分递归
la = mergesort(nums[:mid])
ra = mergesort(nums[mid:])
#二路归并
res = []
i = j = 0
while i<len(la) or j<len(ra):
if i<len(la) and (j>=len(ra) or la[i]<=ra[j]):
res.append(la[i])
i += 1
if j<len(ra) and (i>=len(la) or ra[j]<la[i]):
res.append(ra[j])
j += 1
return res
if __name__ == "__main__":
nums = [1,5,1,2,3,0,-5]
print(mergesort(nums))#[-5,0,1,1,2,3,5]
3.链表的归并排序
首先简单定义单向链表:
class ListNode:
def __init__(self,x):
self.val = x
self.next = None
同样的,依据归并排序的思想,我们分为以下几个步骤进行讲述:
3.1递归基
我们知道归并排序的递归基是当数组/链表中的元素小于2个时,返回数组/链表。假设链表的头节点为head
,由此我们得到链表递归排序的递归基为:
if not head or not head.next: return head
3.2二分
我们对于数组的二分可以直接使用mid = len(nums)//2
实现;但对于链表,我们可以通过设置快慢指针,(lower = head, faster = head.next
)使得快指针的速度是慢指针的两倍,这样当快指针指到末尾时,慢指针就找到链表的中间节点(前半部分的最后一个节点):
lower, faster = head, head.next
while lower and faster and faster.next:
lower = lower.next
faster = faster.next.next
mid = lower.next
lower.next = None
这里我们使用while lower and faster and faster.next:
,保证了lower
有next
属性,faster
有next
属性,以及faster.next
有next
属性.
3.3.二分递归
和数组的二分递归一样,我们对前后两部分分别进行二路递归:
la = self.sortList(head)
ra = self.sortList(mid)
3.4二路归并
我们可以分为共同长度部分和多出来的部分。
我们可以写出具有dummy
头节点的二路归并如下:
dummy = ListNode(None)
cur = dummy
#处理共同长度部分
while la and ra:
if la.val <= ra,val:
cur.next = la
la = la.next
else:
cur.next = ra
ra = ra.next
cur = cur.next
#处理多出来的部分
cur.next = la if la else ra
3.5返回值
返回dummy
节点的下一个节点
return dummy.next
得到完整代码如下:
$code for ListSort:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def sortList(self, head: ListNode) -> ListNode:
#递归基
if not head or not head.next: return head
#二分
lower, faster = head, head.next
while lower and faster and faster.next:
lower = lower.next
faster = faster.next.next
mid = lower.next
lower.next = None
#二分递归
la = self.sortList(head)
ra = self.sortList(mid)
#二路归并(带dummy头节点)
dummy = ListNode(None)
cur = dummy
while la and ra:
if la.val < ra.val:
cur.next = la
la = la.next
else:
cur.next = ra
ra = ra.next
cur = cur.next
cur.next = la if la else ra
return dummy.next
4.二路归并部分使用二叉堆
我们可以使用小顶堆实现二路归并部分,我们注意到这化简为一个堆排序的问题,通过不断地入堆、出堆处理,我们得到最终的结果。
假设我们已经得到要二路归并的前后两个链表la
和ra
,我们导入heapq
:
import heapq
为兼容起见,注意在应用heapq
时不要压入自定义的类。
我们得到二路归并的部分:
lists = [la, ra]
heap = []
dummy = ListNode(None)
cur = dummy
#初始化优先队列
for i in range(len(lists)):
if lists[i]:
heapq.heappush(heap,(lists[i].val,i))
lists[i] = lists[i].next
#堆排序
while heap:
val, idx = heapq.heappop(heap)
cur.next = ListNode(val)
cur = cur.next
if lists[i]:
heapq.heappush((lists[i].val, i))
lists[i] = lists[i].next
return dummy.next