二叉树神级遍历方法
Posted 代码狂徒
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二叉树神级遍历方法相关的知识,希望对你有一定的参考价值。
二叉树遍历的方法有很多,例如:前序遍历,中序遍历,后序遍历。但是时间复杂度却只能做到 O(N), 空间时间复杂度 O(N)。今天要介绍是 morris 遍历,时间复杂度可以做到 O(N),空间时间复杂度可以做到 O(1)。
一.Morris 遍历
1. 该算法是如何做到空间时间复杂度为 O(1)
在我们之前学习过的算法中,遍历二叉树的算法都会使用到栈空间,假如不递归,也会使用栈结构,所以空间时间复杂度是 O(N)无法避免。而 Morris 遍历则是使用当前节点的左节点的最右节点指向当前节点,来保证可以回到上层,所以不用使用栈空间结构。
2. 算法流程
-
从根节点开始遍历 -
首先看左节点是否为空 -
如果为空 -
否则直接遍历右节点 -
如果左节点不为空,则找到左节点的最右节点 -
如果最右节点指向的是当前节点,则将其置为空 -
如果最右节点指向的是空,那么就将最右节点指向当前节点,直接进入下一次循环,从步骤 1 开始 -
向右遍历
3. Morris 遍历是如何遍历完下层,然后回到上一层
在之前说的算法流程中提过,将当前左节点的最右指针指向,这一次是第一次遍历到这个位置,当遍历到下层的时候,没有最左节点的时候,就会顺着我们定义好的右节点指向上一层节点移动,这个时候就回到了上层,然后往右遍历,会遍历到最右节点第二次。所以该节点会被遍历到两次。
二. 代码实现
morris 遍历
def morris(node: TreeNode):
cur = node
while cur is not None:
mostRight: TreeNode = cur.left
# 没有左节点
if mostRight is not None:
# 找到最右左节点
while mostRight.right is not None and mostRight.right is not cur:
mostRight = mostRight.right
if mostRight.right is None:
# 1
mostRight.right = cur
cur = cur.left
continue
else:
# 2
mostRight.right = None
else
# 3
pass
cur = cur.right
和我说算法流程一样,我就不解释了。解释一下遍历的位置,1 位置遍历到是每一个节点的启始节点,可以说是父母节点,2 位置则是第二次遍历的位置,此时子节点已经遍历完毕,该位置为后根节点 ,3 位置可以说是右节点的遍历。
根据 morris 遍历,我们可以加工出前序遍历,中序遍历,后序遍历。
前序遍历
def preMorris(node: TreeNode, f):
cur = node
while cur is not None:
mostRight: TreeNode = cur.left
# 没有左节点
if mostRight is not None:
# 找到最右左节点
while mostRight.right is not None and mostRight.right is not cur:
mostRight = mostRight.right
# 指回cur节点
if mostRight.right is None:
mostRight.right = cur
f(cur.val)
cur = cur.left
continue
# 如果当前最右节点指向cur 后打印
else:
mostRight.right = None
else:
f(cur.val)
cur = cur.right
根据我一开始的解释,应该能看的很明白,先遍历先根节点(包括左节点),后遍历右节点。
中序遍历
def orderMorris(node: TreeNode, f):
cur = node
while cur is not None:
mostRight: TreeNode = cur.left
# 没有左节点
if mostRight is not None:
# 找到最右左节点
while mostRight.right is not None and mostRight.right is not cur:
mostRight = mostRight.right
# 指回cur节点
if mostRight.right is None:
mostRight.right = cur
cur = cur.left
continue
# 如果当前最右节点指向cur 后打印
else:
mostRight.right = None
else:
pass
f(cur.val)
cur = cur.right
中序遍历可以直接使用最外面一层,最外一层的遍历方式就是,查找到最左节点后,这个时候才会走到中序遍历的过程,这个时候遍历的就是最左节点,然后最左节点的右节点指向上一层节点(父母节点),也就是根节点,然后根节点,首先会将 mostRight.right
置为空,然后执行 cur = cur.right
往右遍历。此时遍历到的节点就是左根右,符合中序遍历,然后递推到整个过程就会将整棵树遍历完。
后序遍历
def reverseEdge(mostRight) -> TreeNode:
pre = None
while mostRight is not None:
next = mostRight.right
mostRight.right = pre
pre = mostRight
mostRight = next
return pre
def printEdge(mostRight, f):
head: TreeNode = reverseEdge(mostRight)
while head is not None:
f(head.val)
reverseEdge(head)
def postmorris(node: TreeNode, f):
cur = node
while cur is not None:
mostRight: TreeNode = cur.left
# 没有左节点
if mostRight is not None:
# 找到最右左节点
while mostRight.right is not None and mostRight.right is not cur:
mostRight = mostRight.right
# 指回cur节点
if mostRight.right is None:
# 1
mostRight.right = cur
cur = cur.left
continue
# 如果当前最右节点指向cur 后打印
else:
# 2
printEdge(mostRight, f)
mostRight.right = None
cur = cur.right
printEdge(node, f)
后序遍历会稍微复杂点,其只用用到了第二次遍历,这个时候已经遍历到最左位置,过程为翻转最右链表,然后遍历,翻转回来,那么得到的结果就是左节点先被遍历到,然后是右节点,然后遍历到上一层后的节点就是根节点,那么顺序就是左右根。翻转第一次为了得到后序遍历顺序,翻转第二次,防止破坏原来的数据结构。遍历到最后只有 head
节点的右链表,因为 head
节点没有上层节点,所以 head
永远都不会进入 1 和 2 。所以最后我们需要手动遍历 head
节点。
三.总结
morris 遍历作为一种不错的优化手段,其后序遍历较为复杂外,前序遍历和中序遍历都很简单,在这两种遍历的情况下,可以优先选择这种遍历。后序遍历的话,对空间复杂度有要求的话,则可以使用。当然其实都可以在日常使用这种遍历方式去遍历二叉树,因为也都不是很复杂。
以上是关于二叉树神级遍历方法的主要内容,如果未能解决你的问题,请参考以下文章
算法: 二叉树级顺序遍历 (3种解法)102. Binary Tree Level Order Traversal
NC41 最长无重复子数组/NC133链表的奇偶重排/NC116把数字翻译成字符串/NC135 股票交易的最大收益/NC126换钱的最少货币数/NC45实现二叉树先序,中序和后序遍历(递归)(代码片段