2

反转链表(算法16)

 2 years ago
source link: https://allenwind.github.io/blog/2743/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

反转链表(算法16)

经典的反转链表三种解决思路。

问题:实现一个函数,输入一个链表的头结点,反转该链表并输出反转后的链表的头结点。

先实现单链表版本,然后实现双链表版本。

反转链表的其实就是在链表上调整指针的方向,而整体上的反转需要局部上的反转。只要实现了局部上的反转,将局部反转应用与整个链表就能得到反转的链表。局部的反转如下:我们先对链表上的结点依据顺序三三组合。

例如现在有四个结点的链表:

Node<1>->Node<2>->Node<3>->Node<4>

三三组合就有两种:

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函数,通过输入序列建立链表,输出该链表的头结点。

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

反转链表的函数的实现,遍历方法

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
# 判断p_node是否最为最后结点
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>链表

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)

输出结果如下:

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>

这正是我们所期待的结果。

详细代码如下,包括了清晰的注释

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_node是否为最后结点,如果是
# 它就是翻转后链表的头结点
p_reversed_head = p_node

# 翻转p_prev和p_node
p_node.next = p_prev
# p_prev p_node 向后移动一个结点
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特性,弹出的序列为链表的尾到链表的头。实现如下:

首先需要一个栈结构

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]

实现遍历链表的函数

def travel_yield_list(p_node):
while p_node:
yield p_node
p_node = p_node.next

实现反转链表函数。有一个细节需要处理:反转后的链表的最后一个结点需要指向None,因为这个结点是没有反转时的头结点,它指向第二个结点。而反转后的第二个结点又指向反转链表后的最后一个结点,于是出现了环。

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 # 最后一个结点要指向None
return root

测试代码如下

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>间的链反转。这就是递归反转链表的核心,剩下的就是边界处理。

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK