经典的反转链表三种解决思路。
问题
问题:实现一个函数,输入一个链表的头结点,反转该链表并输出反转后的链表的头结点。
先实现单链表版本,然后实现双链表版本。
滑动指针
原理:
反转链表的其实就是在链表上调整指针的方向,而整体上的反转需要局部上的反转。只要实现了局部上的反转,将局部反转应用与整个链表就能得到反转的链表。局部的反转如下:我们先对链表上的结点依据顺序三三组合。
例如现在有四个结点的链表:
1
| Node<1>->Node<2>->Node<3>->Node<4>
|
三三组合就有两种:
1 2
| Node<1>->Node<2>->Node<3> Node<2>->Node<3>->Node<4>
|
定义三个指针:
- p_node
- p_prev
- p_reversed_head
为了避免出现断链,我们在翻转p_node和p_prev时需要先保存p_node.next。
实现:
先实现链表。方便生产测试用例,编写build_linked_list
函数,通过输入序列建立链表,输出该链表的头结点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Node: def __init__(self, value): self.value = value self.next = None
def __repr__(self): return "Node<{}>".format(self.value)
def build_linked_list(node_list): if not node_list: return Node(None) root = Node(node_list[0]) val = root for value in node_list[1:]: node = Node(value) val.next = node val = node return root
def travel_list(p_node): while p_node: print(p_node) p_node = p_node.next
|
反转链表的函数的实现,遍历方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| def reverse_list(root): if root is None or root.next is None: return root
p_reversed_head = None p_node = root p_prev = None while p_node: p_next = p_node.next if p_next is None: p_reversed_head = p_node p_node.next = p_prev p_prev = p_node p_node = p_next return p_reversed_head
|
测试,先建立Node<1>->Node<2>->···->Node<9>
链表
1 2 3 4 5 6 7 8
| root = build_linked_list(list(range(1, 10))) print("travel list:") travel_list(root)
reverse_root = reverse_list(root)
print("travel reverse list") travel_list(reverse_root)
|
输出结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| travel list: Node<1> Node<2> Node<3> Node<4> Node<5> Node<6> Node<7> Node<8> Node<9> travel reverse list Node<9> Node<8> Node<7> Node<6> Node<5> Node<4> Node<3> Node<2> Node<1>
|
这正是我们所期待的结果。
详细代码如下,包括了清晰的注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| class Node:
def __init__(self, value): self.value = value self.next = None
def reversed_list(head): if head is None: return head if head.next is None: return head
p_prev = None p_node = head p_reversed_head = None
while p_node != None: p_next = p_node.next if p_next is None: p_reversed_head = p_node
p_node.next = p_prev p_prev = p_node p_node = p_next
return p_reversed_head
def test(): values = list(range(1, 11)) print(values)
head = Node(0) p_node = head for v in values: p_node.next = Node(v) p_node = p_node.next
head = head.next reversed_list_head = reversed_list(head) p_node = reversed_list_head while p_node: print(p_node.value, end=' ') p_node = p_node.next
if __name__ == '__main__': test()
|
借助辅助空间
另外还有一种思路,借助Stack辅助空间,从头到尾遍历链表依次保存结点到栈中,然后依次弹出栈的中元素,由于栈的FILO特性,弹出的序列为链表的尾到链表的头。实现如下:
首先需要一个栈结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Stack: def __init__(self): self._s = []
def push(self, item): self._s.append(item)
def pop(self): return self._s.pop()
def empty(self): return len(self._s) == 0
def top(self): if self.empty(): return None return self._s[-1]
|
实现遍历链表的函数
1 2 3 4
| def travel_yield_list(p_node): while p_node: yield p_node p_node = p_node.next
|
实现反转链表函数。有一个细节需要处理:反转后的链表的最后一个结点需要指向None,因为这个结点是没有反转时的头结点,它指向第二个结点。而反转后的第二个结点又指向反转链表后的最后一个结点,于是出现了环。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| def reverse_list_with_stack(root): if root is None: return stack = Stack() for node in travel_yield_list(root): stack.push(node)
root = stack.pop() p_node = root while not stack.empty(): node = stack.pop() p_node.next = node p_node = node p_node.next = None return root
|
测试代码如下
1 2 3 4 5 6 7 8
| root = build_linked_list(list(range(1, 10))) print("travel list:") travel_list(root)
reverse_root = reverse_list_with_stack(root)
print("travel reverse list") travel_list(reverse_root)
|
栈操作可以看作是递归操作的直观版本,根据栈操作的方法,可以把反转链表改为递归操作方法。最后,我们使用一种递归思路来解决反转链表问题。
递归方法
具体思路:
假设现在有三个结点的链表:Node<1>—>Node<2>—>Node<3>。为了反转链表,我们需要先反转结点1、2间的链,但如果反转了,结点2的指针就不是指向Node<3>了,于是出现断链,除非结点2和结点三间的链已经反转了。为了解决断链问题,我们先把反转Node<1>和Node<2>间的链的操作压人栈(执行递归调用,该递归调用的操作就是把Node<2>和Node<3>间的链反转)。待递归调用返回后才把Node<1>和Node<2>间的链反转。这就是递归反转链表的核心,剩下的就是边界处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| def _reversed(p_head): p_node = p_head if p_head.next is None: return p_node, p_head node, p_head = _reversed(p_node.next) node.next = p_node return p_node, p_head
def reverse_list_recursively(p_head): if p_head is None: return p_node, p_head = _reversed(p_head) p_node.next = None return p_head
|
总结
上述采用了三种思路:
第二种思路采用了栈作辅助空间,编写实现更直观,但需要对尾结点做处理,否则会出现环。第一种思路采用三指针,有一定的操作技巧。第三种思路和第二种思路本质是相同的,只是在实现上,思路二更具体也更容易理解,第三种思路把栈的操作过程隐藏在递归过程中。
以上实现源码可参考github