二叉树遍历(前中后序的递归/非递归遍历、层序遍历)

news2025/6/18 23:49:56

二叉树的遍历

1. 二叉树的前序、中序、后序遍历

  • 前、中、后序遍历又叫深度优先遍历
    • 注:严格来说,深度优先遍历是先访问当前节点再继续递归访问,因此,只有前序遍历是严格意义上的深度优先遍历
  • 首先需要知道下面几点:
  1. 任何一颗二叉树,都由根节点、左子树、右子树构成。

​ 如图:

  1. 分治算法:分而治之。大问题分成类似的子问题,子问题再分成子问题……直到子问题不能再分割。对树也可以做类似的处理,对一棵树不断地分割,直到子树为空时,分割停止。
  2. 关于二叉树的许多问题我们都要利用分治思想,将一棵完整的树分解成根和左右两棵子树,然后再对这两棵子树进行相同的递归处理,最后得到结果。
  3. 如果对递归的过程想的不太清楚,建议画递归展开图来辅助理解

1.1 前序(先根)遍历

  • 遍历顺序:根- > 左子树 -> 右子树(即先访问根,再访问左子树,最后在访问右子树)

  • 如上图中:A -> B -> C -> NULL -> NULL -> D -> NULL -> NULL -> E -> NULL -> NULL

typedef char BTDataType;
typedef struct BinaryTreeNode
{
   struct BinaryTreeNode *left;	//指向左子树
   struct BinaryTreeNode *right;	//指向右子树
    BTDataType data;
}BTNode;

void PrevOrder(BTNode * root)	//前序遍历
{
   if (!root)
       return;
   
   printf("%d ",root->data);	//根
   PrevOrder(root->left);		//左子树
   PrevOrder(root->right);		//右子树
   return;
}

递归顺序图:

在这里插入图片描述

递归展开图:

1.2 中序(中根)遍历

  • 遍历顺序:左子树 -> 根 -> 右子树(即先访问左子树,再访问根,最后在访问右子树)

  • 如上图中:NULL -> C -> NULL -> B -> NULL -> D -> NULL -> A -> NULL -> E -> NULL

void InOrder(BTNode* root)
{
   if (!root)
   	return;
   InOrder(root->left);	//左子树
   printf("%c ", root->data);		//根
   InOrder(root->right);	//右子树
   return;
}

递归顺序图:

在这里插入图片描述

1.3 后序(后根)遍历

  • 遍历顺序:左子树 -> 右子树 -> 根(即先访问左子树,再访问左=右子树,最后在访问根)

  • 如上图中:NULL -> NULL -> C -> NULL -> NULL -> D -> B -> NULL -> NULL -> E -> A

void PostOrder(BTNode* root)
{
   if (!root)
   {
   	printf("NULL ");
   	return;
   }
   PostOrder(root->left);	//左子树
   PostOrder(root->right);		//右子树
   printf("%c ", root->data);		//根
}

递归顺序图:

在这里插入图片描述

2. 二叉树前中后序的非递归遍历

  • 在利用递归来进行二叉树的前中后序遍历时,我们通常将一棵二叉树看成三部分:根、左子树、右子树
  • 但是对于前中后序的非递归遍历,我们需要转变思路:应当将一棵二叉树看成两部分:左路节点、左路节点的右子树

在这里插入图片描述

2.1 前序非递归

前序遍历的顺序是:根 -> 左子树 -> 右子树

具体到一棵二叉树,就是自顶向下将左路节点遍历完,再自底向上遍历左路节点的右子树。如图:

在这里插入图片描述

为了能够在遍历完左路节点后还能得到这个节点从而得到这个节点的右子树,我们需要利用栈来对左路节点进行存储

实现代码:

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> ret;
        stack<TreeNode*> st;

        TreeNode* cur = root;
        while (cur || !st.empty())
        {
            //遍历左路节点,将左路节点的值打印的同时将节点入栈
            while (cur)
            {
                ret.push_back(cur->val);
                st.push(cur);

                cur = cur->left;
            }

            TreeNode* tmp = st.top();
            st.pop();
			
            //此时cur即为左路节点的右子树
            //将这棵右子树看成一颗完整的二叉树,进行相同的操作
            cur = tmp->right;
        }
        
        return ret;
    }
};

