前言
满二叉树和完全二叉树:
C++中map、set、multimap,multiset的底层实现都是平衡二叉搜索树,所以map、set的增删操作时间时间复杂度是logn
二叉树可以链式存储,也可以顺序存储。
那么链式存储方式就用指针, 顺序存储的方式就是用数组。
一、递归遍历
题目:
给你二叉树的根节点 root ,返回它节点值的 前序 遍历。
思路:
- 确定递归函数的参数和返回值
- 确定终止条件
- 确定单层递归的逻辑
代码:
/**
* 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 {
public:
void traversal(TreeNode* cur, vector<int>& vec){
if (cur == NULL) 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;
}
};
二、迭代法遍历
Preorder Traversal
思路:
前序遍历是中左右,每次先处理的是中间节点,那么先将根节点放入栈中,然后将右孩子加入栈,再加入左孩子。
为什么要先加入 右孩子,再加入左孩子呢? 因为这样出栈的时候才是中左右的顺序。
Initialize a stack, and push the root node in the stack.
As long as the stack is not empty, repeat the following steps:
- Pop the top element of the stack, and add its value to the result vector.
- If the current node has a right child, push it into the stack(because a stack is a LIFO data structure, we first push the right child, so the left child will be processed first.)
- If the current node has a left child. push it into the stack.
In this way, we can traverse the binary tree in preorder without recursion.
代码:
/**
* 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 {
public:
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> result;
if (root == NULL) return result;
st.push(root);
while(!st.empty()) {
TreeNode* node = st.top();// visit the root
st.pop();
result.push_back(node->val);
if (node->right) st.push(node->right);// Pushing right child into stack
if (node->left) st.push(node->left);// Pushing left child into stack
}
return result;
}
};
Inorder Traversal
思路:
在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。
这是因为前序遍历中访问节点(遍历节点)和处理节点(将元素放进result数组中)可以同步处理,但是中序就无法做到同步!
-
初始化一个空栈和一个指向树根的指针cur。
-
进入一个循环,只要cur不为NULL或者栈不为空就继续循环。这确保了所有节点都被处理,并且如果树不是完全二叉树,函数也不会过早地终止。
-
如果cur不为NULL,则将其压入栈中,并将cur设置为其左孩子。这个过程是在尝试遍历到当前子树的最左侧。
-
如果cur为NULL,则取出栈顶元素(这是最近没有左子树或其左子树已经被遍历过的节点),将其值添加到结果向量中,并将cur设置为其右孩子。
-
最后,返回结果向量。
Code:
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
TreeNode* cur = root;
while (cur != NULL || !st.empty()) {
if (cur != NULL) {
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 != 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();
result.push_back(node->val);// 输出
}
}
return result;
}
};