算法与数据结构 之 树

image.png

一、概念

树是一种非线性结构,就像真实的树倒挂,具体定义:树是包含n(n>=0)个节点的有穷集,其中:
1、每个元素称为节点
2、有一个特定的节点被称为跟节点或树根
3、除根节点之外的其余元素被分为m(m>=0)个互不相交的集合T1,T2,T3...... Tm-1, 其中每一个集合Ti(1 <=i<=m)本身也是一颗树,被称作原树的子树

首先介绍一下二叉树的结构(如下图):


image.png

A为根节点(root), D为H,I的父节点(Parent Node), H,I为D的子节点(Child Node), H和I之间互为兄弟节点(Sliblings)。树的深度指的是树中有多少个level,图中,树的深度为4。

二、二叉树的存储

二叉树的存储分为两种:
1、基于指针或者引用的二叉链表存储法


链式存储法

2、基于数组的顺序存储法,其中完全二叉树使用数组存储是最节省内存的一种方式


顺序存储法

三、遍历方式

广度优先搜索:也成为层次遍历。
深度优先搜索::分为前序,中序,后序遍历。

image.png

前序遍历:对于树中的任意结点来说,先打印这个结点,然后再打印它的左子树,最后打印右子树。
前序遍历

中序遍历:对于树中的任意结点来说,先打印它的左子树,然后再打印这个结点,最后打印右子树。

中序遍历

后续遍历:对于树中的任意结点来说,先打印右子树,然后再打印它的左子树,最后打印结点本身。

后续遍历

总结一下,就是前中后指的都是根的位置在前中后。

四、二叉树的代码定义

对于树的代码定义:说明,每个节点都是一个类(class), 每个类中包括了节点的值,和节点的左节点(left)和右节点(right)。

# 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

看一个具体例子(如图):[1,null,2,3]

image.png

二叉树的结构打印:
TreeNode{val: 1, left: None, right: TreeNode{val: 2, left: TreeNode{val: 3, left: None, right: None}, right: None}}

五、常见面试题:

下面看具体的问题:
关于树的算法一般有两种,递归(Recursion)和迭代(Iteration),下面我会分别列举几种常见的关于树的例子。

问题一:二叉树的前序遍历

例题1:https://leetcode-cn.com/problems/binary-tree-preorder-traversal/ 144. 二叉树的前序遍历

思路:

思路一: 迭代(Iteration), T:O(n), n为二叉树的节点数; S:O(n), n为栈的开销
思路二: 递归(Recursion),T:O(n), n为二叉树的节点数; S:平均情况下为 O(logn),最坏情况下树呈现链状,为 O(n)。

代码实现:
# 迭代
class Solution:
    def preorderTraversal(self, root: TreeNode) -> List[int]:
        re, stack = [], [root]
        while stack:
            node = stack.pop()
            if node:
                re.append(node.val)
                stack.append(node.right)
                stack.append(node.left)
        return re

# 递归:
class Solution:
    def preorderTraversal(self, root: TreeNode) -> List[int]:
        def dfs(root, res):
            if root:   
                res.append(root.val)
                dfs(root.left, res)
                dfs(root.right, res)
        res = []
        dfs(root, res)
        return res

问题二、求二叉树的中序遍历

例题2: https://leetcode-cn.com/problems/binary-tree-inorder-traversal/ 94. 二叉树的中序遍历

给定一个二叉树的根节点 root ,返回它的 中序 遍历。

示例 1:


示例1

输入:root = [1,null,2,3]
输出:[1,3,2]

示例 2:

输入:root = []
输出:[]

示例 3:

输入:root = [1]
输出:[1]

示例 4:


示例4

输入:root = [1,2]
输出:[2,1]

示例 5:


示例 5

输入:root = [1,null,2]
输出:[1,2]

思路:
递归和迭代。时间复杂度和空间复杂度分析和前序遍历一样。
思路一:递归(Recursion),左根右, 时间复杂度为O(n),其中n为树的节点数 , 空间复杂度为O(n),其中n为树的深度。

思路二:迭代(Iteration),遍历root, stack中依次装入root的left的节点; stack中为空时,为迭代终止条件;stack中弹出结点,将值加入返回的结果集中。开始遍历右节点。
所以,最先加入根,然后左节点;弹出时变成,左节点,根。然后压入右节点,弹出右节点;

时间复杂度为O(n),其中n为树的节点数 , 空间复杂度为O(n),其中n为树的深度。

代码实现:
# 递归
class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        def dfs(root, res):
            if root:
                dfs(root.left, res)
                res.append(root.val)
                dfs(root.right, res)
        res = []
        dfs(root, res)
        return res

# 迭代
class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        res, stack = [], []
        while True:
            while root:
                stack.append(root)
                root = root.left
            if not stack:
                return res
            node = stack.pop()
            res.append(node.val)
            root = node.right

问题三、求二叉树的最大深度

例题3: https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/ 104. 二叉树的最大深度

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