2.2 中序非递归

中序遍历的顺序是:左子树 -> 根 -> 右子树

具体到一棵二叉树,即从左路节点的最深处开始,先遍历这个节点,再遍历这个节点的右子树,自底向上。

在这里插入图片描述

同样,为了能够找到之前的左路节点,也需要一个栈来对左路节点进行保存

实现代码:

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> ret;
        stack<TreeNode*> st;

        TreeNode* cur = root;
        while (cur || !st.empty())
        {
            //遍历左路节点的时候将左路节点入栈
            //由于左子树先于根,因此先不要打印左路节点(根)的值
            while (cur)
            {
                st.push(cur);
                cur = cur->left;
            }
			
            //程序第一次走到这里时,tmp就是左路节点的最深处
            //tmp的左子树为nullptr(或已经遍历完tmp的左子树),因此打印其(根)值
            TreeNode* tmp = st.top();
            st.pop();
            ret.push_back(tmp->val);
            
            //遍历左路节点的右子树
            cur = tmp->right;
        }
        
        return ret;
    }
};

2.3 后序非递归

后序的遍历顺序为:左子树 -> 右子树 -> 根

具体到一棵二叉树,即从左路节点的最深处开始,先遍历左路节点的右子树,再遍历左路节点,自底向上。如图:

在这里插入图片描述

同样,也需要一个栈来保存之前的左路节点

此外,由于后序遍历的顺序为:左子树 -> 右子树 -> 根,需要遍历完根(左路节点)的左子树和右子树后才能对其值进行打印,在这个过程中,我们会经过两次根,且只能在第二次经过根时才能打印根的值,为了确保我们打印根的时机,可以利用一个指针prev来记录之前遍历的位置如果prev停留在左路节点的右子树的根节点,就说明此时左右子树已经遍历完,可以打印根的值

实现代码:

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> ret;
        stack<TreeNode*> st;

        TreeNode* cur = root;
        TreeNode* prev = nullptr;

        while (cur || !st.empty())
        {
            //将左路节点入栈
            while (cur)
            {
                st.push(cur);
                cur = cur->left;
            }

            TreeNode* tmp = st.top();
            
            //如果左路节点的右子树为空或者prev停留在右子树的根
            //说明根的左子树和右子树都已遍历完
            //打印根值(遍历根),同时跟新prev的位置
            if (tmp->right == nullptr || prev == tmp->right)
            {
                ret.push_back(tmp->val);
                st.pop();

                prev = tmp;
            }
            else	//否则,说明根的右子树没有遍历完,遍历右子树
                cur = tmp->right;
        }
        
        return ret;
    }
};

2. 二叉树的层序遍历

  • 层序遍历又叫广度优先遍历

  • 设二叉树的根节点所在层数为1,层序遍历就是从根节点出发,首先访问第一层的节点,然后从左到右访问第二层上的节点,接着访问第三层的节点,以此类推,自上而下,自左往右逐层访问树的结点的过程就是层序遍历

  • 层序遍历借助队列的先进先出思想来实现

  • 核心思想:上一层带下一层

  • 如图就是对上面那棵树的层序遍历示意图:

    在这里插入图片描述

  • 实现代码

typedef BTNode* QDataType;		//队列元素类型
typedef struct QueueNode
{
   struct QueueNode* next;
   QDataType data;
}QueueNode;
typedef struct Queue	//定义存放指向队头,队尾指针的结构体
{
   QueueNode* head;	//指向队头
   QueueNode* tail;	//指向队尾
}Queue;

