二叉树遍历
二叉树遍历是经常会遇到的数据结构面试题,小白在此分享一种通用方法。尽量让大家可以“一法通,万法通”
递归遍历
关于递归,可以首先假设已经有这样的一个现成的、和当下正在编写函数同型的函数可供我们调用,也就是可以在函数内部调用自己本身。因此,写递归函数的话,只需要缕清原问题和子问题的递归逻辑,以及递归终止条件(不然的话岂不是“子子孙孙无穷尽也了”?),就可以写出相应的代码啦。各种递归遍历方式的逻辑是一样的,这里以后序遍历为例,首先先要得到左子树的后序遍历结果,而后是右子树的后序遍历结果,最后再遍历当前节点。关于左、右子树的后序遍历结果到底是什么?不要忘了我们正在编写函数是干什么的?就是后序遍历树呀,所以只需要把左右子树当作出入参数传入我们的函数中,并将结果按后序的逻辑来整合就好,以下是递归遍历的C++代码。
class Solution {
public:
/*后序遍历*/
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res{};
if(!root) return res;//递归终止条件
vector<int> ls=postorderTraversal(root->left);
res.insert(res.end(),ls.begin(),ls.end());
ls=postorderTraversal(root->right);
res.insert(res.end(),ls.begin(),ls.end());
res.insert(res.end(),root->val);
return res;
}
};
class Solution {
public:
/*中序遍历*/
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res{};
if(root==nullptr) return res;
vector<int> ls=inorderTraversal(root->left);
res.insert(res.end(),ls.begin(),ls.end());
res.insert(res.end(),root->val);
ls=inorderTraversal(root->right);
res.insert(res.end(),ls.begin(),ls.end());
return res;
}
};
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res{};
if(root==nullptr) return res;
res.insert(res.end(),root->val);
vector<int> ls=preorderTraversal(root->left);
res.insert(res.end(),ls.begin(),ls.end());
vector<int> rs=preorderTraversal(root->right);
res.insert(res.end(),rs.begin(),rs.end());
return res;
}
};
迭代遍历
递归遍历是由系统维护一个递归栈,迭代遍历便是我们自己去显示维护栈。同样以后序遍历为例,首先不要忘记边界条件:如果输入为空树,直接返回一个空向量即可。
将根节点放入我们的堆栈,将迭代过程一般化,首先将当前节点从堆栈取出但不弹出,判断节点是否为空指针,如果非空,因为是后序遍历,读取顺序为左—右—根,所以入栈顺序正好相反,为根—右—左,由于此时根节点已经在栈顶,所以只需要先后将非空右节点、非空左节点放入即可,为了将遍历结果能正确存入向量,我们此处紧跟根节点之后填入一个空指针,后续判断时,一旦遇到空指针,我们就知道后续的数据是我们需要打印的啦。
接下来就是如果当前取出的节点是空指针是,那么我们就先弹出空指针,并在读取新的栈顶数据后弹出即可。
将整个过程循环,知道栈重新为空,则终止循环。以下是二叉树迭代后序遍历C++版代码。
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
if(!root) return {};
vector<int> res{};
stack<TreeNode* > TNs{};
TNs.push(root);
//迭代法
while(!TNs.empty()){//直到栈空
TreeNode* cur=TNs.top();
if(cur){//人为在适当的位置放入空节点
TNs.push(nullptr);
if(cur->right) TNs.push(cur->right);
if(cur->left) TNs.push(cur->left);
}else{
TNs.pop();
res.insert(res.end(),TNs.top()->val);
TNs.pop();
}
}
return res;
}
};
对于中序遍历,只需要修改上面当前节点非空时填入堆栈的顺序即可。因为是中序遍历,读取顺序为左—根—右,所以入栈顺序正好相反,为右—根—左,由于此时根节点已经在栈顶,所以需要先弹出根节点,而后将非空右节点填入堆栈,之后再将根节点填入,同样,为了将遍历结果能正确存入向量,我们此处紧跟根节点之后填入一个空指针,后续判断时,一旦遇到空指针,我们就知道后续的数据是我们需要打印的啦。最后将非空左节点放入。以下是中序遍历时,填入堆栈的关键代码,其他部分同后序遍历一致。
TNs.pop();
if(cur->right) TNs.push(cur->right);
TNs.push(cur);
TNs.push(nullptr);
if(cur->left) TNs.push(cur->left);
对于前序遍历,同样只需要修改上面当前节点非空时填入堆栈的顺序即可。因为是前序遍历,读取顺序为根—左—右,所以入栈顺序正好相反,为右—左—根,由于此时根节点已经在栈顶,所以需要先弹出根节点,而后依次将非空右节点、非空左节点填入堆栈,最后再将根节点填入,同样,为了将遍历结果能正确存入向量,我们此处紧跟根节点之后填入一个空指针,后续判断时,一旦遇到空指针,我们就知道后续的数据是我们需要打印的啦。最后。以下是前序遍历时,填入堆栈的关键代码,其他部分同后序遍历一致。
TNs.pop();
if(cur->right) TNs.push(cur->right);
if(cur->left) TNs.push(cur->left);
TNs.push(cur);
TNs.push(nullptr);
最后把用队列实现二叉树层次遍历的代码也附上吧,关键是扩容条件的决定。
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
if(!root) return {};
vector<vector<int> > res{};
queue<TreeNode* > TNq;
TNq.push(root);
while(!TNq.empty()){
int siz=TNq.size();//利用队列大小来决定是否扩容
vector<int> temp{};
for(int i=0;i<siz;i++){
TreeNode* cur=TNq.front();
temp.push_back(cur->val);
TNq.pop();
if(cur->left) TNq.push(cur->left);
if(cur->right) TNq.push(cur->right);
}
res.push_back(temp);
}
return res;
}
};
欢迎大家批评指正!