本文参考代码随想录二叉树章节
二叉树的基本概念
如图所示为一个二叉树,根节点为{5}
,叶子节点(无左右子节点)为{4,6,8,9}
,接下来所有的遍历过程都将以这个二叉树为例,在leetcode
测试用例中,这个二叉树可以写成[5,2,3,4,1,8,7,null,null,6,null,null,null,null,9]
二叉树的遍历方式主要可以分为以下几种:
- 深度优先遍历 - 前序遍历/ 中序遍历/ 后序遍历
- 递归法
- 迭代法 (基于栈)
- 广度优先遍历 - 层序遍历 (基于队列)
对于上面这个二叉树,其遍历结果分别为:
- 前序遍历 (中左右)
[5,2,4,1,6,3,8,7,9]
- 中序遍历 (左中右)
[4,2,6,1,5,8,3,7,9]
- 后序遍历 (左右中)
[4,6,1,2,8,9,7,3,5]
- 层序遍历
[[5],[2,3],[4,1,8,7],[6,9]]
本文主要涉及到的leetcode
题目如下:
在leetcode
中,二叉树节点类的定义如下:
- 属性包括
val
也即节点指向的值,left
right
分别指向左右子节点 - 构造函数有两个重载,可以定义一个空节点,或者用 值 / 值 + 左右子节点 来创建一个节点
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) {}
};
深度优先遍历
递归法
所谓递归就是先递后归,以前序遍历为例::
- 递就是将原问题分解成子问题:[原问题] 前序遍历二叉树的所有节点 = [子问题] 访问根节点 + 前序遍历根节点的左子树 + 前序遍历根节点的右子树
- 沿着原问题一直分解,子问题越来越小,直到达到一个尽头,这个尽头就是递归的边界条件,这个返回的过程就是归
144.二叉树的前序遍历
class Solution {
public:
void Traversal(TreeNode* root, vector<int>& ans) {
if (root == nullptr) return;
ans.push_back(root->val);
Traversal(root->left, ans);
Traversal(root->right, ans);
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> ans;
Traversal(root, ans);
return ans;
}
};
中序遍历 和 后序遍历 同理,只需要更换递归函数中代码的顺序即可:
94.二叉树的中序遍历
[原问题] 中序遍历二叉树的所有节点 = [子问题] 中序遍历根节点的左子树 + 访问根节点 + 中序遍历根节点的右子树
class Solution {
public:
void Traversal(TreeNode* root, vector<int>& ans) {
if (root == nullptr) return;
Traversal(root->left, ans);
ans.push_back(root->val);
Traversal(root->right, ans);
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> ans;
Traversal(root, ans);
return ans;
}
};
145.二叉树的后序遍历
[原问题] 后序遍历二叉树的所有节点 = [子问题] 后序遍历根节点的左子树 + 后序遍历根节点的右子树 + 访问根节点
class Solution {
public:
void Traversal(TreeNode* root, vector<int>& ans) {
if (root == nullptr) return;
Traversal(root->left, ans);
Traversal(root->right, ans);
ans.push_back(root->val);
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int> ans;
Traversal(root, ans);
return ans;
}
};
迭代法
递归法的原理就是每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,这就是递归为什么可以返回上一层位置的原因。因此我们也可以直接基于栈完成二叉树的前 中 后序遍历。
144.二叉树的前序遍历
基于栈的前序遍历过程如下:[5,2,4,1,6,3,8,7,9]
- 首先将树的根节点压入栈
- 每次弹出栈顶元素后,需要将弹出的栈顶元素的右子节点和左子节点(前提是不为空)先后压入栈中
| 操作 | 栈 | 遍历序列 |
| — | — | — |
| 5入栈 |{5}
| |
| 5 出栈 3 2 入栈 |{3,2}
|{5}
|
| 2 出栈 1 4 入栈 |{3,1,4}
|{5,2}
|
| 4 出栈 |{3,1}
|{5,2,4}
|
| 1 出栈 6 入栈 |{3,6}
|{5,2,4,1}
|
| 6 出栈 |{3}
|{5,2,4,1,6}
|
| 3 出栈 7 8 入栈 |{7,8}
|{5,2,4,1,6,3}
|
| 8 出栈 |{7}
|{5,2,4,1,6,3,8}
|
| 7 出栈 9 入栈 |{9}
|{5,2,4,1,6,3,8,7}
|
| 9 出栈 |{}
|{5,2,4,1,6,3,8,7,9}
|
| 栈为空,结束遍历 | | |
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> ans;
stack<TreeNode*> st;
if (root) st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
ans.push_back(node->val);
st.pop();
if (node->right) st.push(node->right);
if (node->left) st.push(node->left);
}
return ans;
}
};
145.二叉树的后序遍历
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> ans;
stack<TreeNode*> st;
if (root) st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
ans.push_back(node->val);
st.pop();
if (node->left) st.push(node->left);
if (node->right) st.push(node->right);
}
reverse(ans.begin(), ans.end());
return ans;
}
};
94.二叉树的中序遍历
前序遍历与中序遍历的不同点在于:前序遍历每次先访问根节点,接着就把根节点的值放入遍历序列中;而中序遍历不同,访问还是从根节点开始,但先放入遍历序列中的却是最左边的子节点
基于栈的中序遍历的逻辑过程如下:[4,2,6,1,5,8,3,7,9]
- 如果当前节点不为空,就将其压入栈,并访问其左子节点
- 如果访问到空节点(例如4的左子节点就为空节点),那就弹出栈顶元素,将栈顶元素的值加入遍历序列中并访问其右子节点
- 以这个树为例,就是首先将5 2 4 都压入栈;4的左子节点为空,此时从栈中将4弹出,加入遍历序列中,然后访问其右子节点,也为空;接着从栈中将2弹出,其左子树已经遍历完毕,因此将2加入遍历序列,接着访问其右子节点1,将1和6都压入栈 …
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> ans;
stack<TreeNode*> st;
TreeNode* cur_node = root;
while (cur_node != nullptr || !st.empty()) {
if (cur_node) {
st.push(cur_node);
cur_node = cur_node->left;
} else {
cur_node = st.top();
st.pop();
ans.push_back(cur_node->val);
cur_node = cur_node->right;
}
}
return ans;
}
};
统一迭代法 代码随想录
从前文中我们发现使用迭代法实现先中后序遍历,很难写出统一的代码,不像是递归法,实现了其中的一种遍历方式,其他两种只要稍稍改一下节点顺序就可以了
由于直接使用栈无法同时解决访问节点(遍历节点)和处理节点(将元素放进结果集)不一致的情况。那我们就将访问的节点放入栈中,把要处理的节点也放入栈中,但是要对要处理的节点做标记(要处理的节点放入栈之后,紧接着放入一个空指针作为标记), 这种方法也可以叫做标记法。
中序遍历:
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;
}
};
前序遍历:
class Solution {
public:
vector<int> preorderTraversal(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); // 右
if (node->left) st.push(node->left); // 左
st.push(node); // 中
st.push(NULL);
} else {
st.pop();
node = st.top();
st.pop();
result.push_back(node->val);
}
}
return result;
}
};
后序遍历
class Solution {
public:
vector<int> postorderTraversal(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();
st.push(node); // 中
st.push(NULL);
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;
}
};
广度优先遍历
102.二叉树的层序遍历
操作 | 队列 | 序列 |
---|---|---|
5 入队 | {5} | |
5 出队 2 3 入队 | {2,3} | [[5]] |
2 3 出队 4 1 8 7 入队 | {4,1,8,7} | [[5],[2,3]] |
4 1 8 7 出队 6 9 入队 | {6,9} | [[5],[2,3],[4,1,8,7]] |
6 9 出队 | {} | [[5],[2,3],[4,1,8,7],[6,9]] |
队为空,结束 |
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> ans;
queue<TreeNode*> que;
if (root) que.push(root);
while (!que.empty()) {
int n = que.size();
vector<int> vec;
while (n--) {
TreeNode* node = que.front();
que.pop();
vec.push_back(node->val);
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
}
ans.push_back(vec);
}
return ans;
}
};