文章目录
理论基础
二叉树有满二叉树和完全二叉树两种主要的形式。
满二叉树
满二叉树是一种特殊的二叉树,其中每个节点要么是叶子节点(即没有子节点),要么恰好有两个子节点。
满二叉树的特性包括:
- 每一层的节点数是 2 的 n 次方,其中 n 是层数,从 0 开始。
- 一个满二叉树的节点总数为 2^(h+1) - 1,其中 h 是树的高度(根节点的高度为 0)。
完全二叉树
在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层(h 从 1 开始),则该层包含 1~ 2^(h-1) 个节点。
完全二叉树的特性包括:
- 除了最后一层外,其他所有层上的节点都是满的。
- 最后一层的节点从左到右尽可能填满,没有空位。
二叉搜索树
二叉搜索树是一个有序树,其特性为:
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉排序树
平衡二叉搜索树
平衡二叉搜索树是一种特殊的二叉搜索树,常见的平衡二叉树包括 AVL(Adelson-Velsky and Landis)树和红黑树,AVL 树的性质是一棵空树或它左右两个子树的高度差绝对值不超过 1,并且左右两个子树都是一棵平衡二叉树。
C++ 中 map,set,multimap,multiset 的底层实现都是平衡二叉搜索树,所以 map 和 set 的增删操作时间复杂度是 l o g n logn logn
二叉树的存储方式
二叉树可以链式存储(指针),也可以顺序存储(数组)。
二叉树一般使用链式存储。
链式存储
顺序存储
如果父节点的数组下标是 i,左孩子是 i2+1,右孩子是 i2+2
二叉树的遍历方式
二叉树主要有两种遍历方式:
- 深度优先遍历:先往深走,遇到叶子节点再往回走。
前序、中序还是后续指的是中间节点的位置:
- 前序遍历(递归法,迭代法):中左右(5412678)
- 中序遍历(递归法,迭代法):左中右(1425768)
- 后序遍历(递归法,迭代法):左右中(1247865)
- 广度优先遍历:一层一层的去遍历。
- 层次遍历(迭代法)
二叉树的定义
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) :val(x), left(nullptr), right(nullptr){}
};
递归遍历
递归的三步分析:
- 确定递归函数的参数和返回值;
- 确定终止条件
- 确定单层递归的逻辑
leetcode 144-二叉树的前序遍历
https://leetcode.cn/problems/binary-tree-preorder-traversal/description/
class Solution {
public:
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == nullptr)return;
vec.push_back(cur->val);
traversal(cur->left, vec);
traversal(cur->right, vec);
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
traversal(root, result);
return result;
}
};
leetcode 145-二叉树的后序遍历
https://leetcode.cn/problems/binary-tree-postorder-traversal/description/
class Solution {
public:
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == nullptr)return;
traversal(cur->left, vec);
traversal(cur->right, vec);
vec.push_back(cur->val);
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int> result;
traversal(root, result);
return result;
}
};
leetcode 94-二叉树的中序遍历
https://leetcode.cn/problems/binary-tree-inorder-traversal/description/
class Solution {
public:
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == nullptr)return;
traversal(cur->left, vec);
vec.push_back(cur->val);
traversal(cur->right, vec);
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
traversal(root, result);
return result;
}
};
迭代遍历
可以用栈模拟二叉树的遍历过程。
前序遍历
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
if (root == nullptr) return result;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
if (node != nullptr) {
st.pop();
result.push_back(node->val);
if(node->right) st.push(node->right);
if (node->left) st.push(node->left);
}
}
return result;
}
};
后序遍历
前序遍历的遍历顺序是“中左右”,我们可以在上面的代码的基础上稍微进行调整完成后序遍历的代码“左右中”,即先将左右的顺序调整变为“中右左”,然后在此基础上使用 reverse 函数翻转 result 数组变为“左右中”
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
if (root == nullptr) return result;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
if (node != nullptr) {
st.pop();
result.push_back(node->val);
if (node->left) st.push(node->left);
if (node->right) st.push(node->right);
}
}
reverse(result.begin(),result.end());
return result;
}
};
中序遍历
中序遍历和前后序遍历不同,前序遍历先访问的元素是中间节点,要处理的元素也是中间节点,访问和要处理的元素是一致的,而中序节点需要一层一层向下访问,直到到达树左边的最底部,然后才开始处理节点,处理顺序和访问顺序是不一致的。
使用迭代法完成中序遍历,需要借助指针的遍历来帮助访问节点,栈用于处理节点上的元素。
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
TreeNode* cur = root;
while (cur != nullptr || !st.empty()) {
if (cur != nullptr) {
st.push(cur);
cur = cur->left;
}
else {
cur = st.top();
st.pop();
result.push_back(cur->val);
cur = cur->right;
}
}
return result;
}
};
统一迭代
前面我们用迭代法进行前中后序遍历时的方法没有统一,下面我们使用二叉树的同意迭代法进行解决:
中序遍历
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
if (root != nullptr) st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
if (node != nullptr) {
st.pop();
if (node->right) st.push(node->right);
st.push(node);
st.push(nullptr);
if (node->left) st.push(node->left);
}
else {
st.pop();
node = st.top();
st.pop();
result.push_back(node->val);
}
}
return result;
}
};
在此基础上我们只需要修改部分代码的位置就能实现前序遍历和后序遍历:
前序遍历
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
if (root != nullptr) st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
if (node != nullptr) {
st.pop();
if (node->right) st.push(node->right);
if (node->left) st.push(node->left);
st.push(node);
st.push(nullptr);
}
else {
st.pop();
node = st.top();
st.pop();
result.push_back(node->val);
}
}
return result;
}
};
后序遍历
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
if (root != nullptr) st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
if (node != nullptr) {
st.pop();
st.push(node);
st.push(nullptr);
if (node->right) st.push(node->right);
if (node->left) st.push(node->left);
}
else {
st.pop();
node = st.top();
st.pop();
result.push_back(node->val);
}
}
return result;
}
};
标签:node,遍历,迭代,st,result,push,二叉树
From: https://blog.csdn.net/Catherine121314/article/details/139167642