leetcode OJ 树的遍历

本文介绍了使用Python在LeetCode上进行树的遍历,包括前序、中序和后序遍历。重点讨论了非递归的前序遍历方法,通过模拟递归利用栈来实现。同时提到了递归和非递归中序遍历的实现,并指出后序遍历的一种简洁解法,利用Python特性简化操作。

最近发现python写OJ很方便,试了一下树的遍历。

树的结构:

class TreeNode:
     def __init__(self, x):
         self.val = x
         self.left = None
         self.right = None

前序遍历:

递归的方法:(这个递归在自己电脑上测试是没问题的,网友测试也没问题,不知为啥leetcode不通过)

class Solution:
    # @param root, a tree node
    # @return a list of integers
    def preorderTraversal(self, root,result=[]):
        if root==None:
            return result
        else:
            result.append(root.val)
            Solution.preorderTraversal(self,root.left,result)
            Solution.preorderTraversal(self,root.right,result)
        return result

递归其实没什么好讲的,就是套用递归的一个模型:

void func( mode){ 
    if(endCondition)
    { 
        constExpression //基本项 
    } 
    else 
    { 
        accumrateExpreesion //归纳项 
        mode=expression //步进表达式 
        func(mode) / /调用本身,递归 
    } 

} 
具体的按照需求往里套一套,树的遍历的递归还是很好理解的,前序遍历就是按照前序遍历的概念来的,先访问根节点,如果左子树不为空,则前序遍历左子树,否则前序遍历右子树。

下面具体说一说非递归的前序遍历,也就是题目要求的方法,先上代码。

代码1

class Solution:
    # @param root, a tree node
    # @return a list of integers
    def preorderTraversal(self, root):
        stack=[]   #模拟栈的list
        result=[]  #存放结果的list 
        if root!=None:
            stack.append(root)
        while len(stack)!=0:
            length=len(stack)
            root=stack.pop(length-1)  #弹出栈顶元素
            result.append(root.val)   #访问栈顶元素
            if root.right!=None:      #右叶子入栈
                stack.append(root.right)
            if root.left!=None:       #左叶子入栈
                stack.append(root.left)
        return result
python的list比较强大,队列和栈都能模仿,这里我们用来模仿栈。进栈就是简单的append,出栈的话,我们指定pop最后一个元素,因为最后一个元素是最近才加入的,先进后出嘛。

前序遍历的访问顺序是根节点->左叶子->右叶子,此方法的栈其实就是模拟递归的,将根节点先存入栈,然后弹出访问,分别把它的右叶子和左叶子压入栈(因为先左再右)。下次循环弹出的就是左叶子,左叶子就是左子树的根节点,访问它,再把它的右叶子和左叶子压入栈,依次往复,达到了前序遍历左子树的目的。当左子树遍历完了,下一个弹出的就是根节点的右叶子,同理再前序遍历右子树,完成遍历。

代码2

class Solution:
    # @param root, a tree node
    # @return a list of integers
    def preorderTraversal(self, root):
        result=[]
        stack=[]
        while (root!=None or len(stack)!=0):
            if(root!=None):
                result.append(root.val)  #当前节点不为空就访问它
                stack.append(root)       #访问后再入栈
                root=root.left           #访问下一个节点
            else:
                length=len(stack)
                root=stack.pop(length-1)  #弹出栈顶元素
                root=root.right           #通过栈顶元素找到右子树
        return result
与上一种方法,上来就访问根节点(相对的,也包括子树的根节点),然后把其右、左子树都压入栈不同(这之后根节点实际就没有作用,被我们舍弃了),第二种方法与直观思维比较相符,前序遍历无非就是我从根节点开始,有左叶子就往左走,一直找到最左的那一个。由于是先访问根节点,所以我们把路过的根节点都给访问了,但是这里我们却不能把它舍弃了,因为到目前为止,我们并没有处理右叶子,所以我们在访问完根节点之后把它压入栈,用于之后找到对应的右叶子节点。这就是第二种方法的思路了,如果“根节点”还有左叶子就访问根节点,根节点入栈,指向左叶子,如果没有了,就再通过栈中的“根节点”找到右叶子继续。

中序遍历:

递归算法:

class Solution:
    # @param root, a tree node
    # @return a list of integers
    def inorderTraversal(self, root,result=[]):
        if root==None:
            return result
        else:       
            Solution.inorderTraversal(self,root.left,result)
            result.append(root.val)
            Solution.inorderTraversal(self,root.right,result)
        return result


递归算法具体就不说了,还是按照定义来的。

