二叉树的各种遍历方法
最近在学习二叉树的题目,先从二叉树的遍历开始入手。
二叉树的遍历主要分为深度优先遍历(DFS)和宽度优先遍历(DFS),在树中的话,先序、中序、后续遍历都属于深度优先遍历,层次遍历属于宽度优先遍历。
深度优先遍历
先序、中序、后续遍历的序取决于根在遍历中的访问次序。本文在这一部分会讲解递归和迭代两种实现方法。(递归和迭代都是循环,两者的区别是递归每次调用函数本身做循环,迭代则是调用函数中的一段代码做循环)
先序遍历
先序遍历属于深度优先遍历,按照“根-左-右”的顺序访问树中结点。
递归法
很简单,没啥好说的。
//递归法
class Solution {
public:
void Traversal(TreeNode* cur, vector<int>& sum) {
if (cur == nullptr)
return;
sum.push_back(cur->val); //根
Traversal(cur->left, sum); //左
Traversal(cur->right, sum); //右
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
Traversal(root, result);
return result;
}
};
迭代法
使用迭代法的话需要容器来存放树中结点,由于是深度遍历,贯穿到底再逐层回来,所以使用栈来存放结点信息可以满足这种需求。需要注意的是由于是栈,所以存放结点时先存放右节点,再存放左节点。
//迭代法
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> cur;
if (root == nullptr)
return result;
cur.push(root);
while (!cur.empty()) {
TreeNode * node = cur.top();
result.push_back(node->val);
cur.pop();
if (node->right != nullptr) { //由于用栈存放结点,所以先存右节点
cur.push(node->right);
}
if (node->left != nullptr) { //再存左节点,保证出栈顺序是左到右
cur.push(node->left);
}
}
return result;
}
};
中序遍历
中序遍历的访问顺序是“左-根-右”。递归法与先序基本相同,不在赘述。主要是迭代法有所不同,如果单纯的将先序的迭代中的访问结点的代码放到右节点入栈与左节点入栈之间的话,会发现图中的结点会先访问6号结点,然后访问2号,然后返回根0号,遍历顺序会完全混乱。
所以中序遍历的迭代法需要先一路找到树中最左边的结点,压入沿途的结点,然后访问该结点,将其出栈,然后去访问其根节点的右侧子树。
```cpp
//迭代法
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
if (root == nullptr) return result;
TreeNode * node = root;
while (!st.empty() || node != nullptr) {
if (node != nullptr) {
st.push(node);
node = node->left;
} else {
node = st.top();
result.push_back(node->val);
st.pop();
node = node->right;
}
}
return result;
}
};
后序遍历
中序遍历的访问顺序是“左-右-根“。代码与前序遍历的代码基本一样,不再赘述。
统一写法的迭代法
统一写法的迭代法思想是使用空指针nullptr来标记根节点,这样每个结点到头时都会有空指针做标记,后续结点的访问后依次从栈中弹出即可访问整棵树。栈中元素及结果数组如下图所示(根的左子树和右子树的具体过程有省略):
先序遍历的代码:
//前序遍历的统一写法的迭代法
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(); //遇到栈顶为空指针时弹出此节点
result.push_back(st.top()->val);
st.pop();
}
}
return result;
}
};
层次遍历
层次遍历即逐层的遍历树中的节点,层中节点从左到右、从上到下遍历,使用队列才存储节点可以满足这种要求。
递归法
递归法其实不需要队列就可以实现,递归法通过在创建行大小等于树层数的二维数组来遍历,先创建不同层的一维数组,然后在不同层对应的一维数组中添加节点数据。
//递归法
class Solution {
public:
void Order(TreeNode* cur, vector<vector<int>> & result, int depth) {
if (cur == nullptr)
return;
if (result.size() == depth)
result.push_back(vector<int>()); //创建不同层的一维数组
result[depth].push_back(cur->val); //在不同层对应的一维数组中添加结点数据
Order(cur->left, result, depth + 1);
Order(cur->right, result, depth + 1);
}
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> result;
queue<TreeNode*> que;
int depth = 0;
Order(root, result, depth);
return result;
}
};
迭代法
迭代法需要使用队列,队列里每次存储每一层的节点,每次循环中都会访问完队列中节点然后清空,并且插入队列中节点的子节点,产生新的队列。具体代码如下:
//迭代法
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> result;
queue<TreeNode*> que; //que队列存储同一层的节点
if (root != nullptr)
que.push(root);
while (!que.empty()) {
vector<int> vec;
int size = que.size(); //队列大小会变化
for (int i = 0; i < size; i++) {
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); //压入下一层的节点
}
result.push_back(vec);
}
return result;
}
};
本文章只是自己的学习过程,其代码和思想均出自代码随想录,本文只是二次加工
代码随想录网站链接: https://www.programmercarl.com