二叉树遍历的理论基础
二叉树的遍历方式分为两种:
深度优先遍历:先往深处走,遇到叶子节点再往回走
广度优先遍历:一层一层遍历
深度优先遍历:前序遍历(中左右),中序遍历(左中右),后序遍历(左右中)
广度优先遍历:层序遍历
前中后序遍历可以使用递归法和迭代法,栈是实现递归的一种实现结构。
广度优先遍历使用迭代法,使用队列实现。
二叉树的定义如下:
struct TreeNode{
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x):val(x),left(NULL),right(NULL){}
};
递归的三要素
1.确定递归函数的参数和返回值
2.确定终止条件
3.确定单层递归的逻辑
leetcode 144. 前序遍历
递归法:
class Solution {
public:
void Traversal(TreeNode* root,vector<int>& res)
{
if(root==NULL) return;
res.push_back(root->val); //中
Traversal(root->left,res); //左
Traversal(root->right,res);//右
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
Traversal(root,res);
return res;
}
};
迭代法:
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*> st; //栈存储TreeNode*
vector<int> res;
if(root==NULL) return res;
st.push(root);
while(!st.empty())
{
TreeNode* cur=st.top(); //这里是TreeNode*
st.pop();
res.push_back(cur->val);
if(cur->right) st.push(cur->right); //当前弹出节点的右孩子先入栈,因为栈是先进后出
if(cur->left) st.push(cur->left); //当前弹出节点的左孩子后入栈,则弹出时先弹出
}
return res;
}
};
leetcode 145. 后序遍历
题目链接:二叉树的后序遍历
递归法:
class Solution {
public:
void Traversal(TreeNode* root,vector<int>& res) //注意res用的是&
{
if(root==NULL) return;
Traversal(root->left,res); //左
Traversal(root->right,res);//右
res.push_back(root->val); //中
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
Traversal(root,res);
return res;
}
};
迭代法:
后序遍历的迭代法是在前序遍历迭代法基础上,把入栈顺序中右左改成中左右,则出栈顺序就是中右左,再整体reverse一下就是左右中,即后序遍历。
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> res;
if(root==NULL) return res;
st.push(root);
while(!st.empty())
{
TreeNode* cur=st.top();
st.pop();
res.push_back(cur->val);
if(cur->left) st.push(cur->left); //把前序遍历的右先进栈改成左先进栈
if(cur->right) st.push(cur->right);
}
reverse(res.begin(),res.end()); //最后整体翻转
return res;
}
};
leetcode 94. 中序遍历
题目链接:二叉树的中序遍历
递归法:
class Solution {
public:
void Traversal(TreeNode* root,vector<int>& res)
{
if(root==NULL) return;
Traversal(root->left,res); //左
res.push_back(root->val); //中
Traversal(root->right,res);//右
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
Traversal(root,res);
return res;
}
};
迭代法:
中序遍历的迭代法与其他遍历不同。
1.前序遍历有两个操作:把元素放进result里;遍历节点。前序遍历的顺序是中左右,先访问中间节点,要处理的也是中间节点,访问元素和要处理的元素是一致的
2.中序遍历的顺序是左中右,先访问二叉树的顶部节点,然后层层往下访问,知道访问到树左面的最底部,再出路节点,因此访问顺序和处理顺序是不一致的。
3.中序遍历的迭代法,组要借助指针遍历来访问节点,用栈处理节点上的元素
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> st;
TreeNode* cur=root; //通过指针cur遍历节点
while(cur!=NULL || !st.empty()) //当cur=NULL&&st.empty()为真时循环结束
{
if(cur!=NULL)
{
st.push(cur); //cur没有找到左边最底部,则不断把元素压入栈中
cur=cur->left; //cur一直往左走
}
else //出现cur为空,则开始处理元素
{
cur=st.top(); //弹出的节点赋值给cur,通过cur去寻找右孩子
st.pop();
res.push_back(cur->val);
cur=cur->right; //通过cur寻找右孩子
}
}
return res;
}
};
迭代法的统一格式
迭代法实现的前中后序,风格不是很统一,中序遍历和前后序遍历风格不一致,一会用栈遍历,一会用指针遍历。
中序遍历无法同时解决访问节点(遍历节点)和处理节点(将元素放入结果集)不一致的情况。那我们将访问的节点放入栈中,把要处理的节点也放入栈中但是需要作标记。
迭代法的前中后序遍历,写出统一的风格,需要采用标记法,即将要处理的节点放入栈中后,紧接着放入一个空指针作为标记。
中序遍历:
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
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();
res.push_back(node->val); // 加入到结果集
}
}
return res;
}
};
前序遍历:
与中序遍历相比,仅仅只改变了两行代码的顺序
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;
}
};