树3,用循环实现树的三种遍历

参考:
[LeetCode] Binary Tree Preorder Traversal 二叉树的先序遍历
[LeetCode] Binary Tree Inorder Traversal 二叉树的中序遍历
[LeetCode] Binary Tree Postorder Traversal 二叉树的后序遍历

一、前序遍历

1.1、方法1

代码思路

前序遍历,对每个节点的访问顺序是根,左,右。

因此,在每个循环中, 弹出一个节点后(作为根节点的话),输出根节点的值,然后为了按照顺序访问左子节点,右子节点,应该先把右子节点压入栈中, 再把左子节点压入栈中。

因为循环时,栈中已经有了节点, 所以在循环开始前,应该把根节点 root压入栈中。

上面这么简单的循环操作能够实现前序遍历??事实证明,是可以的。因为每个右子节点,都是先于左子节点压入栈中,保证了每次都会先访问左子节点。
大树的右子节点最先入栈,因此大树的右子节点也是要等到左子树访问完毕以后才会进行访问的。
所以这么遍历是合理的。

实现代码:

注意

  • 1、下面用来栈,所以需要熟记对栈用列表实现方法,基本操作 (压入,弹出)对应的代码。
  • 2、其中对节点属性的索引,直接用node.val就行了,不需要加括号,方法才需要加括号。
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def preorderTraversal(self, root):
        """
        :type root: TreeNode
        :rtype: List[int]
        """
        if root:
            #将根节点压住栈中
            s = []
            s.append(root)
            #开始循环
            tree_list = []
            while s:
                curr_node = s.pop()
                tree_list.append(curr_node.val)
                if curr_node.right:
                    s.append(curr_node.right)
                if curr_node.left:
                    s.append(curr_node.left)
                #这个方法,不需要再对节点没有左右子节点的情况进行操作了
                #如果节点没有左右子节点,也是可以将循环进行下去的
                
        #这里没有说,如果为空,需要返回什么,是空列表[]还是别的东西,需要跟面试官讨论一下
        #这个题里的,是当做空列表
        else:
            return []

        return tree_list

1.2、方法2

上述方法,每个节点都要按照顺序入栈,以保证出栈顺序跟前序遍历相符合,因此需要仔细思考入栈出栈操作。而方法二,不需要 仔细思考这个问题。

方法2引用了一个辅助接点p。
方法二的重要思想:

  • 1、p值等于进行遍历时,每一步的访问的节点
  • 2、栈用来存入,正常遍历顺序时,等会还会用到的节点。

下面对这两个思想进行讲解一下。
讲解一:前序遍历,第一步,p等于10,下一步p会等于6,然后等于4,等于4的左子节点None,再等于4的右子节点None,然后会等于6的左子节点8.。。可见,p的值就是正常的索引顺序。

讲解二:访问10,因为10有左子节点,所以 访问完10的左子节点,等会还要靠10索引10的右子节点,所以需要把10压入栈中;;同理,在访问6时,也要把6压入栈中。。。。访问完4的左子节点发现为空以后,接下来会回到4,用4来访问4的右子节点,于是这时候会弹出4。

而什么时候输出值,我们根据前序遍历,应该在第一次访问到根节点的时候,就输出值。

真正写代码的时候,手动分析一下,在p不为空时,代码应该怎么写,在p为空时,代码应该怎么写。

例如:
p=10,不为空,那么应该输出10的值,将10入栈,接着索引左子节点。

s.append(p)
tree_list.append(p.val)
p = p.left

p作为4的左子节点,为空,那么应该返回其父节点(栈顶节点),并索引父节点的右子节点。

p_father = s.pop()
p = p_father.right

一定要注意while的循环条件是,栈不为空或p不为空,有一个满足即可。s和p中间的是or,一定是or!

