【LeetCode & 剑指offer刷题】树题1:二叉树的遍历总结(前序、中序、后序、层序、 之字形层序、垂直遍历)

【LeetCode & 剑指offer 刷题笔记】目录(持续更新中...)

 

二叉树的遍历总结

(前序、中序、后序、层序、 之字形层序、垂直遍历)

 

三种递归遍历

//前序遍历(根-左-右)
void preorder(TreeNode *root, vector<int> &path)
{
    if(root == nullptr) return;
    path.push_back(root->val);
    preorder(root->left, path);
    preorder(root->right, path);
}
//中序遍历(左-根-右)
void inorder(TreeNode *root, vector<int> &path)
{
    if(root == nullptr) return;
    inorder(root->left, path);
    path.push_back(root->val);
    inorder(root->right, path);
}
//后续遍历(左-右-根)
void postorder(TreeNode *root, vector<int> &path)
{
    if(root == nullptr) return;
    postorder(root->left, path);
    postorder(root->right, path);
    path.push_back(root->val);
}
 

非递归遍历

前序遍历:(根 - 左 - 右)
 根据前序遍历访问的顺序,优先访问根结点,然后再分别访问左结点和右结点。即对于任一结点,其可看做是根结点,因此可以直接访问,访问完之后,若其左结点不为空,按相同规则访问它的左子树;当访问其左子树时,再访问它的右子树。因此其处理过程如下:
     对于任一结点P:
     1)访问结点P并将结点P入栈;
     2)判断结点P的左孩子是否为空,若为空,则取栈顶结点并进行出栈操作,并将栈顶结点的右孩子置为当前的结点P,循环至1);若不为空,则将P的左孩子置为当前的结点P;
     3)直到P为NULL并且栈为空,则遍历结束。
 
//非递归前序遍历
class Solution
{
public:
    vector<int> preorderTraversal(TreeNode* root)
    {
        vector<int> path;
        if(root == nullptr) return path;
       
        stack<TreeNode*> s;
        TreeNode *p = root;
        while(p || !s.empty())
        {
            if(p) //当左结点不为空时
            {
                path.push_back(p->val); //访问当前结点(父结点)
                s.push(p); //入栈
 
                p = p->left; //指向下一个左结点
            }
            else            //当左结点为空时
            {
                p = s.top();
                s.pop();         //出栈
 
                p = p->right;    //指向右结点
            }
        }
        return path;
    }
};
 
中序遍历:(左 - 根 - 右)
根据中序遍历的顺序,对于任一结点,优先访问其左孩子,而左孩子结点又可以看做一根结点,然后继续访问其左孩子结点,直到遇到左孩子结点为空的结点才进行访问,然后按相同的规则访问其右子树。因此其处理过程如下:
   对于任一结点P,
  1)若其左孩子不为空,则将P入栈并将P的左孩子置为当前的P,然后对当前结点P再进行相同的处理;
  2)若其左孩子为空,则取栈顶元素并进行出栈操作,访问该栈顶结点,然后将当前的P置为栈顶结点的右孩子
  3)直到P为NULL并且栈为空则遍历结束
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
//非递归中序遍历
class Solution
{
public:
    vector<int> inorderTraversal(TreeNode* root)
    {
        vector<int> path;
        if(root == nullptr) return path;
       
        stack<TreeNode*> s;
        TreeNode *p = root;
        while(p || !s.empty())
        {
            if(p) //当左结点不为空时
            {
                s.push(p); //入栈
 
                p = p->left; //指向下一个左结点
            }
            else            //当左结点为空时
            {
                p = s.top();
                path.push_back(p->val); //访问栈顶元素(父结点)
                s.pop();         //出栈
               
                p = p->right;    //指向右结点
            }
        }
        return path;
    }
};
 
后序遍历:(左 - 右 - 根)
要保证根结点在左孩子和右孩子访问之后才能访问,因此对于任一结点P,先将其入栈。
(1)如果P不存在左孩子和右孩子,则可以直接访问它;
(2)或者P存在左孩子或者右孩子但是其左孩子和右孩子都已被访问过了,则同样可以直接访问该结点
(3)若非上述两种情况,则将P的右孩子和左孩子依次入栈,这样就保证了每次取栈顶元素的时候,左孩子在右孩子前面被访问,左孩子和右孩子都在根结点前面被访问。
 
