题目来自leetcode:
前序: 前序遍历
中序: 中序遍历
后序: 后序遍历
1、颜色标记法
颜色标记法兼具栈迭代方法的高效,又像递归方法一样简洁易懂,更重要的是,这种方法对于前序、中序、后序遍历,能够写出完全一致的代码。
核心思想如下:
- 使用颜色标记节点的状态,新节点为白色(0),已访问的节点为灰色(1)。
- 如果遇到的节点为白色,则将其标记为灰色,然后将其右子节点、自身、左子节点(中序)依次入栈。
- 如果遇到的节点为灰色,则将节点的值输出。
中序遍历:
vector<int> inorderTraversal(TreeNode* root) {
using PIT = pair<int, TreeNode*>;
vector<int> res;
stack<PIT> ista({{0, root}});
while(ista.size()){
auto [type, node] = ista.top(); ista.pop();
if(type == 0){
if(node->right) ista.push({0, node->right});
ista.push({1, node});
if(node->left) ista.push({0, node->left});
}else{
res.push_back(node->val);
}
}
return res;
}
栈是一种 先进后出
的结构,因此入栈顺序必须调整为遍历顺序的倒序。
如果是前序遍历,入栈顺序为 右,左,中
;
后序遍历,入栈顺序中,右,左
2、常规迭代
1)先序遍历
方法一(较简单)
访问栈顶节点,并将右子树、左子树按顺序入栈(先右再左,左子树比右子树先出栈)。左子树访问完才会访问右子树,各节点按照访问顺序分别入栈,转到第一步。
简化:
while(栈非空){
栈顶出栈、访问->右子树进栈->左子树进栈
}
【TIP】空节点入栈和不入栈分别有一种写法
//左右子树都入栈,空节点不入栈
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
if(!root) return res;
stack<TreeNode*> Stack;
Stack.push(root);
while(!Stack.empty())
{
TreeNode* curNode=Stack.top();
res.push_back(curNode->val);
Stack.pop();
if(curNode->right)
Stack.push(curNode->right);
if(curNode->left)
Stack.push(curNode->left);
}
return res;
}
};
//另一种写法,空节点入栈,可以处理空节点
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
if(!root) return res;
stack<TreeNode*> Stack;
Stack.push(root);
while(!Stack.empty())
{
TreeNode* curNode=Stack.top();
res.push_back(curNode->val);
Stack.pop();
if(curNode==NULL)
continue;
Stack.push(curNode->right);
Stack.push(curNode->left);
}
return res;
}
};
方法二(通用法)
内循环:访问当前节点并将右子树入栈,往左子树方向一直访问下去。左子树访问到底退出循环,进入外循环。
外循环:将栈顶右子树取出,转到上一步。
因为左链已经访问完毕,所以只需将右子树入栈。
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*> S;
vector<int> v;
TreeNode* rt = root;
while(rt || S.size()){
while(rt){
S.push(rt->right); //NULL有可能入栈,但并不影响
v.push_back(rt->val);
rt=rt->left;
}
rt=S.top();
S.pop();
}
return v;
}
}
方法二修改版
用左链代替右子树入栈,出栈时,用节点右子树代替出栈节点。
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*> S;
vector<int> v;
TreeNode* rt = root;
while(rt || S.size()){
while(rt){
S.push(rt);
v.push_back(rt->val);
rt=rt->left;
}
rt=S.top();
S.pop();
rt=rt->right;//rt可能为NULL,但并不影响
}
return v;
}
2)中序遍历
方法一
因为中序遍历得先访问左节点,所以沿左侧分支寻找最左节点时并没有访问根节点。因此,需要将根节点入栈(后进先出),出栈时同时访问右节点(将右节点设为根节点,完成此子任务)。
PS:由先序遍历方法二第二个版本推广得到,只有几行有变化。
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
stack<TreeNode*> S;
vector<int> v;
TreeNode* rt = root;
while(rt || S.size()){
while(rt){
S.push(rt);
rt=rt->left;
}
rt=S.top();
S.pop();
v.push_back(rt->val);
rt=rt->right; //转向右子树(可能为空,但不影响)
}
return v;
}
)
先序中序对比
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
stack<TreeNode*> S;
vector<int> v;
TreeNode* rt = root;
while(rt || S.size()){
while(rt){
S.push(rt);
//先序遍历:v.push_back(rt->val);
rt=rt->left;
}
rt=S.top();
S.pop();
//中序遍历:v.push_back(rt->val);
rt=rt->right; //转向右子树(可能为空,但不影响)
}
return v;
}
)
方法二
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
stack<TreeNode*> s1;
vector<int> res;
if(root!=NULL){
while(root!=NULL || !s1.empty()){
if(root!=NULL){
s1.push(root);
root=root->left;
}
else{
root=s1.top();
res.push_back(root->val);
s1.pop();
root=root->right;
}
}
}
return res;
}
};
3)后序遍历
方法一
将前序遍历的中左右变为中右左,然后再将中右左反向!
反向即为先进后出,用第二个栈实现!!!
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> s1;
stack<TreeNode*> s2;
vector<int> res;
if(!root) return res;
s1.push(root);
while(!s1.empty()){
root=s1.top();
s1.pop();
s2.push(root);
if(root->left)
s1.push(root->left);
if(root->right)
s1.push(root->right);
}
while(!s2.empty()){
res.push_back(s2.top()->val);
s2.pop();
}
return res;
}
};
方法二(辅助标记)
由中序遍历推广得到,前半部分几乎一样。
不同点在于:沿左链入栈到左链最低点后,并没有直接弹出栈顶,而是
先判断:
该节点是否有右节点 或者 右节点是否访问完毕
若右节点访问完毕 或者 无右节点,才开始访问操作:
弹出并访问该节点,并把该节点赋值给pre(辅助标记,表示前一个被访问的节点),令节点为空,开始对下一个栈顶的判断。
若右节点没有被访问(pre不是右节点):将右节点独立为子任务,遍历该子任务。
class Solution {
public:
stack<TreeNode*> mystack;
vector<int> ans;
TreeNode* curr = root;
TreeNode* pre = NULL;//辅助标记:前一个被访问的节点
while(curr || !mystack.empty())
{
while(curr)
{
mystack.push(curr);
curr = curr->left;
}
curr = mystack.top();
//若前一个访问的是右节点(已访问过)或者没有右节点,则访问该节点
if(!curr->right || pre == curr->right){
mystack.pop();
ans.push_back(curr->val);
pre = curr;//将该节点设为前一个访问的节点
curr = NULL;
}else{//右节点还没有被访问,则将右节点独立为子任务进行遍历
curr = curr->right;
pre = NULL;
}
}
return ans;
}
3、递归版本
根据遍历顺序,分别把遍历函数放在左右子树递归语句前中后即可。
//前序:
class Solution {
public:
vector<int> vec;
vector<int> preorderTraversal(TreeNode* root) {
if(root==NULL) return vec;
vec.push_back(root->val);
if(root->left) preorderTraversal(root->left);
if(root->right) preorderTraversal(root->right);
return vec;
}
};
//中序:
class Solution {
public:
vector<int> vec;
vector<int> preorderTraversal(TreeNode* root) {
if(root==NULL) return vec;
if(root->left) preorderTraversal(root->left);
vec.push_back(root->val);
if(root->right) preorderTraversal(root->right);
return vec;
}
};
//后序:
class Solution {
public:
vector<int> vec;
vector<int> preorderTraversal(TreeNode* root) {
if(root==NULL) return vec;
if(root->left) preorderTraversal(root->left);
if(root->right) preorderTraversal(root->right);
vec.push_back(root->val);
return vec;
}
};
层序遍历
队列
class Solution {
public:
int findBottomLeftValue(TreeNode* root) {
int res=root->val;
queue<TreeNode*> bfsque;
bfsque.push(root);
while(!bfsque.empty()){
res=bfsque.front()->val;
for(int i=bfsque.size();i>0;i--){
TreeNode* tmp=bfsque.front();
bfsque.pop();
if(tmp->left) bfsque.push(tmp->left);
if(tmp->right) bfsque.push(tmp->right);
}
}
return res;
}
};
非层次遍历做法:
class Solution {
public:
int maxdepth=-1;
int res=0;
int findBottomLeftValue(TreeNode* root) {
dfs(root,0);
return res;
}
void dfs(TreeNode* root, int depth){
if(!root) return;
if(depth>maxdepth){
res=root->val;
maxdepth=depth;
}
dfs(root->left,depth+1);
dfs(root->right,depth+1);
}
};