系列文章目录
Python 算法学习:链表合成(升序)
一、算法需求
给定两个升序排列的链表,采用多种方法,合并它们为一个新的升序链表。
二、方法+源码
方法1:迭代
- 这段代码首先创建了两个链表,然后调用merge_two_lists函数进行合并,并打印合并后的链表。输出应该是1 -> 1 -> 2 -> 3 -> 4 -> 4 -> None,表示合并后的链表是升序的。
代码如下:
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def merge_two_lists(l1, l2):
# 创建一个虚拟头节点,便于处理链表的合并
dummy = ListNode(0)
current = dummy
while l1 and l2:
if l1.val < l2.val:
current.next = l1
l1 = l1.next
else:
current.next = l2
l2 = l2.next
current = current.next
# 处理剩余节点
current.next = l1 or l2
return dummy.next
# 创建链表 1 -> 2 -> 4
list1 = ListNode(1, ListNode(2, ListNode(4)))
# 创建链表 1 -> 3 -> 4
list2 = ListNode(1, ListNode(3, ListNode(4)))
# 合并链表
merged_list = merge_two_lists(list1, list2)
# 打印合并后的链表
current = merged_list
while current:
print(current.val, end=" -> ")
current = current.next
print("None")
方法2:递归
-
这种方法的优点是代码简洁,直观地表达了合并链表的逻辑。然而,对于非常长的链表,递归可能会导致栈溢出。在实际应用中,迭代方法通常更受青睐,因为它避免了递归调用的开销。
-
在这个递归方法中:
首先检查l1或l2是否为空,如果为空,则直接返回另一个非空链表。
然后比较l1和l2当前节点的值,较小的那个节点会成为合并后链表的当前节点。
递归地调用merge_two_lists_recursive函数来合并剩下的链表部分,并将其链接到当前节点的next指针上。
最终返回合并后的链表的头节点。
代码如下:
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def merge_two_lists_recursive(l1, l2):
# 如果任意一个链表为空,直接返回另一个链表
if not l1:
return l2
if not l2:
return l1
# 比较当前节点的值,递归合并较小的那个
if l1.val < l2.val:
l1.next = merge_two_lists_recursive(l1.next, l2)
return l1
else:
l2.next = merge_two_lists_recursive(l1, l2.next)
return l2
# 测试递归方法
# 创建链表 1 -> 2 -> 4
list1 = ListNode(1, ListNode(2, ListNode(4)))
# 创建链表 1 -> 3 -> 4
list2 = ListNode(1, ListNode(3, ListNode(4)))
# 合并链表
merged_list = merge_two_lists_recursive(list1, list2)
# 打印合并后的链表
current = merged_list
while current:
print(current.val, end=" -> ")
current = current.next
print("None")
方法3:函数sorted
-
这种方法利用了Python的排序功能,将两个链表的元素合并到一个列表中,然后对列表进行排序,最后将排序后的列表转换回链表。这种方法简单且易于理解,但可能在性能上不如迭代和递归方法,因为它涉及到额外的列表操作和排序时间。
-
在这个实现中:
listify 函数用于将链表转换为列表,通过遍历链表并收集每个节点的值。
to_linked_list 函数用于将列表转换回链表,通过创建新的链表节点并链接它们。
merge_two_lists_sorted 函数首先将两个链表转换为列表,然后将这两个列表合并并使用sorted函数进行排序,最后将排序后的列表转换回链表。 -
这种方法的优点是代码简洁,易于实现,特别是对于初学者来说。然而,它在处理非常大的数据集时可能不够高效,因为列表操作和排序过程可能会消耗较多时间和内存。对于需要高性能的场景,迭代或递归方法通常是更好的选择。
代码如下:
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def merge_two_lists_sorted(l1, l2):
# 将链表转换为列表
def listify(head):
result = []
while head:
result.append(head.val)
head = head.next
return result
# 将列表转换回链表
def to_linked_list(lst):
dummy = ListNode(0)
current = dummy
for val in lst:
current.next = ListNode(val)
current = current.next
return dummy.next
# 合并两个链表的元素并排序
merged_list = sorted(listify(l1) + listify(l2))
return to_linked_list(merged_list)
# 测试排序方法
# 创建链表 1 -> 2 -> 4
list1 = ListNode(1, ListNode(2, ListNode(4)))
# 创建链表 1 -> 3 -> 4
list2 = ListNode(1, ListNode(3, ListNode(4)))
# 合并链表
merged_list = merge_two_lists_sorted(list1, list2)
# 打印合并后的链表
current = merged_list
while current:
print(current.val, end=" -> ")
current = current.next
print("None")
方法4:双指针
-
这种方法与迭代方法类似,但是它使用两个指针分别遍历两个链表,并将节点插入到一个新的链表中,而不是直接链接到当前节点。这种方法可以减少链表节点的重新分配,因为它避免了在迭代过程中不断更新节点的next指针。
-
在这个实现中:
dummy 是一个虚拟头节点,用于简化链表的构建过程。
current 是一个指针,用于构建新的链表。
使用两个while循环分别遍历两个链表,每次比较当前节点的值,并将较小值的节点链接到新链表的末尾。
当其中一个链表遍历完成后,将另一个链表的剩余部分直接链接到新链表的末尾。
最后返回dummy.next,即合并后的链表的头节点。 -
这种方法的优点是它直接在原链表上进行操作,减少了内存的使用,并且避免了递归调用的开销。它适用于需要高效合并链表的场景。
代码如下:
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def merge_two_lists_pointer(l1, l2):
dummy = ListNode(0) # 创建一个虚拟头节点
current = dummy # 使用current指针来构建新链表
while l1 and l2:
if l1.val < l2.val:
current.next = l1
l1 = l1.next
else:
current.next = l2
l2 = l2.next
current = current.next
# 处理剩余节点
current.next = l1 if l1 is not None else l2
return dummy.next # 返回合并后的链表头节点
# 测试双指针方法
# 创建链表 1 -> 2 -> 4
list1 = ListNode(1, ListNode(2, ListNode(4)))
# 创建链表 1 -> 3 -> 4
list2 = ListNode(1, ListNode(3, ListNode(4)))
# 合并链表
merged_list = merge_two_lists_pointer(list1, list2)
# 打印合并后的链表
current = merged_list
while current:
print(current.val, end=" -> ")
current = current.next
print("None")
方法5:heapq模块
-
该模块提供了一个基于堆的优先队列实现。我们可以将链表的元素添加到优先队列中,然后逐个取出最小的元素来构建新的链表。这种方法利用了堆的性质,可以高效地获取最小元素。
-
在这个实现中:
使用heapq模块的heappush和heappop函数来添加和移除元素。
每个元素是一个包含值、来源链表索引和节点本身的元组,这样可以在取出最小元素时知道下一步应该从哪个链表中取值。
使用一个虚拟头节点dummy和current指针来构建新的链表。
循环直到优先队列为空,每次循环中取出最小元素并将其链接到新链表中,然后检查并添加下一个节点到优先队列。 -
这种方法的优点是它可以高效地处理两个链表的合并,特别是当链表很长且元素数量不均衡时。然而,它需要额外的空间来存储优先队列,并且对于链表操作来说可能不如直接的链表操作直观。
代码如下:
import heapq
from collections import namedtuple
# 定义一个包含值和来源链表索引的元组
ListNode = namedtuple('ListNode', ['val', 'node'])
def merge_two_lists_heap(l1, l2):
# 创建一个优先队列
heap = []
# 将两个链表的头节点添加到优先队列中
if l1:
heapq.heappush(heap, (l1.val, 0, l1))
if l2:
heapq.heappush(heap, (l2.val, 1, l2))
dummy = ListNode(0) # 创建一个虚拟头节点
current = dummy # 使用current指针来构建新链表
# 从优先队列中逐个取出最小的元素
while heap:
val, list_idx, node = heapq.heappop(heap)
current.next = node
current = current.next
# 如果当前节点的下一个节点不为空,则将其添加到优先队列中
if node.next:
heapq.heappush(heap, (node.next.val, list_idx, node.next))
return dummy.next # 返回合并后的链表头节点
# 测试heapq方法
# 创建链表 1 -> 2 -> 4
list1 = ListNode(1, None)
list1.node = ListNode(2, None)
list1.node.node = ListNode(4, None)
# 创建链表 1 -> 3 -> 4
list2 = ListNode(1, None)
list2.node = ListNode(3, None)
list2.node = ListNode(4, None)
# 合并链表
merged_list = merge_two_lists_heap(list1, list2)
# 打印合并后的链表
current = merged_list
while current:
print(current.val, end=" -> ")
current = current.next
print("None")
总结
总而言之,每种方法都有其适用场景和优缺点。迭代和递归是最常用的方法,而双指针技术则提供了一种减少内存分配的方式。使用heapq模块的方法适合处理复杂的数据结构合并问题,而Python函数则提供了一种通用的解决方案。