二叉树基础
- 平衡二叉搜索树(AVL):
map, set, multimap, multiset底层实现都是AVL树,增删操作时间复杂度为logn - 树的顺序存储:父节点数组下标为i,左孩子为
2i+1、右孩子为2i+2 - 遍历方式:深度优先遍历(前中后序【递归法、迭代法】)、广度优先遍历(层次遍历【迭代法】)
- 二叉树节点的定义
struct TreeNode{
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
递归遍历
题目:144 145 94
要点
- 确定递归函数的参数和返回值
- 确定中止条件
- 单层递归的逻辑
前序遍历
class Solution {
public:
void preorder(TreeNode* cur, vector<int>& result) {
if (cur == nullptr) return; //bug! cur本身就为空了,不存在cur->val
//相当于遍历,每次都还要访问叶节点的左右孩子,才返回
result.push_back(cur->val);
preorder(cur->left, result);
preorder(cur->right, result);
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
preorder(root, res);
return res;
}
};
后序遍历
void postorder(TreeNode* cur, vector<int>& result) {
if (cur == nullptr) return;
postorder(cur->left, result);
postorder(cur->right, result);
result.push_back(cur->val);
}
中序遍历
void inorder(TreeNode* cur, vector<int>& result) {
if (cur == nullptr) return;
inorder(cur->left, result);
result.push_back(cur->val);
inorder(cur->right, result);
}
总结
递归遍历主要是递归函数的三要素,将结果存放在递归函数参数中(用引用的方式)
迭代遍历
题目:144 145 94
要点
- 遍历树的逻辑可分为:访问节点、处理节点;前序遍历正好两个逻辑是同时的
- 中左右,先把右分支的压入栈,再压左分支
- 迭代法的后序遍历,通过将前序遍历代码改为中右左,再翻转得到
- 中序遍历,访问+处理两个逻辑分为,
cur指针来访问节点、栈来处理节点;栈中保存的都是经过访问但未处理的节点;cur指针为空、栈不为空时,说明访问到叶子节点的左右孩子了,需要弹出栈的最上元素,进行输出。
前序遍历(迭代法)
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> st; // 不是int类型,不然丢失父子节点的关系
if (root == nullptr) return res;
st.push(root);
while (!st.empty()) { // 每个循环处理一个节点
TreeNode* cur = st.top();
res.push_back(cur->val);
st.pop();
if (cur->right) st.push(cur->right);
if (cur->left) st.push(cur->left);
}
return res;
}
};
后序遍历(迭代法)
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> st;
if (root == nullptr) return res;
st.push(root);
while (!st.empty()) { //中右左
TreeNode* cur = st.top();
res.push_back(cur->val);
st.pop();
if (cur->left) st.push(cur->left);
if (cur->right) st.push(cur->right);
}
reverse(res.begin(), res.end());
return res;
}
};
中序遍历(迭代法)
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> st; //stack里放已访问但是未处理的元素
TreeNode* cur = root;
while (cur != nullptr || !st.empty()) { // 既无需要访问的元素,也无待处理的元素,就结束了
if (cur != nullptr) {
st.push(cur);
cur = cur->left;
}else {
cur = st.top();
st.pop();
res.push_back(cur->val);
cur = cur->right;
}
}
return res;
}
};
总结
迭代法进行前中后遍历,前序由于访问和处理同时,相比于中序不需要指针去单独作访问操作。
统一风格的迭代法遍历
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> st;
if (root != nullptr) st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
st.pop();
if (node != nullptr) {
if (node->right) st.push(node->right);
st.push(node);
st.push(nullptr);
if (node->left) st.push(node->left);
}else {
node = st.top();
st.pop();
res.push_back(node->val);
}
}
return res;
}
};
- 尽管访问和处理逻辑顺序不同,不用
cur指针来访问,使用NULL作为标记位,每当st.top()为NULL时,下一个元素为要处理的元素 - 要处理的元素和要访问的元素放在同一个栈中,用标记位区分
- 入栈顺序和迭代法的前序遍历一样,反着来
- 先入
根节点node,再入NULL;这样出来时,NULL的下一个top()元素就是要处理的元素