vector<int> postorderTraversal(TreeNode* root)
{
    vector<int> result;
    stack<TreeNode*> s;
    if(root== nullptr) return result;
    
    TreeNode* p; //当前结点指针
    TreeNode* pre = nullptr; //用于记录上一次访问的结点
    s.push(root);            //根结点指针入栈
    while(!s.empty())//不为空时才会入栈,故p不可能为nullptr,无需像之前加p的判断
    {
        p = s.top();   // 指向栈顶元素
        bool temp1 = p->left == nullptr && p->right == nullptr; //如果当前结点为叶子结点
        bool temp2 = pre != nullptr && (pre == p->left || pre == p->right); //或者当前结点的左结点和右结点都已被访问过了(若pre=p->left说明右结点为空,因为栈中按照根右左这样的顺序入栈,根左这种结构才能出现这种情况
        
        if(!temp1 && !temp2)//如果不是上面两种情况,直接入栈
        {
            //先将右结点入栈,再将左结点入栈,这样可以保证之后访问时先访问左结点在访问右结点
            if(p->right) s.push(p->right); //右结点入栈
            if(p->left) s.push(p->left);   //左结点入栈
        }
        else
        {
 
            result.push_back(p->val); //访问顺序:左、右、根
 
            s.pop();
            pre = p; //保存刚刚访问过的结点
        }
 
    }
    
    return result;
    
}
 
如果只是产生后序遍历序列可以用以下方法:(学习链表用于头部插入的技巧) 严格来说该方法不是按照后序遍历的顺序去访问各结点的
vector<int> postorderTraversal(TreeNode* root)
{
    list<int> temp; //开辟临时链表
    stack<TreeNode*> s; //存储各结点指针
    TreeNode* p = root;
    
    while(p || !s.empty())
    {
        while(p) //右结点不为空时
        {
            s.push(p);
            temp.push_front(p->val); //在头部插入元素,用链表比较好,和前序遍历相反
            p = p->right; //指向下一个右结点,和前序遍历相反
        }
        if(!s.empty()) //右结点为空时
        {
            p = s.top();
            s.pop(); //出栈
            p = p->left; //指向左结点,和前序遍历相反
        }
    }
    
    vector<int> result;
    copy(temp.begin(), temp.end(), back_inserter(result)); //list中元素复制到vector
    return result;
}
 
参考:
 

其他遍历方式:层序遍历、 之字形层序遍历、垂直遍历

102. Binary Tree Level Order Traversal
Given a binary tree, return the level order traversal of its nodes' values. (ie, from left to right, level by level).
For example:
Given binary tree [3,9,20,null,null,15,7],
3
/
9 20
    /
    15 7
return its level order traversal as:
[
[3],
[9,20],
[15,7]
]
 
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
//问题:按层次遍历,每层存于vector中
//方法一:递归法 O(n), O(1)
//实际上就是用的前序遍历的思想(前序遍历对每一层而言,靠左的先访问,满足层序遍历规律),区别在于每次递归传入了level信息
class Solution
{
public:
    vector<vector<int>> levelOrder(TreeNode* root)
    {
        vector<vector<int>> result; //建立可以存放vector的空容器
        traverse(root,1,result);    //从第一层开始遍历
        return result;
    }
  
    void traverse(TreeNode* root,int level,vector<vector<int>>& result)
    {
      
        if(root == NULL) return; //递归的出口(包括递归子函数的出口)
        if(level > result.size())
        {
            result.push_back(vector<int>());//在下一层时,增加空容器(因为事先不知道树的层数,故要一边遍历,一边增加容器大小)
        }
      
        result[level - 1].push_back(root->val); //将元素值push进第level层的容器(索引从0开始)
        traverse(root->left, level+1, result);
        traverse(root->right, level+1, result); //最后一个语句return之后,整个递归函数才结束
      
    }
};
 
/*
迭代法,O(n),O(1) 掌握
层序遍历二叉树是典型的广度优先搜索BFS的应用,但是这里稍微复杂一点的是,我们要把各个层的数分开,存到一个二维向量里面
用队列实现
1)首先根结点入队
2)访问队首元素,队首元素出队,若子结点不为空,子结点(下一层的所有结点)入队
3)一层一层的访问,直至队列清空
*/
class Solution
{
public:
    vector<vector<int> > levelOrder(TreeNode *root)
    {
        vector<vector<int> > res;
        if (root == nullptr) return res;
 
        queue<TreeNode*> q;
        q.push(root); //根结点入队
       
        while (!q.empty())
        {
            vector<int> level;
            int size = q.size(); //当前层的结点数,会随着每层结点的push,长度会变化
           
            for (int i = 0; i <  size; ++i) //遍历该层结点,并将下一层结点入队
            {
                TreeNode *node = q.front();
                level.push_back(node->val); //访问当前结点
                q.pop(); //出队
               
                //将当前结点的左右子结点入队
                if (node->left) q.push(node->left);
                if (node->right) q.push(node->right);
                //下一层的结点排在上一层结点之后
            }
           
            res.push_back(level);
        }
        return res;
    }
};
 
103. Binary Tree Zigzag Level Order Traversal
 
Given a binary tree, return the zigzag level order traversal of its nodes' values. (ie, from left to right, then right to left for the next level and alternate between).
For example:
Given binary tree [3,9,20,null,null,15,7],
3
/
9 20
    /
    15 7
