树里的每一个节点有一个根植和一个包含所有子节点的列表。从图的观点来看,树也可视为一个拥有N 个节点
和N-1 条边
的一个有向无环图。
二叉树
是一种更为典型的树状结构。如它名字所描述的那样,二叉树是每个节点最多有两个子树
的树结构,通常子树被称作“左子树”和“右子树”。
树的遍历
前序遍历
前序遍历首先访问根节点,然后遍历左子树,最后遍历右子树。
中序遍历
中序遍历是先遍历左子树,然后访问根节点,然后遍历右子树。
对于二叉搜索树,我们可以通过中序遍历得到一个递增的有序序列。
后序遍历
后序遍历是先遍历左子树,然后遍历右子树,最后访问树的根节点。
当你删除树中的节点时,删除过程将按照后序遍历的顺序进行。 也就是说,当你删除一个节点时,你将首先删除它的左节点和它的右边的节点,然后再删除节点本身。
另外,后序在数学表达中被广泛使用。 编写程序来解析后缀表示法更为容易。
层序遍历
层序遍历就是逐层遍历树结构。
广度优先搜索
是一种广泛运用在树或图这类数据结构中,遍历或搜索的算法。 该算法从一个根节点开始,首先访问节点本身。 然后遍历它的相邻节点,其次遍历它的二级邻节点、三级邻节点,以此类推。
当我们在树中进行广度优先搜索时,我们访问的节点的顺序是按照层序遍历顺序的。
使用一个叫做队列的数据结构来帮助我们做广度优先搜索。
LeetCode | LeetCode-cn | 难度 |
---|---|---|
144.Binary Tree Preorder Traversal | 二叉树的前序遍历 | Medium |
94.Binary Tree Inorder Traversal | 中序遍历二叉树 | Medium |
145.Binary Tree Postorder Traversal | 二叉树的后序遍历 | Hard |
102.Binary Tree Level Order Traversal | 二叉树的层序遍历 | Medium |
运用递归解决问题
“自顶向下” 的解决方案
“自顶向下” 意味着在每个递归层级,我们将首先访问节点来计算一些值,并在递归调用函数时将这些值传递到子节点。 所以 “自顶向下” 的解决方案可以被认为是一种前序遍历(先访问了根结点并根据根结点计算出一些具体的值)。 具体来说,递归函数 top_down(root, params)
的原理是这样的:
return specific value for null node
update the answer if needed // anwer <-- params
left_ans = top_down(root.left, left_params) // left_params <-- root.val, params
right_ans = top_down(root.right, right_params) // right_params <-- root.val, params
return the answer if needed // answer <-- left_ans, right_ans
给定一个二叉树,请寻找它的最大深度。
根节点的深度是1
。 对于每个节点,如果我们知道某节点的深度,那我们将知道它子节点的深度。 因此,在调用递归函数的时候,将节点的深度传递为一个参数,那么所有的节点都知道它们自身的深度。 而对于叶节点,我们可以通过更新深度从而获取最终答案。 这里是递归函数 maximum_depth(root, depth)
的伪代码:
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
C++ 代码模板
int answer; // don't forget to initialize answer before call maximum_depth
void maximum_depth(TreeNode* root, int depth) {
if (!root) {
return;
}
if (!root->left && !root->right) {
answer = max(answer, depth);
}
maximum_depth(root->left, depth + 1);
maximum_depth(root->right, depth + 1);
}
“自底向上” 的解决方案
在每个递归层次上,我们首先对所有子节点递归地调用函数,然后根据返回值和根节点本身的值得到答案。 这个过程可以看作是后序遍历的一种。 通常, “自底向上” 的递归函数 bottom_up(root)
为如下所示:
return specific value for null node
left_ans = bottom_up(root.left) // call function recursively for left child
right_ans = bottom_up(root.right) // call function recursively for right child
return answers // answer <-- left_ans, right_ans, root.val
如果我们知道一个根节点,以其左子节点为根的最大深度为l
和以其右子节点为根的最大深度为r
,我们是否可以回答前面的问题? 当然可以,我们可以选择它们之间的最大值,再加上1来获得根节点所在的子树的最大深度。 那就是 x = max(l,r)+ 1
。
这意味着对于每一个节点来说,我们都可以在解决它子节点的问题之后得到答案。 因此,我们可以使用“自底向上“的方法。下面是递归函数 maximum_depth(root)
的伪代码:
return 0 if root is null // return 0 for null node
left_depth = maximum_depth(root.left)
right_depth = maximum_depth(root.right)
return max(left_depth, right_depth) + 1 // return depth of the subtree rooted at root
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
}
LeetCode | LeetCode-cn | 难度 |
---|---|---|
104.Maximum Depth of Binary Tree | 二叉树最大深度 | Easy |
101.Symmetric Tree | 对称二叉树 | Easy |
112.Path Sum | 路径总和 | Easy |
总结
当遇到树问题时,请先思考一下两个问题:
- 你能确定一些参数,从该节点自身解决出发寻找答案吗?
- 你可以使用这些参数和节点本身的值来决定什么应该是传递给它子节点的参数吗?
如果答案都是肯定的,那么请尝试使用 “自顶向下
” 的递归来解决此问题。 (信息从上往下传递)
或者你可以这样思考:对于树中的任意一个节点,如果你知道它子节点的答案,你能计算出该节点的答案吗? 如果答案是肯定的,那么 “自底向上
” 的递归可能是一个不错的解决方法。 (信息从下往上传递)
LeetCode | LeetCode-cn | 难度 | 题解 |
---|---|---|---|
106.Construct Binary Tree from Inorder and Postorder Traversal | 从中序与后序遍历序列构造二叉树 | Medium | |
105.Construct Binary Tree from Preorder and Inorder Traversal | 从前序与中序遍历序列构造二叉树 | Medium | |
116.Populating Next Right Pointers in Each Node | 填充每个节点的下一个右侧节点指针 | Medium | |
117. Populating Next Right Pointers in Each Node II | 填充每个节点的下一个右侧节点指针 II | Medium | |
236.Lowest Common Ancestor of a Binary Tree | 二叉树的最近公共祖先 | Medium | |
297.Serialize and Deserialize Binary Tree | 二叉树的序列化与反序列化 | Hard |