非递归算法:

代码3

class Solution:
    # @param root, a tree node
    # @return a list of integers
    def inorderTraversal(self, root):
        stack=[]
        result=[]
        while (root!=None or len(stack)!=0):
            if root!=None:
                stack.append(root)               #根节点入栈,并不访问
                root=root.left                   #找到左叶子
            else:
                length=len(stack)
                root=stack.pop(length-1)         #根节点出栈
                result.append(root.val)          #访问根节点
                root=root.right                  #找到右叶子
        return result
因为中序遍历的顺序是:左叶子->根节点->右叶子,根节点像代码2那样要求是不丢弃的,所以我们首先想到的就是模仿代码2来操作,思路和代码2基本是一样的,只是将访问根节点移到了弹出根节点之后、找到右叶子之前。(之前是先访问再入栈,现在是先入栈,再出栈在访问)
代码4

class Solution:
    # @param root, a tree node
    # @return a list of integers
    def inorderTraversal(self, root):
        stack=[]
        result=[]
        if root!=None:
            stack.append(root)
            root.be=False   #对节点增加了访问标志位(局部类属性)
        while len(stack)!=0:
            root=stack.pop(len(stack)-1)
            if root.be==True:
                result.append(root.val)    #如果右、左叶子已入栈,则访问
            else:
                if root.right!=None:       #右叶子入栈,右叶子标志位设为False
                    stack.append(root.right)
                    root.right.be=False
                stack.append(root)         #根节点入栈
                if root.left!=None:        #左叶子入栈,左叶子标志位设为False
                    stack.append(root.left)
                    root.left.be=False
                root.be=True               #右、左叶子都已入栈,则根节点的标志位设为True
        return result
再来想一想代码1的方法,前序遍历由于根节点先访问,因此访问之后可以丢掉不管了,但是中序遍历却不可以,因为要先访问左叶子再访问根节点。也就是说在左叶子入栈之前,根节点也要入栈,但是如果根节点出栈再入栈出栈再入栈,那就是个死循环了,所以这里必须引进一个标志位,来确定“根节点”的右、左叶子是不是已经入栈了,如果入了,那就不需要再进栈了,我们可以访问它了(这时候左叶子已经出栈且被访问了)。这里不得不说一下python类的扩展性了,可以定义局部类属性,无疑使这个标志位的添加问题轻松解决(虽然如果不做说明的话,很难理解)。

后序遍历:

递归算法:

class Solution:
# @param root, a tree node
# @return a list of integers
    def postorderTraversal(self, root,result=[]):
        if root==None:
            return result
        else:       
            Solution.postorderTraversal(self,root.left,result)       
            Solution.postorderTraversal(self,root.right,result)
            result.append(root.val)
        return result
非递归算法:

代码5

class Solution:
    # @param root, a tree node
    # @return a list of integers
    def postorderTraversal(self, root):
        result=[]
        stack=[]
        if root!=None:
            stack.append(root)
            root.be=False
        while(len(stack)!=0):
            root=stack.pop(len(stack)-1)
            if root.be==True:
                result.append(root.val)
            else:
                stack.append(root)
                if root.right!=None:
                    stack.append(root.right)
                    root.right.be=False
                if root.left!=None:
                    stack.append(root.left)
                    root.left.be=False
                root.be=True
        return result

代码5的思路和代码4基本是一样的,这里就不详细讲了,类似代码2的思路不太适合后序遍历,还没太想好。

更新:

之前总觉得后序遍历是最难的,还要搞标识位。最近又看到了新的方法,其实是自己语言掌握的不好,所谓后序遍历不过是根节点在左右孩子的后面,而既然我们顺序访问总会先访问根节点,那我们就先把根节点访问了,之后访问孩子的时候,把节点值插到根节点的前面不就可以了,这一点用Python异或是C++太容易实现了。

class Solution:
    # @param root, a tree node
    # @return a list of integers
    def postorderTraversal(self, root):
        stack=[]
        result=[]
        if root!=None:
            stack.append(root)
        while len(stack)!=0:
            length=len(stack)
            tmp_node=stack.pop(length-1)
            result.insert(0,tmp_node.val)
            if tmp_node.left!=None:
                stack.append(tmp_node.left)
            if tmp_node.right!=None:
                stack.append(tmp_node.right)
        return result
这个思路和代码1很相像,访问完根节点就舍弃,其实只有录入节点值那一句不一样,这样看来后序遍历也就不难了。换句话说“左右中”的遍历结果和“中右左”的恰好是相反的,这样就更好理解了。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值