二叉树作为一个基础的数据结构,遍历算法作为一个基础的算法,两者结合当然是经典的组合了。
二叉数的遍历主要有前中后遍历和层次遍历。 前中后属于 DFS,层次遍历属于 BFS。 DFS 和 BFS 都有着自己的应用
下面主要讨论各种遍历的迭代实现方式
前序遍历
这个是遍历是比较简单的,遍历的顺序为根-左-右,可以把这种遍历方式为一个“一人吃饱全家不饿”的人。
拿到一个这个人(根节点),他首先想的是我要出栈;
出栈完了之后,在想到我的左右节点,由于栈先进后出,所以如果我有右节点则入栈,如果有左节点再继续入栈。
等到我的左节点上位(准备出栈)以后,当然继续让它自己出栈,接着在看它的左右节点。
实现如下:
#迭代方法
def preorderTraversal(self,root):
res=[]
stack=[root]
while stack:
node=stack.pop()
res.append(node.val)
if node.right!=None :stack.append(node.right)
if node.left!=None :stack.append(node.left)
return res
中序遍历
中序遍历的顺序为左-根-右,可以看做是一个偏心与左孩子的家长。
这个家长刚上位(入栈)之后,首先要考虑的是它的左孩子,要尽快让它的左孩子上位(入栈)。
左孩子上位后考虑的还是它的左孩子,所以最左侧的边界处的节点都入栈了。
如果没有左孩子,就再考虑自己,让自己出栈,然后考虑右孩子,到了右孩子之后,右孩子考虑的孩子还是它自己的左孩子。
所以中序遍历要比前序遍历要复杂一点,主要出栈以后,下一个需要考虑的是它的直接右节点node,接下来继续考虑的是node的左节点。
def inorderTraversal(self,root):
res=[]
stack=[root]
left=root.left
#左节点全部先入栈——根节点最左侧节点(最左边界处的节点)
while left!=None:
stack.append(left)
left=left.left
#从栈中拿出节点
#1.拿出来的节点查看还有没有右侧节点
#2.有右侧节点的,先把他的左侧边界节点入栈
while stack:
node=stack.pop()
res.append(node.val)
t=node.right
while t:
stack.append(t)
t=t.left
return res
后序遍历
后续遍历的顺序为左-右-根,可以看做是一个无私的家长
这个家长首先考虑的是让他的左孩子和右孩子都入栈(先入栈右孩子,再入栈左孩子)。
当一个节点没有左孩子和右孩子的时候,可以开始第一次出栈,出栈以后当然要看栈中的下一个元素是否有右孩子,如果有,是否输出过了呢?
所以要设置一个变量,来记录上一个输出的是不是它的孩子(不用纠结是左孩子还是右孩子,因为父节点在栈中的位置总是比所有孩子更深),如果上一个输出的是它的孩子,说明它的孩子都已经出栈了,它就可以出栈了。
如果是叶子节点,让然可以直接出栈。
def postorderTraversal(self,root):
if root==None:
return
res=[]
stack=[root]
p=root #标识元素,用来判断节点是否“刚才”已经遍历过
while stack:
top=stack[-1]
#该出栈元素——子节点已经遍历过了 或 为叶子节点
if top.left==p or top.right==p or (top.left==None and top.right==None):
p=stack.pop()
res.append(p.val)
#该入栈元素——先入栈右节点,在入栈当前节点左节点,是并列的
else:
if top.right:
stack.append(top.right)
if top.left:
stack.append(top.left)
return res
小结
入栈:
- 前序:刚开始入栈一个root元素就够了,自己可以进行输出,输出后将右孩子和左孩子入栈
- 中序:得把左侧边界节点开始入栈,并从最左节点开始出栈
- 后序:得把当前节点的右孩子入栈和左孩子入栈,进而把左节点的右孩子和左孩子入栈,尽管开始出栈的元素和中序遍历相同,但栈中元素不同。
出栈:
- 前序:出栈后,就在继续把栈中下一个元素出栈,接着将右、左节点入栈。
- 中序:出栈后,遇到下一个节点,首先看这个节点的左孩子,如果有则将左孩子入栈。
- 后序:出栈后,查看栈中下一个元素是否具有孩子节点,如果有,按照上述入栈书序进行入栈。