[刷题笔记] 二叉树的广度优先遍历 (递归法迭代法) 和 深度优先遍历

本文参考代码随想录二叉树章节

二叉树的基本概念

image.png
如图所示为一个二叉树,根节点为{5},叶子节点(无左右子节点)为{4,6,8,9},接下来所有的遍历过程都将以这个二叉树为例,在leetcode测试用例中,这个二叉树可以写成[5,2,3,4,1,8,7,null,null,6,null,null,null,null,9]
二叉树的遍历方式主要可以分为以下几种:

  • 深度优先遍历 - 前序遍历/ 中序遍历/ 后序遍历
    • 递归法
    • 迭代法 (基于栈)
  • 广度优先遍历 - 层序遍历 (基于队列)

对于上面这个二叉树,其遍历结果分别为:

  • 前序遍历 (中左右) [5,2,4,1,6,3,8,7,9]
  • 中序遍历 (左中右) [4,2,6,1,5,8,3,7,9]
  • 后序遍历 (左右中) [4,6,1,2,8,9,7,3,5]
  • 层序遍历 [[5],[2,3],[4,1,8,7],[6,9]]

本文主要涉及到的leetcode题目如下:

leetcode中,二叉树节点类的定义如下:

  • 属性包括 val 也即节点指向的值,left right 分别指向左右子节点
  • 构造函数有两个重载,可以定义一个空节点,或者用 值 / 值 + 左右子节点 来创建一个节点
struct TreeNode {
    int val;
    TreeNode *left;
	TreeNode *right;
    TreeNode() : val(0), left(nullptr), right(nullptr) {}
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};

深度优先遍历

递归法

所谓递归就是先递后归,以前序遍历为例::

  • 递就是将原问题分解成子问题:[原问题] 前序遍历二叉树的所有节点 = [子问题] 访问根节点 + 前序遍历根节点的左子树 + 前序遍历根节点的右子树
  • 沿着原问题一直分解,子问题越来越小,直到达到一个尽头,这个尽头就是递归的边界条件,这个返回的过程就是归
144.二叉树的前序遍历
class Solution {
public:
    void Traversal(TreeNode* root, vector<int>& ans) {
        if (root == nullptr) return;
        ans.push_back(root->val);
        Traversal(root->left, ans);
        Traversal(root->right, ans);
    }
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> ans;
        Traversal(root, ans);
        return ans;
    }
};

中序遍历 和 后序遍历 同理,只需要更换递归函数中代码的顺序即可:

94.二叉树的中序遍历

[原问题] 中序遍历二叉树的所有节点 = [子问题] 中序遍历根节点的左子树 + 访问根节点 + 中序遍历根节点的右子树

class Solution {
public:
    void Traversal(TreeNode* root, vector<int>& ans) {
        if (root == nullptr) return;
        Traversal(root->left, ans);
        ans.push_back(root->val);
        Traversal(root->right, ans);      
    }
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> ans;
        Traversal(root, ans);
        return ans;
    }
};
145.二叉树的后序遍历

[原问题] 后序遍历二叉树的所有节点 = [子问题] 后序遍历根节点的左子树 + 后序遍历根节点的右子树 + 访问根节点

class Solution {
public:
    void Traversal(TreeNode* root, vector<int>& ans) {
        if (root == nullptr) return;
        Traversal(root->left, ans);
        Traversal(root->right, ans);
        ans.push_back(root->val);
    }
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> ans;
        Traversal(root, ans);
        return ans;
    }
};

迭代法

递归法的原理就是每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,这就是递归为什么可以返回上一层位置的原因。因此我们也可以直接基于栈完成二叉树的前 中 后序遍历。

144.二叉树的前序遍历