代码实现:

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def preorderTraversal(self, root):
        """
        :type root: TreeNode
        :rtype: List[int]
        """
        if root:
            p = root
            s = []

            tree_list = []
            while s or p:

                #若p不为空,则入栈,输出p的值,并开始访问p的左子节点
                if p:
                    s.append(p)
                    tree_list.append(p.val)
                    p = p.left
                
                #若p为空,比如4的左子节点为空,则说明这条路径访问到头了,应该访问其父节点的右子节点了
                #于是弹出栈顶元素,访问右子节点
                else:
                    p_father = s.pop()
                    p = p_father.right
            
            return tree_list
        else:
            return []

二、中序遍历

中序遍历,传统的用栈的方法,见参考。比较麻烦,这里不写,就只写上面的方法2的情况。

基本什么都没有变,只不过在手动的进行索引的时候,在p为空或者不为空时,发生变化。因为中序遍历,顺序是左,根,右。
所以一定不能在刚碰到根节点时就输出值。应该在弹出栈点时,输出该节点的值。若是记不住,就手动索引,然后分析一下:

例如:
p=10,不为空,那么应该将10入栈,接着索引左子节点。

s.append(p)
p = p.left

p作为4的左子节点,为空,那么应该返回其父节点(在栈顶,可以认为是根节点),作为根节点应该输出值,然后索引根节点的右子节点。

p_father = s.pop()
tree_list.append(p_father.val)
p = p_father.right

代码实现:

class Solution:
    def inorderTraversal(self, root):
        """
        :type root: TreeNode
        :rtype: List[int]
        """
        if root:
            s = []
            tree_list = []
            p  = root
            while s or p:
                #若p不为空,则继续对p进行访问,即访问p的左子节点
                #并将等会还会用到的根节点(在此处就是这个小树的根节点)压入栈中
                if p:
                    s.append(p)
                    p = p.left
                #若p为空,父节点的左子节点为空,那么按照中序遍历,就要返回父节点的值
                #然后对父节点的右子节点进行访问
                else:
                    p_father = s.pop()
                    tree_list.append(p_father.val)
                    p = p_father.right
                    
            return tree_list
        else:
            return []

三、后序遍历

注意,这里直接用上面的方法2的思路,是不行的。后序遍历是左,右,根。

3.1、方法1

由于后序遍历的顺序是左-右-根,而先序遍历的顺序是根-左-右,二者其实还是很相近的,我们可以先在先序遍历的方法上做些小改动,使其遍历顺序变为根-右-左,然后把输出的结果反转一下,即可得到左-右-根。

具体实现方法:

  • 1、先在先序遍历做改动,使之遍历顺序为根-右-左,那么只需要改变左右节点入栈顺序。
  • 2、对于结果上的翻转,可以最后翻转,也可以一开始,每次都在结果list的开头插入节点。用insert(0,node.val)。

基于 前序遍历的方法1实现代码,做的改动

注意,这里一定要判断左右子节点是否为空,不为空才能入栈

代码实现:

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def postorderTraversal(self, root):
        """
        :type root: TreeNode
        :rtype: List[int]
        """
        if root:
            s = []
            s.append(root)
            tree_list = []
            while s:
                p = s.pop()
                tree_list.insert(0,p.val)
                #注意,这里一定要判断左右子节点是否为空,不为空才能入栈
                if p.left:
                    s.append(p.left)
                if p.right:
                    s.append(p.right)
            return tree_list
        else:
            return []

3.2、方法2

根据上述,基于前序遍历的方法2,只需把两个if中的left和right调换一下,然后把append改成insert(0,node.val)即可。

class Solution:
    def postorderTraversal(self, root):
        """
        :type root: TreeNode
        :rtype: List[int]
        """
        if root:
            p = root
            s = []

            tree_list = []
            while s or p:

                if p:
                    s.append(p)
                    tree_list.insert(0,p.val)
                    p = p.right
                else:
                    p_father = s.pop()
                    p = p_father.left
            
            return tree_list
        else:
            return []
    原文作者:小碧小琳
    原文地址: https://www.jianshu.com/p/27299eabafb5
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