五月集训(第19天)—二叉树
二叉树
1. 144. 二叉树的前序遍历
思路1: 递归实现
先序遍历,当前节点-->左子节点-->右子节点
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
vector<int> ret;
void dfs(TreeNode *root) {
if (root) {
ret.push_back(root->val);
dfs(root->left);
dfs(root->right);
}
}
public:
vector<int> preorderTraversal(TreeNode* root) {
ret.clear();
dfs(root);
return ret;
}
};
递归还能写的再优雅些
class Solution {
vector<int> ret;
public:
vector<int> preorderTraversal(TreeNode* root) {
if (root == nullptr) return {};
ret.push_back(root->val);
preorderTraversal(root->left);
preorderTraversal(root->right);
return ret;
}
};
思路2: 迭代实现
先序遍历,当前节点-->左子节点-->右子节点
利用栈模拟递归过程,注意现将右子节点压栈,再将左子节点压栈,才能保证遍历树时,先访问左子节点,后访问右子节点。
在遍历每个节点时直接访问(ret.push_back(node->val)
)
class Solution {
public:
// 迭代实现
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode *> stk;
vector<int> ret;
ret.clear();
if (root) stk.push(root);
TreeNode *node = root;
while (!stk.empty()) {
node = stk.top(); /* 取出栈顶 */
stk.pop();
ret.push_back(node->val); /* 访问栈顶节点,即当前子树根节点 */
// 先将右子节点压栈,再将左子节点压栈,保证访问的顺序是,先左子节点,再访问右子节点
if (node->right) stk.push(node->right);
if (node->left) stk.push(node->left);
}
return ret;
}
};
2. 94. 二叉树的中序遍历
思路1: 递归实现
中序遍历,左子节点-->当前节点-->右子节点
class Solution {
vector<int> ret;
void dfs(TreeNode *root) {
if (root) {
dfs(root->left);
ret.push_back(root->val);
dfs(root->right);
}
}
public:
vector<int> inorderTraversal(TreeNode* root) {
ret.clear();
dfs(root);
return ret;
}
};
递归还能写的再优雅些
class Solution {
vector<int> ret;
public:
vector<int> inorderTraversal(TreeNode* root) {
if (root == nullptr) return {};
inorderTraversal(root->left);
ret.push_back(root->val);
inorderTraversal(root->right);
return ret;
}
};
思路2: 迭代实现
中序遍历,左子节点-->当前节点-->右子节点
利用栈模拟递归过程,先向左遍历到最左节点,有右子节点,则向右子节点遍历,否则返回上一层向右遍历,直到遍历结束。
在遍历完左子树后,访问栈顶节点,即先访问左子节点,再访问根节点(ret.push_back(node->val)
)
class Solution {
public:
// 迭代实现
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode *> stk;
vector<int> ret;
ret.clear();
TreeNode *node = root;
while (!stk.empty() || node) { /* 栈非空或者当前节点不为空指针 */
// 栈空,当前节点不为空: 可遍历当前节点的右子树
// 栈非空,当前节点空: 返回到上一层遍历其右子树
// 栈非空,当前节点非空: 继续遍历当前节点右子树
// 栈空,当前节点空: 所有节点都被遍历过了,遍历结束
while (node) { /* 找到最左子节点 */
stk.push(node);
node = node->left;
}
node = stk.top();
ret.push_back(node->val);
stk.pop(); /* 返回上一层 */
node = node->right; /* 遍历右子树 */
}
return ret;
}
};
3. 145. 二叉树的后序遍历
思路1: 递归实现
后序遍历,左子节点-->右子节点-->当前节点
class Solution {
vector<int> ret;
void dfs(TreeNode *root) {
if (root) {
dfs(root->left);
dfs(root->right);
ret.push_back(root->val);
}
}
public:
vector<int> postorderTraversal(TreeNode* root) {
ret.clear();
dfs(root);
return ret;
}
};
递归还能写的再优雅些
class Solution {
vector<int> ret;
public:
vector<int> postorderTraversal(TreeNode* root) {
if (root == nullptr) return {};
postorderTraversal(root->left);
postorderTraversal(root->right);
ret.push_back(root->val);
return ret;
}
};
思路2: 迭代实现
后序遍历,左子节点-->右子节点-->当前节点
麻烦一些,先记录上次弹栈的节点,即被处理过的子树的根节点,用当前节点的左右子节点与其比较,判断左右子节点是否被遍历过。
左子节点:如果不为空,且左右子树未被遍历过则入栈,访问左子树
右子节点:如果不为空,且右子树未被遍历过则入栈,访问右子树
如果左右子树都被遍历过,则当前节点弹栈,相当于访问完左右子树后访问当前子树根节点。
class Solution {
vector<int> ret;
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode *> stk;
vector<int> ret;
ret.clear();
if (root != nullptr) stk.push(root);
TreeNode *last_pop = root;
TreeNode *node = root;
while (!stk.empty()) {
node = stk.top();
if (node->left != nullptr && node->left != last_pop && node->right != last_pop) { /* 先访问左子节点,遍历左子树 */
// 要遍历左子树,则要左子树没有被遍历过,即上次遍历完的节点既不是node->left,也不是node->right(因为先访问左子节点,如果右子节点被访问过了,则非空左子节点一定被访问过了)
stk.push(node->left);
}
else if (node->right != nullptr && node->right != last_pop) { /* 再访问右子节点,遍历右子树 */
// 遍历右子树,则要求右子树没有遍历过,即上次遍历完的节点不是node->right
stk.push(node->right);
} else { /* 最后访问当前子树根节点 */
ret.push_back(node->val);
stk.pop();
last_pop = node;
}
}
return ret;
}
};
迭代还能写的再优雅些
思路3: 迭代实现
后序遍历,左子节点-->右子节点-->当前节点
修改前序遍历结果:中左右 --> 中右左,将结果反向输出(利用栈的性质,压入,弹出)就得到了后续遍历结果左右中
具体实现的解释在代码注释中
class Solution {
vector<int> ret;
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode *> stk;
stack<TreeNode *> ans_stk;
vector<int> ret;
ret.clear();
if (root) stk.push(root);
TreeNode *node = root;
while (!stk.empty()) { /* 修改先序遍历压栈顺序,导致遍历顺序修改: 中左右 --> 中右左 */
node = stk.top();
stk.pop();
ans_stk.push(node); /* 将以 中右左 为顺序的遍历结果压入ans_stk中 */
if (node->left) stk.push(node->left);
if (node->right) stk.push(node->right);
}
while (!ans_stk.empty()) { /* 将遍历结果从栈中弹出,遍历顺序变为 左右中 即为后序遍历 */
ret.push_back(ans_stk.top()->val);
ans_stk.pop();
}
return ret;
}
};
4. 104. 二叉树的最大深度
思路1:
记录每个节点的深度,利用先序遍历,更新深度最大值。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
int max_depth = 0;
void dfs(TreeNode *root, int depth) {
if (root) {
dfs(root->left, depth + 1);
max_depth = max(max_depth, depth);
dfs(root->right, depth + 1);
}
}
public:
int maxDepth(TreeNode* root) {
dfs(root, 1);
return max_depth;
}
};
思路2:
我对递归的理解还有很大的提升空间,直接利用当前函数递归求取其左右子树的深度。返回其左右子树最大深度 + 1。
class Solution {
public:
int maxDepth(TreeNode* root) {
if (root == nullptr) return 0;
return max( maxDepth(root->left), maxDepth(root->right) ) + 1;
}
};