image.png
基于栈的前序遍历过程如下:[5,2,4,1,6,3,8,7,9]

  • 首先将树的根节点压入栈
  • 每次弹出栈顶元素后,需要将弹出的栈顶元素的右子节点和左子节点(前提是不为空)先后压入栈中
    | 操作 | | 遍历序列 |
    | — | — | — |
    | 5入栈 | {5} | |
    | 5 出栈 3 2 入栈 | {3,2} | {5} |
    | 2 出栈 1 4 入栈 | {3,1,4} | {5,2} |
    | 4 出栈 | {3,1} | {5,2,4} |
    | 1 出栈 6 入栈 | {3,6} | {5,2,4,1} |
    | 6 出栈 | {3} | {5,2,4,1,6} |
    | 3 出栈 7 8 入栈 | {7,8} | {5,2,4,1,6,3} |
    | 8 出栈 | {7} | {5,2,4,1,6,3,8} |
    | 7 出栈 9 入栈 | {9} | {5,2,4,1,6,3,8,7} |
    | 9 出栈 | {} | {5,2,4,1,6,3,8,7,9} |
    | 栈为空,结束遍历 | | |
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> ans;
        stack<TreeNode*> st;
        if (root) st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            ans.push_back(node->val);
            st.pop();
            if (node->right) st.push(node->right);
            if (node->left) st.push(node->left);
        }
        return ans;
    }
};
145.二叉树的后序遍历

在这里插入图片描述

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> ans;
        stack<TreeNode*> st;
        if (root) st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            ans.push_back(node->val);
            st.pop();
            if (node->left) st.push(node->left);
            if (node->right) st.push(node->right);
        }
        reverse(ans.begin(), ans.end());
        return ans;
    }
};
94.二叉树的中序遍历

前序遍历与中序遍历的不同点在于:前序遍历每次先访问根节点,接着就把根节点的值放入遍历序列中;而中序遍历不同,访问还是从根节点开始,但先放入遍历序列中的却是最左边的子节点
基于栈的中序遍历的逻辑过程如下:[4,2,6,1,5,8,3,7,9]

  • 如果当前节点不为空,就将其压入栈,并访问其左子节点
  • 如果访问到空节点(例如4的左子节点就为空节点),那就弹出栈顶元素,将栈顶元素的值加入遍历序列中并访问其右子节点
  • 以这个树为例,就是首先将5 2 4 都压入栈;4的左子节点为空,此时从栈中将4弹出,加入遍历序列中,然后访问其右子节点,也为空;接着从栈中将2弹出,其左子树已经遍历完毕,因此将2加入遍历序列,接着访问其右子节点1,将1和6都压入栈 …

image.png

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> ans;
        stack<TreeNode*> st;
        TreeNode* cur_node = root;
        while (cur_node != nullptr || !st.empty()) {
            if (cur_node) {
                st.push(cur_node);
                cur_node = cur_node->left;
            } else {
                cur_node = st.top();
                st.pop();
                ans.push_back(cur_node->val);
                cur_node = cur_node->right;
            }
        }
        return ans;
    }
};
统一迭代法 代码随想录

从前文中我们发现使用迭代法实现先中后序遍历,很难写出统一的代码,不像是递归法,实现了其中的一种遍历方式,其他两种只要稍稍改一下节点顺序就可以了
由于直接使用栈无法同时解决访问节点(遍历节点)和处理节点(将元素放进结果集)不一致的情况。那我们就将访问的节点放入栈中,把要处理的节点也放入栈中,但是要对要处理的节点做标记(要处理的节点放入栈之后,紧接着放入一个空指针作为标记), 这种方法也可以叫做标记法。
中序遍历:

class Solution {
public:
    vector<int> inorderTraversal(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);  // 右
                st.push(node);                          // 中
                st.push(NULL); // 添加空节点标记
                if (node->left) st.push(node->left);    // 左
            } else { 
                st.pop();           
                node = st.top();    
                st.pop();
                result.push_back(node->val); 
            }
        }
        return result;
    }
};

前序遍历:

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;
    }
};

广度优先遍历

102.二叉树的层序遍历

操作队列序列
5 入队{5}
5 出队 2 3 入队{2,3}[[5]]
2 3 出队 4 1 8 7 入队{4,1,8,7}[[5],[2,3]]
4 1 8 7 出队 6 9 入队{6,9}[[5],[2,3],[4,1,8,7]]
6 9 出队{}[[5],[2,3],[4,1,8,7],[6,9]]
队为空,结束
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> ans;
        queue<TreeNode*> que;
        if (root) que.push(root);
        while (!que.empty()) {
            int n = que.size();
            vector<int> vec;
            while (n--) {
                TreeNode* node = que.front();
                que.pop();
                vec.push_back(node->val);
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
            }
            ans.push_back(vec);
        }
        return ans;
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值