赞
踩
迭代方式统一写法的中序是可以的
- class Solution {
- public:
- TreeNode* invertTree(TreeNode* root) {
- stack<TreeNode*> st;
- if (root != NULL) st.push(root);
- while (!st.empty()) {
- TreeNode* node = st.top();
- if (node != NULL) {
- st.pop();
- if (node->right) st.push(node->right); // 右
- st.push(node); // 中
- st.push(NULL);
- if (node->left) st.push(node->left); // 左
-
- } else {
- st.pop();
- node = st.top();
- st.pop();
- swap(node->left, node->right); // 节点处理逻辑
- }
- }
- return root;
- }
- };
这个代码是一个非递归版本的二叉树翻转实现,它使用了一个栈(stack)来模拟递归过程。这种方法也被称为深度优先搜索(DFS)的迭代版本。
首先,如果根节点不为空,将其推入栈中。然后进入一个循环,只要栈不为空就继续执行循环。在每次循环中,首先取出栈顶的节点。如果这个节点不为空,那么将其右子节点、自己、一个空节点(作为标记),左子节点依次入栈。然后进入下一次循环。
如果栈顶的节点为空,那么将其弹出,然后再次取出栈顶的节点,并将其弹出,然后交换这个节点的左右子节点。这样,就完成了对这个节点的处理。
这个过程会一直进行,直到栈变为空,此时所有的节点都已经被处理过,二叉树也就完成了翻转。
举个例子,如果输入的二叉树是:
- 4
- / \
- 2 7
初始: [4]
推入4的左右子节点和4自身: [7, 4, NULL, 2]
弹出2和NULL,交换2的左右子节点: [7, 4]
推入4的左右子节点和4自身: [4, NULL, 7]
弹出7和NULL,交换7的左右子节点: [4]
推入4的左右子节点和4自身: [NULL, 4]
弹出4和NULL,交换4的左右子节点: []
假设我们有以下的二叉树:
- 4
- / \
- 2 7
- / \ / \
- 1 3 6 9
我们首先将根节点4推入栈中,然后进入while循环。
在循环中,我们首先检查栈顶元素。因为栈顶元素是4,不是NULL,所以我们进入if分支。在这个分支中,我们首先将4弹出,然后将其右子节点7、自身4、NULL标记、左子节点2依次推入栈中。此时栈的内容为:[2, NULL, 4, 7]。
然后我们再次进入while循环,取出栈顶元素2,因为2不是NULL,所以我们同样进入if分支。我们将2弹出,然后将其右子节点3、自身2、NULL标记、左子节点1依次推入栈中。此时栈的内容为:[1, NULL, 2, 3, NULL, 4, 7]。
接下来我们取出栈顶元素1,因为1是叶节点,没有子节点,所以我们只需要将1和NULL标记推入栈中。此时栈的内容为:[NULL, 1, NULL, 2, 3, NULL, 4, 7]。
接下来我们取出栈顶元素NULL,因为这是一个NULL标记,所以我们进入else分支。在这个分支中,我们首先将NULL弹出,然后取出栈顶元素1,并将其弹出,然后交换1的左右子节点。因为1是叶节点,没有子节点,所以这个交换操作没有实际效果。此时栈的内容为:[NULL, 2, 3, NULL, 4, 7]。
我们再次进入while循环,取出栈顶元素NULL,然后进入else分支,弹出NULL,取出栈顶元素2,并将其弹出,然后交换2的左右子节点。此时2的左右子节点被交换,变为:
- 2
- / \
- 3 1
此时栈的内容为:[3, NULL, 4, 7]。
这个过程会一直进行,直到栈变为空,此时所有的节点都已经被处理过,二叉树也就完成了翻转。最后翻转后的二叉树为:
- 4
- / \
- 7 2
- / \ / \
- 9 6 3 1
在这个代码中,NULL在栈中的作用主要是作为一个标记,用来表示当前节点的左右子节点都已经被访问过了,下一步需要处理当前节点(交换其左右子节点)。
当我们从栈顶取出一个节点时,如果这个节点不是NULL,那么我们会将其右子节点、自己、NULL标记、左子节点依次入栈。这样,我们就可以在下一次循环中继续访问其左子节点。同时,NULL标记和自己被放在了左子节点之后,这保证了我们在处理完左子节点之后,会回到自己,进行处理。
当我们从栈顶取出一个节点时,如果这个节点是NULL,那么我们知道下一个节点就是需要处理的节点,因为其左右子节点都已经被处理过了。我们会将NULL弹出,然后取出下一个节点,交换其左右子节点。
所以,你可以把NULL看作是一个标记,它的出现表示当前节点的左右子节点都已经被访问过了,下一步需要处理当前节点。
给你两棵二叉树 root
和 subRoot
。检验 root
中是否包含和 subRoot
具有相同结构和节点值的子树。如果存在,返回 true
;否则,返回 false
。
二叉树 tree
的一棵子树包括 tree
的某个节点和这个节点的所有后代节点。tree
也可以看做它自身的一棵子树。
- =深度优先搜索暴力匹配=
- class Solution {
- public:
- bool isSubtree(TreeNode* root, TreeNode* subRoot) {
- if (!root) {
- return false;
- }
- if (isSameTree(root, subRoot)) {
- return true;
- }
- return isSubtree(root->left, subRoot) || isSubtree(root->right, subRoot);
- }
-
- bool isSameTree(TreeNode* p, TreeNode* q) {
- if (!p && !q) {
- return true;
- }
- if (!p || !q) {
- return false;
- }
- return p->val == q->val && isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
- }
- };
-
- 实际上是在主树root的每个节点上,都尝试与子树subRoot进行完全匹配。
- 我们在函数isSubtree中,对root进行深度优先搜索。对于root中的每个节点,我们都调用isSameTree函数来检查从该节点开始的子树是否与subRoot相同。
- isSameTree函数也是一个深度优先搜索,它会递归地比较两棵树的左子树和右子树是否相同。
- 因此,这个算法的时间复杂度是O(mn),其中m是主树root的节点数,n是子树subRoot的节点数。这是因为对于root中的每个节点,我们都可能需要查看subRoot中的所有节点来进行匹配。
- 空间复杂度是O(m),这是因为在最坏的情况下,我们可能需要递归地访问root中的所有节点。这将在调用栈中产生m个函数调用,因此需要O(m)的空间。
二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
层序遍历的思想
que
,用于存储待处理的节点。while
循环,当队列不为空时,执行以下操作:size
(队列的大小)。depth
加1,因为我们正在进入新的一层。for
循环,从0到size-1
)depth
的值即为二叉树的最大深度。depth
作为结果。- class Solution {
- public:
- int maxDepth(TreeNode* root) {
- if(root==NULL) return 0;
- int depth=0;
- queue<TreeNode*> que;
- que.push(root);
- while(!que.empty()){
- int size=que.size();
- depth++;
- for(int i=0;i<size;i++){
- TreeNode* node=que.front();
- que.pop();
- if(node->left) que.push(node->left);
- if(node->right) que.push(node->right);
- }
- }
- return depth;
- }
- };
递归法求解二叉树的最大深度:
确定递归函数的参数和返回值参数:
1. 树的根节点TreeNode* node
。
2. 返回值:这棵树的深度,类型为int
确定终止条件:
“如果当前节点为空(node == NULL
),则返回0”
- class Solution {
- public:
- int getdepth(TreeNode* node) {
- if (node == NULL) return 0;
- int leftdepth = getdepth(node->left); // 左
- int rightdepth = getdepth(node->right); // 右
- int depth = 1 + max(leftdepth, rightdepth); // 中
- return depth;
- }
- int maxDepth(TreeNode* root) {
- return getdepth(root);
- }
- };
可以深度优先搜索(DFS)和回溯的思想。前序的话求深度:
递归函数是: void getDepth(TreeNode* node, int depth)
递归函数需要两个参数,当前的节点(node)和当前的深度(depth)。
返回值是:
在每次递归调用中 getDepth
函数都没有明确的返回值,它将最终结果保存在了成员变量 result
中。在最后的主函数 maxDepth
中返回的是 result
, 这个 result
表示二叉树的最大深度。
终止条件是:if (node->left == NULL && node->right == NULL) return ;
这个条件用于判断当前节点为叶子节点,也就是该节点既没有左孩子也没有右孩子,该路径结束,返回上一级节点。
单层的递归逻辑是:
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。
迭代法:
前序遍历(根节点->左子树->右子树):
1. 访问当前节点,判断是否为空,判断终止条件
2. 更新最小深度: 如果当前访问的节点是叶子节点,则更新最小深度。result = min(result, depth);
。
3.递归遍历左右子树: 如果当前节点不为空且不是叶子节点,会依次递归遍历左子树和右子树。
getdepth(node->left, depth + 1);
和getdepth(node->right, depth + 1);
.
4.当左右子节点都递归完毕访问的节点是叶子节点更新最小深度。
后序遍历(左子树->右子树->根节点):
1. 如果节点为空,则返回深度0,递归结束。这是递归的边界条件。
2. 它首先递归访问左右子树,然后在所有子节点被处理完之后,再更新当前节点的深度。int leftDepth = getDepth(node->left);
和int rightDepth = getDepth(node->right);
。
更新当前节点的深度:只有在访问完子节点之后,才会根据左右子树的深度来更新当前节点的深度。
如果左子树为空且右子树不为空,意味着当前节点不可能是最小深度的节点,因为右子树还没有被完全遍历。将当前节点的深度更新为右子树的深度加1。if (node->left == NULL && node->right != NULL) { return 1 + rightDepth;}
。
如果右子树为空且左子树不为空,意味着当前节点也不可能是最小深度的节点,因为左子树还未完全遍历。这种情况下,将当前节点的深度更新为左子树的深度加1。if (node->left != NULL && node->right == NULL) {return 1 + leftDepth; }
。
当左右子树都不为空时,当前节点的深度为左右子树深度的较小值再加1。
层序遍历:只有当左右孩子都为空的时候,说明遍历到最低点了。返回depth
给你一棵 完全二叉树 的根节点 root
,求出该树的节点个数。
完全二叉树方法:
当完全二叉树的左子树和右子树的高度相等时,说明左子树是一棵满二叉树,可以直接通过公式 (2 << leftDepth) - 1
计算出节点数。 (2<<leftDepth)
的作用相当于 2^(leftDepth+1)
,是左子树的节点数加根节点。
反之,如果左子树和右子树的高度不等,那么右子树的高度比左子树小一且右子树也是满二叉树,左子树仍然是一棵完全二叉树。这种情况下,递归分别求解左子树和右子树的节点数量,两者之和再加上根节点就是当前完全二叉树的节点数量。
if (root == nullptr) return 0;
left
和right
,同时定义了两个变量leftDepth
和rightDepth
来记录左右子树的深度。- TreeNode* left = root->left;
- TreeNode* right = root->right;
- int leftDepth = 0, rightDepth = 0;
- while (left) {
- left = left->left;
- leftDepth++;
- }
- while (right) {
- right = right->right;
- rightDepth++;
- }
return (2 << leftDepth) - 1
来求出节点总数,这里 (2 << leftDepth)
相当于 pow(2, leftDepth+1)
,我们的目的是求 2^(深度+1)-1
。- if (leftDepth == rightDepth) {
- return (2 << leftDepth) - 1;
- }
return countNodes(root->left) + countNod
所以,无论怎样,这种方法都会覆盖到完全二叉树的所有节点,所以不会漏掉任何节点
给定一个二叉树,判断它是否是 平衡二叉树
平衡二叉树的定义是:它是一棵空树或它的左右子树的高度差的绝对值不超过 1,并且左右两个子树都是一颗平衡二叉树。换句话说,平衡二叉树要求每个节点的左右子树的高度差不超过1。求高度并判断。
而高度只能从下到上去查,所以只能后序遍历(左右中)
平衡二叉树的定义是:它是一棵空树或它的左右子树的高度差的绝对值不超过 1,并且左右两个子树都是一颗平衡二叉树。换句话说,平衡二叉树要求每个节点的左右子树的高度差不超过1。
为了判断一个二叉树是否是平衡的,你需要从底部开始,先判断子树是不是平衡的,再判断父节点是否平衡。每一个节点都这样判断。这就需要后序遍历(左-右-中)。因为在前序和中序遍历(先访问父节点)时,我们无法获取子树的相关信息。
所以,我们首先遍历到底部(左右子节点),计算出它们的高度,然后返回给父节点,让父节点去判断“我是不是平衡的”。如果非平衡,我们就直接返回非平衡的结果。而这恰恰是维护、“更新”高度所必需的,也是后序遍历可以给我们带来的便利之处。让我们在获取所有必要信息后再做决定。
这也就是为什么我们在计算最大深度问题中也用到后序遍历的原因,首先遍历子节点获取深度,然后根据子节点的深度计算出当前节点的深度。
所以,后序遍历特别适合这类需要从下至上(从子节点到父节点)进行操作的场合。
要求比较高度。
1. 明确递归函数的参数和返回值
参数:当前传入节点。 返回值:以当前传入节点为根节点的树的高度。
如果当前传入节点为根节点的二叉树已经不是二叉平衡树了,还返回高度的话就没有意义了。、那标记为-1。
2.明确终止条件
在递归的过程中,当递归到达叶子节点时,会试图继续访问它的子节点,也就是空节点。这时,我们返回0,表示已经到达树的最底部。
3. 明确单层递归的逻辑
分别求出其左右子树的高度,然后如果差值小于等于1,则返回当前二叉树的高度,否则返回-1,表示已经不是二叉平衡树了。
- class Solution {
- public:
- // 返回以该节点为根节点的二叉树的高度,如果不是平衡二叉树了则返回-1
- int getHeight(TreeNode* node) {
- if (node == NULL) {
- return 0;
- }
- int leftHeight = getHeight(node->left);
- if (leftHeight == -1) return -1;
- int rightHeight = getHeight(node->right);
- if (rightHeight == -1) return -1;
- return abs(leftHeight - rightHeight) > 1 ? -1 : 1 + max(leftHeight, rightHeight);
- }
- bool isBalanced(TreeNode* root) {
- return getHeight(root) == -1 ? false : true;
- }
- };
1. 递归函数需要三个参数,当前的节点 (cur
), 目前的路径 (path
) 和结果 (result
)。traversal
函数都没有明确的返回值,它将所有的结果保存在了参数 result
2. 终止条件是:if (cur->left == NULL && cur->right == NULL)
,这个条件用于判断当节点是叶子节点时,也就是该节点既没有左孩子也没有右孩子时,我们就在结果result
中添加当前的路径。
3. 单层的递归逻辑是:
result
中,结束当前递归。if (cur->left) traversal(cur->left, path + "->", result); // 左 回溯就隐藏在这里
当我们调用traversal(cur->left, path + "->", result)
时,实际上创建了一个新的字符串path + "->"
,并且在这个新的字符串的基础上继续递归。这个新的字符串并不会影响到原来的path
字符串。因此,当递归返回到上一级的时候,path
还是其原来的值,这就完成了回溯。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。