return its zigzag level order traversal as:
[
[3],
[20,9],
[15,7]
]

 
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
//分析:广度优先遍历(??感觉遍历顺序是先序遍历,为深度优先遍历),用一个bool记录是从左到右还是从右到左,每一层结束就翻转一下
//用level-order遍历,用奇数层偶数层判断,偶数层时反向存数
class Solution
{
public:
    vector<vector<int>> zigzagLevelOrder(TreeNode* root)
    {
        vector<vector<int>> result; //创建包含子容器的容器
        traverse(root, 1, result);
        return result;
    }
  
    //递归函数的功能:按Zigzag Level Order扫描某一层(第level层)的元素,存在一个vector
    void traverse(TreeNode* root, int level, vector<vector<int>>& result)
    {
      
        if(root == NULL) return; //递归子函数和递归母函数的出口
      
        if(level > result.size()) result.push_back(vector<int>()); //增加子容器
          
        //每一个结点都可以看成本层的根节点,将当前层(第level层)的结点元素pushvector
        if(level%2 == 1) //奇数层时,从左到右遍历
        {
            result[level-1].push_back(root->val);
        }
        else    //下一层为偶数层时,反向存数
        {
            result[level-1].insert(result[level-1].begin(), root->val); //在开头插入数据
        }
      
        //从左到右存数
        traverse(root->left, level+1, result);
        traverse(root->right, level+1, result);
      
    }
  
};
/*
方法二:迭代法
按level order遍历,偶数层时翻转一下(可以用一bool型变量,每一层反号一次)
*/
class Solution
{
public:
    vector<vector<int>> zigzagLevelOrder(TreeNode *root)
    {
        vector<vector<int> > res;
        if (root == nullptr) return res;
       
        queue<TreeNode*> q;
        q.push(root); //根结点入队
        bool right_to_left = false;
      
        while (!q.empty())
        {
            vector<int> level;
            int size = q.size(); //当前层的结点数
          
            for (int i = 0; i <  size; ++i) //遍历该层结点,并将下一层结点入队
            {
                TreeNode *node = q.front();
                level.push_back(node->val); //访问当前结点
                q.pop(); //出队
              
                //将当前结点的左右子结点入队
                if (node->left) q.push(node->left);
                if (node->right) q.push(node->right);
                //下一层的结点排在上一层结点之后
            }
          
            if(right_to_left) reverse(level.begin(), level.end()); //反序
            res.push_back(level);
            right_to_left = !right_to_left;
        }
        return res;
    }
};
 

Binary Tree Vertical Order Traversal 二叉树的竖直遍历

Given a binary tree, return the vertical order traversal of its nodes' values. (ie, from top to bottom, column by column).
If two nodes are in the same row and column, the order should be from left to right.
Examples:
Given binary tree [3,9,20,null,null,15,7],
     3
    /
  9 20
       /
     15 7
return its vertical order traversal as:
[
[9],
[3,15],
[20],
[7]
]
Given binary tree [3,9,20,4,5,2,7],
_3_
/
9 20
/ /
4 5 2 7
return its vertical order traversal as:
[
[4],
[9],
[3,5,2],
[20],
[7]
]
/* 掌握
问题:二叉树的垂直遍历
方法:层序遍历,并给每个结点赋上列号(对于每列元素而言,层序遍历访问的先后顺序满足垂直遍历规律)
把根节点给个序号0,然后开始层序遍历,
凡是左子节点则序号减1,右子节点序号加1
这样我们可以通过序号来把相同列的节点值放到一起
*/
class Solution
{
public:
    vector<vector<int>> verticalOrder(TreeNode* root)
    {
        vector<vector<int>> res;
        if (!root) return res;
        
        map<int, vector<int>> m; //构建存储<序号,遍历序列>对的map
        queue<pair<int, TreeNode*>> q; //构建存储<序号,结点>对的队列
        
        q.push({0, root}); //根结点入队,根结点序号设为0
        while (!q.empty()) //层序遍历
        {
            auto a = q.front();
            m[a.first].push_back(a.second->val); //访问当前结点,将结点值push到相同列的容器中
            q.pop(); //出队
           
            
            //将下一层结点入队
            if (a.second->left) q.push( {a.first - 1, a.second->left} ); //左结点序号减一
            if (a.second->right) q.push( {a.first + 1, a.second->right} ); //右结点序号加一
            //下一层的结点排在上一层结点之后
        }
        
        for (auto mi : m)  //map中遍历序列按顺序push到结果容器中(map内部会自动排序,序号从小到大排列遍历序列)
        {
            res.push_back(mi.second);
        }
        return res;
    }
};
 
 
 
原文地址:https://www.cnblogs.com/wikiwen/p/10225766.html