• 二叉树的前序、中序、后序、层序遍历


    参考内容:

    五分钟让你彻底理解二叉树的非递归遍历

    Python实现二叉树的非递归遍历

    二叉树遍历——深度优先(前中后序)+广度优先(层序遍历)

    构造二叉树

    定义二叉树结构如下

    struct node
    {
        int data;
        node *left;
        node *right;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    构造如下形态二叉树

    node *init_tree()
    {
        node *node1 = (node *)malloc(sizeof(node));
        node *node2 = (node *)malloc(sizeof(node));
        node *node3 = (node *)malloc(sizeof(node));
        node *node4 = (node *)malloc(sizeof(node));
        node *node5 = (node *)malloc(sizeof(node));
        node *node6 = (node *)malloc(sizeof(node));
        node *node7 = (node *)malloc(sizeof(node));
        node *node8 = (node *)malloc(sizeof(node));
        
        node1->data = 1;
        node2->data = 2;
        node3->data = 3;
        node4->data = 4;
        node5->data = 5;
        node6->data = 6;
        node7->data = 7;
        node8->data = 8;
    
        node1->left = node2;
        node1->right = node3;
        
        node2->left = node4;
        node2->right = node5;
        
        node3->right = node6;
        
        node5->left = node7;
        node5->right= node8;
    
        return node1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    前序遍历(递归)

    前序遍历顺序为根左右。要遍历整个二叉树我们就需要遍历二叉树的每一个子树,对于任何一个子树它的遍历方式均为根左右顺序遍历。即所有子问题均与父问题除规模大小不同外,其余均相同。所以可以采用递归方式实现前序遍历。

    // 前序遍历 根左右
    void pre_order_traversal(node *root)
    {
        if(root) {
            cout<<root->data<<" ";
            pre_order_traversal(root->left);
            pre_order_traversal(root->right);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    遍历结果为:1 2 4 5 7 8 3 6

    中序遍历(递归)

    中序遍历顺序为左根右。其与前序遍历仅顺序不同,其余均相同。

    // 中序遍历 左根右
    void in_order_traversal(node *root)
    {
        if(root) {
            in_order_traversal(root->left);
            cout<<root->data<<" ";
            in_order_traversal(root->right);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    遍历结果为:4 2 7 5 8 1 3 6

    后序遍历(递归)

    后序遍历顺序为左右根。其与前序、中序遍历仅顺序不同,其余均相同。

    // 后序遍历 左右根
    void post_order_traversal(node *root)
    {
        if(root) {
            post_order_traversal(root->left);
            post_order_traversal(root->right);
            cout<<root->data<<" ";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    遍历结果为:4 7 8 5 2 6 3 1

    前序遍历方法一(非递归)

    因为递归实际上是由系统帮我们进行压栈,所以理论上所有递归算法都可以改为循环+栈实现,那么我们先照着上述前序遍历的样子修改为循环+栈的形态。需要注意的是由于栈先进后出的特性,为了保证左孩子在右孩子前被访问,所以应该先右孩子入栈,再左孩子入栈。

    // 前序遍历 根左右
    void pre_order_traversal(node *root)
    {
        stack<node *> s;
        s.push(root);
    
        while(!s.empty()) {
    
            node *cur = s.top();
            s.pop();
    
            if(cur) {
                cout<<cur->data<<" ";
                s.push(cur->right);
                s.push(cur->left);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    遍历结果为:1 2 4 5 7 8 3 6

    前序遍历方法二(非递归)

    现在我们换一种思路来实现前序非递归遍历,仔细观察前序遍历的递归调用过程。

    1. 先把从根结点开始的所有左子树放入栈中;
    2. 弹出栈顶元素
    3. 如果栈顶元素有右子树,那么右子树入栈
    4. 重复上述过程直到栈为空

    因此我们可以写出遍历代码

    // 前序遍历 根左右
    void pre_order_traversal(node *root)
    {
        stack<node *> s;
        node *cur = root;
    
        while(cur || !s.empty()) {
            // 将左子树全部入栈
            while(cur) {
                cout<<cur->data<<" ";
                s.push(cur);
                cur = cur->left;
            }
    
            if(!s.empty()) {
                cur = s.top();
                s.pop();
                cur = cur->right;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    遍历结果为:1 2 4 5 7 8 3 6

    中序遍历(非递归)

    有了前面的基础,我们再来考虑中序遍历,会发现中序遍历与前序遍历只是打印结点的位置不一样。前序遍历是在结点入栈时打印,中序遍历只需要替换为在结点出栈时打印即可。

    // 中序遍历 左根右
    void in_order_traversal(node *root)
    {
        stack<node *> s;
        node *cur = root;
    
        while(cur || !s.empty()) {
            // 将左子树全部入栈
            while(cur) {
                s.push(cur);
                cur = cur->left;
            }
    
            if(!s.empty()) {
                cur = s.top();
                cout<<cur->data<<" ";
                s.pop();
                cur = cur->right;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    遍历结果为:4 2 7 5 8 1 3 6

    后序遍历方法一(非递归)

    后序遍历相对来说显得更加复杂了。在前序和中序遍历中,只要左子树处理完毕实际上栈顶元素就可以出栈了,但后序遍历需要把左子树和右子树都处理完毕才能出栈,显然我们需要某种方法记录遍历的过程。

    实际上我们只需要记录下遍历的前一个结点就能解决问题,因为通过前一个结点我们可以做如下判断:

    1. 如果前一个结点是当前结点的右子树,那么说明右子树已经遍历完毕可以出栈了
    2. 如果前一个结点是当前结点的左子树而且当前结点没有右子树,那么说明可以出栈了
    3. 如果当前结点即没有左子树也没有右子树,即为叶子结点,那么说明可以出栈了

    若不属于上述情况,则依次将当前结点的右孩子和做孩子入栈,这样就能保证每次取栈顶元素时,左孩子都在右孩子前面被访问,左孩子和右孩子都在父结点前面被访问。

    // 后序遍历 左右根
    void post_order_traversal(node *root)
    {
        stack<node *> s;
        node *pre = NULL;
        node *cur = root;
    
        s.push(cur);
    
        while(!s.empty()) {
            cur = s.top();
            // 叶子结点
            if((!cur->left && !cur->right) // 叶子结点
            || pre == cur->right // 前一个结点为当前结点右子树
            || (pre == cur->left && !cur->right)) { // 前一个结点为当前结点左子树,且没有右子树
                cout<<cur->data<<" ";
                pre = cur;
                s.pop();
            } else {
                if(cur->right)
                    s.push(cur->right);
    
                if(cur->left)
                    s.push(cur->left);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    遍历结果为:4 7 8 5 2 6 3 1

    后序遍历方法二(非递归)

    后序遍历的顺序是左右根,如果把这个顺序倒过来就是根右左,是不是发现和前序遍历很像?那么我只需要按照根右左的方式遍历完,然后将遍历结果掉一个个儿就可以,而栈就具备掉个儿的功能,因此可写出如下代码。

    // 后序遍历 左右根
    void post_order_traversal(node *root)
    {
        stack<node *> s;
        stack<int> ans;
        node *cur = root;
    
        while(cur || !s.empty()) {
            // 将左子树全部入栈
            while(cur) {
                ans.push(cur->data);
                s.push(cur);
                cur = cur->right;
            }
    
            if(!s.empty()) {
                cur = s.top();
                s.pop();
                cur = cur->left;
            }
        }
    
        while(!ans.empty()) {
            cout<<ans.top()<<" ";
            ans.pop();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    遍历结果为:4 7 8 5 2 6 3 1

    层序遍历

    层序遍历即广度优先遍历,使用队列即可实现。

    // 层序遍历
    void breadth_first_order_traversal(node *root)
    {
        queue<node *> q;
        q.push(root);
    	while(!q.empty()){
    		node *cur = q.front();
    		q.pop();
    		if(cur){
    			cout<<cur->data<<" ";
    			q.push(cur->left);
    			q.push(cur->right);
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    遍历结果为:1 2 3 4 5 6 7 8

  • 相关阅读:
    Java——Spring面向切面编程(详解AOP和OOP的区别)
    敏捷DevOps专家王立杰:端到端DevOps持续交付的5P法则 | IDCF
    阿里云消息团队创新论文被软件工程顶会 FM 2024 录用
    09.2. 长短期记忆网络(LSTM)
    C-1练习题
    Latex论文排版
    Java面试之数据类型
    npm和yarn的一些命令
    【RocketMQ】消息的拉取总结
    超越 Transformer开启高效开放语言模型的新篇章
  • 原文地址:https://blog.csdn.net/heuguangxu/article/details/134276837