二叉树遍历----从零开始的数据结构

一. 前言:

二叉树遍历可以分为 "递归遍历" (DFS), "层序遍历" (BFS)

递归遍历分为三个位置 : 前序遍历,中序遍历,后序遍历

层序遍历则 有三种写法-----对应三种不同的场景

具体内容且听我娓娓道来。

二.递归遍历:

以下是简单的递归遍历二叉树的代码:

cpp 复制代码
#include<iostream>
using namespace std;

class TreeNode{
public:
   int val;
   TreeNode* left;
   TreeNode* right;

   TreeNode(int val) : left(nullptr),right(nullptr) , val(val){}
};//用结构体也是一样的


void traverse(TreeNode* root){
    if (root == nullptr){
        return;
    }

    traverse(root->left);
    traverse(root->right);
}//就是一个游走的指针

这个遍历是从左到右来遍历子节点

正常来说二叉树遍历都是先左后右来遍历子节点,少见的先右后左来遍历子节点

可以看以下图:

假设二叉树中储存了这些节点,

那么我上面遍历的代码的遍历顺序就是 : 1 - 》 2 - 》 4 -》 5 -》 3 -》 6 ---》 7

先走完左边,当root等于nullptr的时候,就返回上一个节点然后接着走右边

• 前 / 中 / 后序遍历:

区分前、中、后序遍历的关键在于根节点被访问(输出/处理)的时机,相对于左子树和右子树的遍历顺序。

  • 前序遍历 :先访问根节点,再遍历左子树,最后遍历右子树。

    顺序:根 → 左 → 右

  • 中序遍历 :先遍历左子树,再访问根节点,最后遍历右子树。

    顺序:左 → 根 → 右

  • 后序遍历 :先遍历左子树,再遍历右子树,最后访问根节点。

    顺序:左 → 右 → 根

++前序位置的代码会在进入节点时立即执行;中序位置的代码会在左子树遍历完成后,遍历右子树之前执行;后序位置的代码会在左右子树遍历完成后执行:++

按我个人的理解:

通过不同序列的遍历: 之后返回的节点就可以得到对应的信息。

前序: 子节点可以知道负节点的信息

中序: "根"(子树的根也算)节点可以知道 返回后左子树的信息

后序: "根" 节点 知晓左右子树的所有信息

那么我们应该如何实现呢?

cpp 复制代码
// 二叉树的遍历框架
void traverse(TreeNode* root) {
    if (root == nullptr) {
        return;
    }
    // 前序位置
    traverse(root->left);
    // 中序位置
    traverse(root->right);
    // 后序位置
}

左子节点递归函数前: 前序

左,右节节递归函数中: 中序

右子节点递归函数后 : 后序

补充: BST二叉遍历树的中序是有序的:

如图上所示,会按"从小到大"开始排列

小总结: 递归遍历是靠程序的"堆和栈"实现的,而层序遍历则是靠 "队列"来协助遍历

三.层序遍历:

假设二叉树存储了如上图的元素,那么遍历代码如下:

• 写法一: 最简单,不在乎树的层数

cpp 复制代码
#include<iostream>
#include<queue>
using namespace std;

class TreeNode{
  public:
    int val;
    TreeNode* left;
    TreeNode* right;

    TreeNode(int val) : val(val), left(nullptr) , right(nullptr){}
    
};

void levelorderTraverse(TreeNode* root){
     if (root == nullptr) return;
      
     queue<TreeNode*> q;
     q.push(root);
     while(!q.empty()){
        TreeNode* cur = q.front();
        q.pop();
        cout << cur->val << endl;//模仿遍历之后的操作

        if (cur->left != nullptr){
            q.push(cur->left);
        }
        if (cur->right != nullptr){
            q.push(cur->right);
        }
     }
}

利用队列q,储存遍历的节点 "左右子节点",然后一层层遍历下去

利用队列的 "先进先出"性质,我们便能够实现层层遍历

• 写法二: 最常用,可以记录树的深度

cpp 复制代码
#include<iostream>
#include<vector>
#include<queue>
using namespace std;

class TreeNode{
   public:
     int val;
     TreeNode* left;
     TreeNode* right;

     TreeNode(int x): val(x), left(nullptr) , right(nullptr) {}
};


