12.5 链表排序
12.5.1 插入排序
将原链表的每一个结点取出,放入到新的排序链表中。
#-*-coding: utf-8-*-
# 链表的插入排序
def llistInsertionSort(origList):
# 确保链表非空
if origList is None:
return
# 对原链表遍历
newList = None
while origList is not None:
# 临时外部指针
curNode = origList
# 移动原链表的头指针
origList = origList.next
# 将结点从原链表中抽离,并放入到新的排序链表中
curNode.next = None
newList = addToSortedList(newList, curNode)
return newList
def addToSortedList(head, node):
predNode = None
curNode = head
while curNode is not None and node.data > curNode.data:
predNode = curNode
curNode = curNode.next
node.next = curNode
if curNode is head:
head = node
else:
predNode.next = node
与python列表类似,其时间复杂度也是O(n²)。不同之处在于,不需要像列表那样,对每一个元素进行移位。
12.5.2 归并排序
#-*-coding: utf-8-*-
# 链表的归并排序
def llistMergeSort(theList):
# 如果链表为空,即递归终止条件,返回None
if theList is None:
return None
# 分割链表成两部分
rightList = _splitLinkedList(theList)
leftList = theList
# 对左半部分链表分割
leftList = llistMergeSort(leftList)
# 对右半部分分割
rightList = llistMergeSort(rightList)
# 合并两个排好序的子链表
theList = _mergeLinkedLists(leftList, rightList)
return theList
# 分割链表,返回右半部分链表的头指针,左半部分链表的头指针,仍然是原链表的头指针
def _splitLinkedList(subList):
# 临时外部指针,分别指向第一和第二个结点
midPoint = subList
curNode = midPoint.next
# 对原链表进行遍历,curNode的移动速度是midPoint的两倍,当curNode移动至原链表末尾时,此时midPoint正好移动到链表中间的位置
while curNode is not None:
curNode = curNode.next
if curNode is not None:
midPoint = midPoint.next
curNode = curNode.next
rightList = midPoint.next # 设置右半部分链表的头指针
midPoint.next = None # 将右半部分链表从原链表中分离
return rightList
# 合并两个排好序的子链表
def _mergeLinkedLists(subListA, subListB):
# 创建假结点
newList = ListNode(None)
newTail = newList
# 向新链表补插入subListA和subListB的结点,直到遍历完其中一个链表为止
while subListA is not None and subListB is not None:
if subListA.data <= subListB.data:
newTail.next = subListA
subListA = subListA.next
else:
newTail.next = subListB
subListB = subListB.next
newTail = newTail.next
newTail.next = None
# 将另一个链表的剩余部分,也添加到新链表中来
if subListA is not None:
newTail.next = subListA
else:
newTail.next = subListB
# 返回假结点的下一个结点
return newList.next
与之前序列的归并排序不同的是,链表的归并排序并不需要包装函数,因为只需传入链表的头指针即可。我们要注意的是两个辅助方法。
_splitLinkedList()将原链表分割成两部分,尤其值得注意的是两个外部指针midPoint和curNode的移动,后者的移动速度是前者的两倍,当curNode移动至链表末尾时,midPoint正好指向链表的中间位置,再将右半部分的头指针设置为midPoint的下一个结点即可,左半部分的头指针仍是原来的头指针,最后将两者分开即可。
再看另一个辅助方法_mergeLinkedLists(),该方法主要是将两个排好序的链表合并。它使用尾指针的目的在于,在链表的末尾补插元素(元素从小到大排列)只需要O(1)的时间复杂度。此外,该方法还使用了假结点(dummy node),假结点是数据域为None的结点,使用它可以在插入或删除结点时避免使用if来分情况讨论(?)。