//层序遍历
void LevelOrder(BTNode* root)		
{
   Queue *q = (Queue *)malloc(sizeof(Queue));	//创建队列
   QueueInit(q);	//初始化队列
   
   //如果根节点为空,直接退出函数
   if (!root)
   	return;
   
   QueuePush(q, root);		//先将根节点入队入队
   while (!QueueEmpty(q))		//当队列不为空
   {
   	BTNode* front = QueueFront(q);		//接收队头元素
   	QueuePop(q);		//出队头元素
       
   	printf("%c ", front->data);	//访问该节点
       
   	if (front->left)		//如果左子树不为空
   		QueuePush(q, front->left);			//左子树入队
   	if (front->right)		//如果右子树不为空
   		QueuePush(q, front->right);		//右子树入队
   }
   
   printf("\n");
   QueueDestroy(q);		//销毁队列	
}

3. 二叉树遍历的应用

在这里插入图片描述

  • 由二叉树和层序遍历的思想,我们可以构造出这棵树

  • 再有前序遍历 根- > 左子树 -> 右子树 的思想,可以知道,这棵树的前序序列为:A B D H E C F G

在这里插入图片描述

  • 这道题是由二叉树的前序序列和中序序列来确定二叉树,我们知道中序遍历的思想是 左子树 -> 根 -> 右子树 ,根将左子树和右子树分割开来,那么我们就可以先用前序序列确定根,再用中序序列确定根的左右子树,这样就可以将这棵二叉树确定了,如图:

在这里插入图片描述

  • 显然根节点为E

我们同样可以用代码,利用一棵二叉树的前序序列和中序序列来将这棵二叉树还原👉Leetcode -> 从前序与中序遍历序列构造二叉树

class Solution {
public:
    //前序序列即为根序列
    //在中序序列找到根后可以将序列分为左右两部分,这两部分分别就是跟的左子树序列和右子树序列
    TreeNode* _buildTree(vector<int>& preorder, vector<int>& inorder, int& prei, int inBegin, int inEnd)
    {
        //区间长度小于1,直接返回空
        if (inBegin > inEnd)
            return nullptr;

        //前序序列即为根序列
        TreeNode* root = new TreeNode(preorder[prei++]);

        //在中序序列中找到根
        int pos = 0;
        while (1)
        {
            if (root->val != inorder[pos])
                pos++;
            else
                break;
        }

        //分别前往左子树和右子树进行连接
        root->left = _buildTree(preorder, inorder, prei, inBegin, pos - 1);
        root->right = _buildTree(preorder, inorder, prei, pos + 1, inEnd);

        return root;
    }
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int i = 0;
        TreeNode* root = _buildTree(preorder, inorder, i, 0, inorder.size() - 1);

        return root;
    }
};

在这里插入图片描述

  • 这题和第二题类似,同样是先由后序遍历(左子树 -> 右子树 -> 根)确定根节点,再由中序遍历确定根的左右子树,只是用后序遍历确定根节点时要从最后开始。如图:

在这里插入图片描述

  • 易得前序遍历为a b c d e

  • 我们同样可以用代码,利用一棵二叉树的前序序列和中序序列来将这棵二叉树还原👉Leetcode -> 从中序与后序遍历序列构造二叉树

class Solution {
public:
    //思想同前序序列和中序序列确定二叉树类似
    TreeNode* _buildTree(vector<int>& inorder, vector<int>& postorder, int& posi, int inBegin, int inEnd)
    {
        if (inBegin > inEnd)
            return nullptr;

        TreeNode* root = new TreeNode(postorder[posi--]);

        int pos = 0;
        while (1)
		        {
            if (root->val != inorder[pos])
                pos++;
            else
                break;
        }

        root->right = _buildTree(inorder, postorder, posi, pos + 1, inEnd);
        root->left = _buildTree(inorder, postorder, posi, inBegin, pos - 1);

		        return root;
    }
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        int i = postorder.size() - 1;
        TreeNode* root = _buildTree(inorder, postorder, i, 0, inorder.size() - 1);

        return root;
    }
};

总结:

由于二叉树的中序遍历可以分割二叉树的左右节点,因此 前序序列 + 中序序列 / 后序序列 + 中序序列 都可以构建出一棵二叉树,而单独序列和 前序序列 + 后序序列就不行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1503590.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32基础--位带操作