示例:
给定二叉树 [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7
思路:

按照总结,常见的思路有两种:递归和迭代。
思路1:递归,二叉树的最大深度,等于左右子节点中的较大者 + 1(1为根节点自己)
思路2:迭代,借助辅助栈stack,借助辅助栈,每次遍历左右节点完后,压入栈中,清空栈中之前的元素,计数+1。
T:O(n), n为节点的个数, S:O(n),n为树的深度

第1次迭代后的stack中为:
[TreeNode{val: 3, left: TreeNode{val: 9, left: None, right: None}, right: TreeNode{val: 20, left: TreeNode{val: 15, left: None, right: None}, right: TreeNode{val: 7, left: None, right: None}}}]
此时是把整个root都压入栈中,深度计数+1,根节点本身就是一层。

第2次迭代后的stack中为:
[TreeNode{val: 9, left: None, right: None}, TreeNode{val: 20, left: TreeNode{val: 15, left: None, right: None}, right: TreeNode{val: 7, left: None, right: None}}]
此时把根结点中的右节点的左右节点压入了栈中,深度计数+1。

第3次迭代后的stack中为:
[TreeNode{val: 15, left: None, right: None}, TreeNode{val: 7, left: None, right: None}]
此时把上一次的节点中的右节点的左右节点压入栈中,深度计数+1。

第4次迭代后的stack中为:
[],计数不变。
返回计数结果,即树的最大深度。

代码实现:
# 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 maxDepth(self, root: TreeNode) -> int:
        return 1 + max(self.maxDepth(root.left), self.maxDepth(root.right)) if root else 0

# 迭代:
class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        depth = 0
        stack = [root] if root else []
        while stack:
            depth += 1
            queue = []
            for node in stack:
                if node.left:
                    queue.append(node.left)
                if node.right:
                    queue.append(node.right)
            stack = queue
        return depth

问题四、N叉树的前序遍历

image.png

例题4: 589. N叉树的前序遍历 https://leetcode-cn.com/problems/n-ary-tree-preorder-traversal/

思路:

思路一:递归,相对简单。先装入根的值,然后依次装入children节点中的值。
T:O(n), n为节点的个数; S:O(n), n为树的最大深度

思路二:迭代, 将root装入stack中,循环stack,先弹出栈,结果集中装入当前节点的值,栈中反方向装入children的节点。

注意:借助辅助栈stack,每次往stack中追加children的倒序值(根据LIFO,后进先出原则),注意用extend,不用append,extend是直接拼接内容的值,而append会拼接整块内容,包括值和[],例如append会把[1,2,3]直接追加,而extend只追加1,2,3的值

T:O(n), n为节点的个数; S:O(n), n为树的最大深度

代码实现:
# 迭代:
class Solution:
    def preorder(self, root: 'Node') -> List[int]:
        if not root:
            return []
        res = [root.val]
        for child in root.children:
            res += self.preorder(child)
        return res

# 递归:
class Solution:
    def preorder(self, root: 'Node') -> List[int]:
        if not root:
            return []
        res, stack = [], [root]
        while stack:
            node = stack.pop()
            if node:
                res.append(node.val)
                stack.extend(node.children[::-1])
        return res

问题5: 105. 从前序与中序遍历序列构造二叉树 https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/

根据一棵树的前序遍历与中序遍历构造二叉树。

注意:
你可以假设树中没有重复的元素。

例如,给出

前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:

    3
   / \
  9  20
    /  \
   15   7

思路:
递归,前序中的第一个元素为根节点的值,获取它,可以构建树节点的value值,然后递归遍历树的左节点和右节点

时间复杂度:O(n),n为节点的个数
空间复杂度:O(n),n为树的深度

代码实现:

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
            if inorder:
                ind = inorder.index(preorder.pop(0))
                root = TreeNode(inorder[ind])
                root.left = self.buildTree(preorder, inorder[0:ind])
                root.right = self.buildTree(preorder, inorder[ind+1:])
                return root

问题6: 二叉树的最近公共祖先https://leetcode-cn.com/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/

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

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

例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]

示例 1:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。

思路:
二叉树的最近公共祖先有两种情况,1,p和q在根节点的左右两侧。 2、p或q其中一个为根节点。递归左右子树查找p和q,如果左节点为空,则返回右节点,如果右节点为空,则返回左节点。说明p和q其中一个为根节点。 如果都没找到,说明p和q在根节点的两侧,返回root根节点。

时间复杂度:O(n),n为节点的个数
空间复杂度:O(n),n为树的深度

代码实现:

class Solution:
    def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
        if not root or root == p or root == q:
            return root
        left = self.lowestCommonAncestor(root.left, p, q)
        right = self.lowestCommonAncestor(root.right, p, q)
        if not left:
            return right
        if not right:
            return left
        return root

撰写记录
2020.12.11-06:11:01-第一次撰写
2020.12.13-08:20:19-第二次撰写
2020.12.13-11:38:00-第三次撰写
2020.12.21-06:26:10-第四次撰写
2021.01.30-20:22:00-第五次撰写
2021.02.16-08:28:00-第六次撰写

你可能感兴趣的:(算法与数据结构 之 树)