vector<int> levelOrderTraverse(TreeNode* root){
      if (root == nullptr) return{};
      
      vector<int> res; //answer数组

      queue<TreeNode*> q;
      q.push(root);
      int depth = 1;//树的深度,根节点是一
      
      while(!q.empty()){
        int size = q.size(); //这一层的个数
        for (int i = 0; i < size; i++){
            TreeNode* cur = q.front();
            q.pop();
            res.push_back(cur->val);


            if (cur->left){
                q.push(cur->left);
            }
            if (cur->right){
                q.push(cur->right);
            }
        }

        depth++;
      }
      return res;
}

for循环是遍历每一层的节点,当然可以用while改写

cpp 复制代码
int size = q.size();
while(size--){
  // ......
}

depth用于记录当前的层数,根节点当作第一层

• 写法三: 适用于每个节点权重不同的复杂场景

++深度 就是 路径权重值 ,++ 后面这个概念会在图的时候详细讲述

每一个节点的权重都可以是任意值,不再是同写法二中每一个路径权重都是1,那么每一层的路径权重都是相同的

为了实现写法三权重不同的情况,聪明的读者已经想到用类或者结构体来封装这个权重了

cpp 复制代码
#include<iostream>
#include<queue>
using namespace std;

class TreeNode{
    public:
    int val;
    TreeNode* left;
    TreeNode* right;

    TreeNode(int x): val(x),left(nullptr) , right(nullptr) {}
};


class state{
public:
   TreeNode* node;
   int depth;//权重

   state(TreeNode* node,int depth) : node(node), depth(depth){}
};

void levelOrderTraverse(TreeNode* root){
    if (root == nullptr) return;

    queue<state> q; //如果在堆区的话,就会是 state*
    //假设根节点路径权重是1
    q.push(state(root,1));
    
    while(!q.empty()){
        state cur = q.front();
        q.pop();
        cout << "depth = " << cur.depth << endl;//模仿遍历后要做的操作

        if (cur.node->left){
            q.push(state(cur.node->left,cur.depth+1));
        }
        if (cur.node->right){
            q.push(state(cur.node->right,cur.depth+1));
        }

    }
}

总结:

1. 写法一:最简单,但功能受限

代码特征 :直接 while(!q.empty()),每次取出队头节点。 适用场景

  • 只需要按顺序访问所有节点,不需要知道节点所在的层数(深度)
  • 例如:简单的二叉树节点值打印、查找某个值是否存在(且不关心深度)。 缺点:无法区分哪些节点属于同一层,因此无法解决与"层"或"深度"相关的问题。
2. 写法二:最常用,可记录深度

代码特征 :在 while 循环内部增加一个 for 循环,利用 int sz = q.size() 一次性处理完当前层的所有节点。 适用场景

3. 写法三:最灵活,适用于复杂权重场景

代码特征 :定义一个 State 结构体/类,将节点指针和附加信息(如 depth、路径权重和等)绑定在一起存入队列。 适用场景

  • 树枝带有权重,且同一层节点的路径权重和不同。
  • 需要维护每个节点的独立状态,而不仅仅是全局的层数。
  • 典型场景:
    • 图的 BFS 遍历
    • BFS 暴力穷举算法框架 中的复杂状态搜索(如迷宫最短路径、滑动谜题等,状态可能包含坐标、步数、当前局面等)。
    • Dijkstra 算法 等最短路径算法的基础思想。 优点:解耦了节点和其附属状态,能够处理非均匀权重的遍历问题,是通用 BFS 算法的核心写法。

结语:

二叉遍历本质上就这两种写法,递归遍历和层序遍历,其他写法的本质也是这两种,希望我和大家能够灵活运用,respect。

喜欢我的文章,不妨点一个免费的赞或者收藏,如果想要持续看我最新的文章,可以点个关注不迷路。

下期再见!

相关推荐
北顾笙9802 小时前
day35-数据结构力扣
数据结构·算法·leetcode
cpp_25013 小时前
P2249 【深基13.例1】查找
数据结构·c++·算法·题解·二分·洛谷
苏宸啊3 小时前
C++智能指针
c++
山甫aa3 小时前
二叉树算法-----从零开始的算法
数据结构·算法
OYangxf4 小时前
基于epoll的单线程Reactor:Tinyredis的网络层实现
c++·redis
睡觉就不困鸭4 小时前
第十七天 翻转字符串里的单词
数据结构·算法·哈希算法·散列表
yinbinggang4 小时前
vmware安装虚拟机
c++
睡觉就不困鸭5 小时前
第十八天 有效的括号
数据结构·算法
小小de风呀5 小时前
de风——【从零开始学C++】(三):类和对象(中序):默认成员函数全解析
开发语言·c++