二叉树的遍历

前序遍历

先根节点,再左边子树,然后右边子树

递归遍历:

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def preorderTraversal(self, root: TreeNode) -> List[int]:
        def helper(node):
            if node == None:
                return 
            result.append(node.val)
            helper(node.left)
            helper(node.right)

        result = []
        helper(root)
        return result

迭代遍历:

class Solution:
    def preorderTraversal(self, root: TreeNode) -> List[int]:
        ans = []
        if not root:
            return ans
        stack = [root]
        while stack:
            node = stack.pop() # 这个弹出很重要,将已经遍历的跟节点剔除,这样就不需要再进行是否已经遍历的判断
            ans.append(node.val)
            if node.right: # 这里先加入根节点的右边节点,再加入左边节点。这样取的时候先取左边子节点
                stack.append(node.right)
            if node.left:
                stack.append(node.left)
        return ans

中序遍历

先是左节点,再是中间根节点,最后右边子节点。

递归解法:

class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        if root == None:
            return []
        res = []
        def helper(node):
            if node== None: # 停止递归的条件
                return 
            helper(node.left)
            res.append(node.val)
            helper(node.right)
        
        helper(root)
        return res

更简洁的递归解法:

def inorder(r):
            return inorder(r.left) + [r.val] + inorder(r.right) if r else []

迭代解法:

class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        if root == None:
            return []
        res = []
        stack = []
        while stack or root != None:
            while root: # 这个while循环一直遍历到二叉树最左边的叶节点
                stack.append(root)
                root = root.left 
            root = stack.pop()
            res.append(root.val)
            root = root.right      
        return res

后序遍历

递归解法,根节点最后遍历

class Solution:
    def postorderTraversal(self, root: TreeNode) -> List[int]:
        def postorder(root: TreeNode):
            if not root:
                return
            postorder(root.left)
            postorder(root.right)
            res.append(root.val)
        
        res = list()
        postorder(root)
        return res

迭代解法:

class Solution:
    def postorderTraversal(self, root: TreeNode) -> List[int]:
        if not root:
            return list()
        
        res = list()
        stack = list()
        prev = None

        while root or stack:
            while root:
                stack.append(root)
                root = root.left
            root = stack.pop()
            if not root.right or root.right == prev:
                res.append(root.val)
                prev = root
                root = None
            else:
                stack.append(root)
                root = root.right
        
        return res

运用递归解决树的问题

自顶向下

主题思想:

  1. 能确定一些参数,从该节点自身解决出发寻找答案吗?
  2. 可以使用这些参数和节点本身的值来决定什么应该是传递给它子节点的参数吗?

例如对于求一个二叉树的最大深度的问题,其伪代码为:

return if root is null
if root is a leaf node:
    answer = max(answer, depth)         // update the answer if needed
maximum_depth(root.left, depth + 1)      // call the function recursively for left child
maximum_depth(root.right, depth + 1)        // call the function recursively for right child

python实现为:

class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        answer = 0

        def helper(root , depth):
            nonlocal answer # 这里一定要用nonlocal,不能用global
            
            if root == None :
                return 
            if root.left == None and root.right == None :               
                answer = max(answer , depth) 

            helper(root.left , depth + 1)
            helper(root.right , depth + 1)

        helper(root , 1)
        return answer

自底向上

在每个递归层次上,首先对所有子节点递归的调用函数,然后根据返回值和根节点本身的值得到答案。可以看做是后序遍历的一种。递归函数bottom_up(root)

1. return specific value for null node
2. left_ans = bottom_up(root.left)          // call function recursively for left child
3. right_ans = bottom_up(root.right)        // call function recursively for right child
4. return answers                           // answer <-- left_ans, right_ans, root.val

C++实现

int maximum_depth(TreeNode* root) {
    if (!root) {
        return 0;                                 // return 0 for null node
    }
    int left_depth = maximum_depth(root->left);
    int right_depth = maximum_depth(root->right);
    return max(left_depth, right_depth) + 1;      // return depth of the subtree rooted at root
}

对称二叉树

判断一棵树是否是对称的(镜像的)

python实现递归:

class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        
        if not root: 
            return True

        return self.isSymmetricTree(root.left, root.right)

    def isSymmetricTree(self, left, right):
        if left is None and right is None: return True   #同时为空
        if left is None or right is None : return False  #一个为空 
        if left.val != right.val : return False          # 值不相等 !!
        return self.isSymmetricTree(left.left, right.right) and self.isSymmetricTree(left.right, right.left)

python实现迭代:

比较一棵二叉树是否是对称的等同于比较以同样根节点一模一样的树是否是镜像的。初始化时我们把根节点入队两次。每次提取两个结点并比较它们的值(队列中每两个连续的结点应该是相等的,而且它们的子树互为镜像),然后将两个结点的左右子结点按相反的顺序插入队列中。当队列为空时,或者我们检测到树不对称(即从队列中取出两个不相等的连续结点)时,该算法结束。

class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        def check(u , v):
            q = Deque()
            q.append(u)
            q.append(v)
            while q:
                u = q.pop()
                v = q.pop()
                if u == None and v == None:
                    continue
                if (u == None or v == None):
                    return False
                if u.val != v.val :
                    return False
                q.append(u.left)
                q.append(v.right)

                q.append(u.right)
                q.append(v.left)
            
            return True

        return check(root,root)

路径总和

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。

叶子节点 是指没有子节点的节点。

python递归解法:

class Solution:
    def hasPathSum(self, root: TreeNode, targetSum: int) -> bool:
        if root == None and targetSum == 0 :
            return False
        if root == None :
            return False
        if root.left == None and root.right == None:
            if root.val == targetSum :
                return True
        return self.hasPathSum(root.left,targetSum-root.val) or self.hasPathSum(root.right,targetSum-root.val)

以上是运用自顶向下的思想。

最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。

分治法解决该题

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        # 分治法的典型题

        if root == None or root == p or root == q:
            return root
        left = self.lowestCommonAncestor(root.left,p,q)
        right = self.lowestCommonAncestor(root.right,p,q)

        if left!= None and right!= None : # 左边有值,右边也有值,说明左子树和右子树都存在某一个节点
            return root
        if left != None : # 右子树返回空,说明p,q都在左子树,所以结果就从left中选
            return left
        if right != None: # 左子树返回空,说明p,q都在右子树,所以结果就从right中选
            return right
        return None 

总结一下二叉树遍历的诀窍:

  1. 先搞清楚当前root节点该做什么,然后根据函数定义递归调用子节点。递归调用会让子节点做相同的事情。
  2. 写递归算法关键要明确函数的定义,然后利用这个定义推到最终结果。一定一定不要陷入递归的细节。从已经完成的任务开始推。
  3. 二叉树的一个难点:怎么把题目细化成每一个节点需要做的事情。

例如:在树中有多少个节点的问题时:

def count(root):
    if not root: return 0
    return 1 + count(root.left) + count(root.right)

226.反转二叉树

class Solution:
    def invertTree(self, root: TreeNode) -> TreeNode:
        if not root : return None
        
        tmp = root.left
        root.left = root.right
        root.right = tmp

        self.invertTree(root.left)
        self.invertTree(root.right)

        return root

116. 填充二叉树节点的右侧指针

class Solution:
    def connect(self, root: 'Node') -> 'Node':        
        if not root: return None
        self.helper(root.left,root.right)
        return root
    
    def helper(self,node1,node2): # 将node1和node2两个相邻节点进行连接
        if node1 == None or node2 == None :
            return
        # 将传入的两个节点连接
        node1.next = node2
        # 连接相同父节点的两个子节点
        self.helper(node1.left,node1.right)
        self.helper(node2.left,node2.right)

        # 连接跨越父节点的两个子节点
        self.helper(node1.right,node2.left)

剑指offer28. 对称二叉树

请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
示例 1:
输入:root = [1,2,2,3,4,4,3]
输出:true
示例 2:
输入:root = [1,2,2,null,3,null,3]
输出:false

这一题和上面一题有相似的地方,如果一开始递归函数的定义并不是我们想要的,比如这一题:isSymmetrics函数的定义是判断一棵以root为节点的二叉树,它是不是对称的。不能陷入误区:我们直接递归将root.left和root.right行不行?答案是不行,因为这样我们就只是在判断以root.left和root.right为根节点的两棵树它是不是对称的,显然不符合我们想要实现的目标。所以这时候我们就要加一个helper的递归函数来帮助我们判断:helper的主要功能是判断以L和R为根节点的树,他俩是不是对称的。

class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        if not root :
            return True
        
        def helper(L,R): # 判断以 L 和 R 为节点的二叉树是不是镜像对称
            if not L and not R : 
                return True
            if not L or not R :
                return False
            if L.val != R.val :
                return False
            if L.val == R.val :
                return helper(L.left,R.right) and helper(L.right,R.left)
        return helper(root.left,root.right)

总结:在完成二叉树的题目的时候,很多中等难度的题目要稍微改变一下递归的定义,如果这一个方法行不通,我们就可以换个思路。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,657评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,889评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,057评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,509评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,562评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,443评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,251评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,129评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,561评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,779评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,902评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,621评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,220评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,838评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,971评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,025评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,843评论 2 354

推荐阅读更多精彩内容