位带简介 位操作就是可以单独的对一个比特位读和写&#xff0c;这个在 51 单片机中非常常见。51 单片机中通过关键字 sbit 来实现位定义&#xff0c;STM32 没有这样的关键字&#xff0c;而是通过访问位带别名区来实现。 在 STM32 中&#xff0c;有两个地方实现了位带&#xff…

leetcode 1143. 最长公共子序列【动态规划】

leetcode 1143. 最长公共子序列 int longestCommonSubsequence(char* text1, char* text2) {int len1 strlen(text1);int len2 strlen(text2);int dp[len1 1][len2 1];memset(dp, 0, sizeof(dp));for (int i 1; i < len1; i) {for (int j 1; j < len2; j) {if (t…

读书笔记之《理解和改变世界》:从信息知识智能的本质看AI

《理解和改变世界: 从信息到知识与智能》作者:是(法) 约瑟夫希发基思&#xff0c; 原作名: Understanding and Changing the World: From Information to Knowledge and Intelligence&#xff0c;2023年出版。 约瑟夫希发基思&#xff08;Joseph Sifakis&#xff09;&#xff…

【Linux】第一个小程序--进度条

这篇博客要综合利用以前的知识&#xff0c;来实现一个进度条程序~ 目录 换行&回车 缓冲区 实现简单的倒计时 实现进度条 version1 version2 在开始写这个小程序之前&#xff0c;我们先学习一些预备知识&#xff1a; 换行&回车 缓冲区 在我们运行这个程序时&…

docker ENTRYPOINT [“sh“,“-c“,“java“,“-jar“,“Hello.jar“] 启动失败问题分析

因为没系统的学过linux语法&#xff0c;所以才会产生如下疑问。大佬请跳过。 问题&#xff1a;当在dockerfile里面配置 ENTRYPOINT ["sh","-c","java","-jar","Hello.jar"] &#xff0c;启动对应容器时会无法正常运行&…

【MySQL】MySQL 的 SSL 连接以及连接信息查看

MySQL 的 SSL 连接以及连接信息查看 在上篇文章中&#xff0c;我们学习过 MySQL 的两种连接方式&#xff0c;回忆一下&#xff0c;使用 -h 会走 TCP 连接&#xff0c;不使用 -h 可以使用另两种方式来走 UnixSocket 连接。我们就接着这个话题再聊点别的&#xff0c;首先要纠正一…

基于springboot+vue实现高校学生党员发展管理系统项目【项目源码+论文说明】

基于springboot实现高校学生党员发展管理系统演示 摘要 随着高校学生规模的不断扩大&#xff0c;高校内的党员统计及发展管理工作面临较大的压力&#xff0c;高校信息化建设的不断优化发展也进一步促进了系统平台的应用&#xff0c;借助系统平台可以实现更加高效便捷的党员信息…

抓包工具获取请求信息

Charles 下载安装 下载 官方下载地址&#xff1a;https://www.charlesproxy.com/latest-release/download.do 下载后傻瓜式安装就好&#xff0c;这个官方的需要激活&#xff0c;可以选择绿色版或者学习版 绿色版 绿色中文版&#xff1a;https://soft.kxdw.com/pc/Charles.z…

STM32标准库——(21)Flash闪存

1.简介 第一个用途&#xff0c;对于我们这个C8T6芯片来说&#xff0c;它的程序存储器容量是64K&#xff0c;一般我们写个简单的程序&#xff0c;可能就只占前面的很小一部分空间&#xff0c;剩下的大片空余空间我们就可以加以利用&#xff0c;比如存储一些我们自定义的数据&…

单数码管(arduino)

1.连接方法 挨个点亮每个灯 #include <Arduino.h>int pin_list[] {4, 5, 19, 21, 22, 2, 15, 18}; int num_pins sizeof(pin_list) / sizeof(pin_list[0]); // 计算数组中的元素数量void setup() {// 设置每个引脚为输出for(int i 0; i < num_pins; i) {pinMode(p…

Synthetic Temporal Anomaly Guided End-to-End Video Anomaly Detection 论文阅读

Synthetic Temporal Anomaly Guided End-to-End Video Anomaly Detection 论文阅读 Abstract1. Introduction2. Related Work3. Methodology3.1. Architecture3.1.1 Autoencoder3.1.2 Temporal Pseudo Anomaly Synthesizer 3.2. Training3.3. Anomaly Score 4. Experiments4.1.…

详解Linux例行性工作

例行性工作&#xff08;计划任务&#xff09; 场景&#xff1a; 生活中&#xff0c;我们有太多场景需要使用到闹钟&#xff0c;比如早上7点起床&#xff0c;下午4点开会&#xff0c;晚上8点购物&#xff0c;等等。再Linux系统里&#xff0c;我们同样也有类似的需求。比如我们…

C语言学习--练习3(贪心)

目录 贪心算法 1. 两数对之间的最大乘积差 2.三角形的最大周长 3.数组拆分 4.救生艇 5.发送饼干 6.摆动数组 贪心算法 概念定义 所谓贪心&#xff0c;总是做出在当前看来是最好的选择。也就是说&#xff0c;不从整体最优上进行考虑&#xff0c;算法得到的是在某种…

第三百九十一回

文章目录 1. 概念介绍2. 方法与细节2.1 实现方法2.2 具体细节 3. 示例代码4. 内容总结 我们在上一章回中介绍了"如何混合选择多个图片和视频文件"相关的内容&#xff0c;本章回中将介绍如何通过相机获取视频文件.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. …

【数据库-黑马笔记】基础-SQL

本文参考b站黑马数据库视频,总结详细全面的笔记 ,可结合视频观看1~26集 MYSQL 的基础知识框架如下 目录 一、MYSQL概述 1、数据库相关概念 2、MYSQL的安装及启动 二、SQL 1、DDL【Data Defination】 2、DML【Data Manipulation】 ①、插入 ②、更新和删除 3、 DQL【Data…

【Scrapy】京东商品数据可视化

【Scrapy】京东商品数据可视化 文章目录 【Scrapy】京东商品数据可视化  &#x1f449;引言&#x1f48e;一、爬取数据&#xff1a;1.1 scrapy爬虫库简介&#xff1a;1.2 技术实现&#xff1a;1.2.1搭建框架结构1.2.2 分析网页结构 二、数据保存&#xff1a;三、数据读取以及…

【Algorithms 4】算法(第4版)学习笔记 16 - 4.2 有向图

文章目录 前言参考目录学习笔记1&#xff1a;介绍1.1&#xff1a;有向图简介1.2&#xff1a;应用举例1.3&#xff1a;相关问题2&#xff1a;有向图 API2.1&#xff1a;有向图表示2.1.1&#xff1a;邻接表数组 Adjacency-list2.1.2&#xff1a;Java 实现&#xff1a;邻接表数组2…

2024年k8s最新版本安装教程

k8s安装教程 1 k8s介绍2 环境搭建2.1 主机准备2.2 主机初始化2.2.1 安装wget2.2.2 更换yum源2.2.3 常用软件安装2.2.4 关闭防火墙2.2.5 关闭selinux2.2.6 关闭 swap2.2.7 同步时间2.2.8 修改Linux内核参数2.2.9 配置ipvs功能 2.3 容器安装2.3.1 设置软件yum源2.3.2 安装docker软…

C# OpenVINO Yolov8-OBB 旋转目标检测

目录 效果 模型 项目 代码 下载 C# OpenVINO Yolov8-OBB 旋转目标检测 效果 模型 Model Properties ------------------------- date&#xff1a;2024-02-26T08:38:44.171849 description&#xff1a;Ultralytics YOLOv8s-obb model trained on runs/DOTAv1.0-ms.yaml …

论文的引用书写方法

前置操作 1、全选文献 2、在开始选项卡 段落功能区 选择编号功能 3、设置编号格式 [1] 论文的引用 1、光标放在需要引用论文的地方 2、选择引用选项卡 点击交叉引用 3、引用类型为编号项 引用内容为段落编号 选择需要的第几条参考文献