Binary Tree[二叉树]

本文深入探讨树结构的基本概念,包括二叉树及其不同类型的遍历方式:前序、中序、后序和层序遍历。同时,介绍了如何通过递归方法解决树的相关问题,包括自顶向下和自底向上的解决方案。此外,还提供了LeetCode上与树相关的经典题目示例。

树里的每一个节点有一个植和一个包含所有子节点的列表。从图的观点来看,树也可视为一个拥有N 个节点N-1 条边的一个有向无环图

二叉树是一种更为典型的树状结构。如它名字所描述的那样,二叉树是每个节点最多有两个子树的树结构,通常子树被称作“左子树”和“右子树”。


树的遍历

前序遍历

前序遍历首先访问根节点,然后遍历左子树,最后遍历右子树。

中序遍历

中序遍历是先遍历左子树,然后访问根节点,然后遍历右子树。

对于二叉搜索树,我们可以通过中序遍历得到一个递增有序序列。

后序遍历

后序遍历是先遍历左子树,然后遍历右子树,最后访问树的根节点。

当你删除树中的节点时,删除过程将按照后序遍历的顺序进行。 也就是说,当你删除一个节点时,你将首先删除它的左节点和它的右边的节点,然后再删除节点本身。

另外,后序在数学表达中被广泛使用。 编写程序来解析后缀表示法更为容易。

层序遍历

层序遍历就是逐层遍历树结构。

广度优先搜索是一种广泛运用在树或图这类数据结构中,遍历或搜索的算法。 该算法从一个根节点开始,首先访问节点本身。 然后遍历它的相邻节点,其次遍历它的二级邻节点三级邻节点,以此类推。

当我们在树中进行广度优先搜索时,我们访问的节点的顺序是按照层序遍历顺序的。

使用一个叫做队列的数据结构来帮助我们做广度优先搜索。

LeetCodeLeetCode-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
}
LeetCodeLeetCode-cn难度
104.Maximum Depth of Binary Tree二叉树最大深度Easy
101.Symmetric Tree对称二叉树Easy
112.Path Sum路径总和Easy

总结

当遇到树问题时,请先思考一下两个问题:

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

如果答案都是肯定的,那么请尝试使用 “自顶向下” 的递归来解决此问题。 (信息从上往下传递)

或者你可以这样思考:对于树中的任意一个节点,如果你知道它子节点的答案,你能计算出该节点的答案吗? 如果答案是肯定的,那么 “自底向上” 的递归可能是一个不错的解决方法。 (信息从下往上传递)

LeetCodeLeetCode-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填充每个节点的下一个右侧节点指针 IIMedium
236.Lowest Common Ancestor of a Binary Tree二叉树的最近公共祖先Medium
297.Serialize and Deserialize Binary Tree二叉树的序列化与反序列化Hard
<think>我们参考用户提供的引用[1]和[2]来实现二叉树的递归拷贝构造函数(深拷贝)。 引用[1]展示了递归拷贝构造函数的实现,通过个私有的递归函数create来复制每个节点。 引用[2]则展示了另种写法,同样使用递归,但参数传递使用了指针引用,以确保能够正确修改指针。 用户的需求是实现BinaryTree类的递归拷贝构造函数(深拷贝)。根据引用,我们可以总结出两种常见的递归实现方式: 方式1(类似引用[1]): 公有拷贝构造函数:BinaryTree(BinaryTree<T> tree) 私有辅助函数:create(BinaryNode<T> *node) 递归复制节点 方式2(类似引用[2]): 公有拷贝构造函数:BinaryTree(BinaryTree<T>& s) 并调用私有递归函数 私有递归函数:copyOther(BinaryNode<T>* &thisSubTree, BinaryNode<T> *otherSubTree) 注意这里第个参数是指针的引用 两种方式的核心都是递归复制每个节点(包括数据、左子树和右子树),并确保在遇到空节点时停止递归。 我们选择方式2进行实现,因为它通过指针引用直接修改当前树的指针,避免了返回值传递,更直观。 假设二叉树的节点定义为: template <typename T> struct BinaryNode { T data; BinaryNode<T>* left; BinaryNode<T>* right; // 构造函数 BinaryNode(const T& d, BinaryNode<T>* l = nullptr, BinaryNode<T>* r = nullptr) : data(d), left(l), right(r) {} }; 二叉树类BinaryTree)包含个指向根节点的指针成员。 递归拷贝构造函数的实现步骤: 1. 在拷贝构造函数中,调用个私有的递归函数来复制另个树的根节点。 2. 递归函数需要两个参数:目标树当前节点的指针引用(因为我们要修改这个指针)和源树当前节点的指针。 3. 递归函数的基本情况:如果源树当前节点为空,则直接返回,目标节点设为空。 4. 否则,为目标节点动态分配内存,复制源节点的数据。 5. 递归复制左子树和右子树。 注意:由于是深拷贝,我们需要为每个节点创建新的空间。 代码实现如下: </think>根据引用内容,实现二叉树递归拷贝构造函数(深拷贝)的核心方法是使用递归遍历复制每个节点及其子树。以下是具体实现方法: ### 递归拷贝构造函数实现 ```cpp template <typename T> class BinaryTree { private: struct BinaryNode { T data; BinaryNode* left; BinaryNode* right; BinaryNode(const T& d) : data(d), left(nullptr), right(nullptr) {} }; BinaryNode* root; // 递归复制辅助函数 BinaryNode* copyNodes(BinaryNode* node) { if (node == nullptr) return nullptr; BinaryNode* newNode = new BinaryNode(node->data); // 创建新节点 newNode->left = copyNodes(node->left); // 递归复制左子树 newNode->right = copyNodes(node->right); // 递归复制右子树 return newNode; } public: // 递归拷贝构造函数 BinaryTree(const BinaryTree<T>& other) { root = copyNodes(other.root); // 从根节点开始递归复制 } }; ``` ### 关键机制说明 1. **深度复制**:递归创建新节点,确保新树与原树完全独立 2. **递归过程**: - 基线条件:遇到空节点时返回 `nullptr` - 递归步骤: * 新建节点复制数据 `new BinaryNode(node->data)` * 递归复制左子树 `copyNodes(node->left)` * 递归复制右子树 `copyNodes(node->right)` 3. **内存管理**:需要配套实现析构函数释放所有节点 ### 时间复杂度分析 时间复杂度为 $O(n)$(n 为节点数),空间复杂度为 $O(h)$(h 为树高,递归栈空间)[^3] ### 注意事项 1. 必须处理空树情况(`other.root == nullptr`) 2. 需要配套实现析构函数防止内存泄漏 3. 适用于任意二叉树结构(平衡/非平衡)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值