数据结构:二叉树遍历

news2024/11/25 0:50:16

概述

二叉树的遍历是指按照某条搜索路径访问二叉树中的每个结点,使得每个结点均被访问一次,而且仅被访问一次。二叉树的遍历方式主要有:先序遍历、中序遍历、后序遍历、层次遍历。先序、中序、后序其实值得是父节点被访问的次序。若在遍历过程中,父节点均先于它的子节点被访问,就是先序遍历;若父节点被访问的次序在它的子节点被访问次序之间,就是中序遍历;若访问完左右孩子之后在访问父节点,就是后序遍历。无论是先序遍历、中序遍历还是后序遍历,左右孩子节点的相对访问次序是不会发生变化的,总是先访问左孩子,在访问右孩子节点。而层次遍历,就是按照从上到下,从左到右的顺序访问二叉树的每一个节点。

数据结构:二叉树遍历1

在介绍遍历算法之前,先定义一个二叉树的结构体,代码如下:

struct node{
    int elem;//数据域
    node *left;//左孩子节点指针
    node *right;//有孩子指针节点
};

先序遍历

递归

使用递归,很容易写出遍历算法。代码如下:

void pre_order_r(const node* root, int(*visit)(const node*)){
    //先序遍历,递归,root根结点,visit访问数据元素的函数指针
    if(root == nullptr) return;

    visit(root);//可以把这个理解成cout << root->elem
    pre_order_r(root->left, visit);
    pre_order_r(root->right, visit);
}

迭代

第一种迭代版本

代码如下:

void pre_order1(const node* root, int(*visit)(const node*)){
    //先序遍历,非递归
    const node *p;
    stack<const node*>st;//辅助栈
    p = root;
    if(p != nullptr) st.push(p);//若根结点不为空,则令根结点进栈
    while(!st.empty()){
        p = st.top();
        st.pop();
        visit(p);
        if(p->right != nullptr) st.push(p->right);
        if(p->left != nullptr) st.push(p->left);
    }
}

下面用一个实例来了解该迭代如何工作。

数据结构:二叉树遍历2

注:格子上面的元素表示已经被弹出并访问过。

这个二叉树的遍历过程如下:

  1. 初始化一个空栈和指针p指向根结点。
  2. 根结点不为空,入栈,此时将a入栈。
  3. 判断栈是否为空,不为空,循环开始,弹出并访问栈顶元素,此时栈顶元素为a。
  4. 如果a有右孩子,则将其右孩子节点入栈;如果有左孩子节点,则将左孩子节点入栈,此时栈中有b,c两个元素。
  5. 重复第三步骤,直至栈为空。遍历结束。

第2种迭代方式

来看一个规模更大,更具有一般性的二叉树:

数据结构:二叉树遍历3

这个二叉树的先序遍历序列是:abdheicfjkgl,也就是遵守下图所示的顺序:

数据结构:二叉树遍历4

进一步,我们把二叉树抽象成下图这样:,

数据结构:二叉树遍历5

a到e是二叉树的左侧链上的节点,i到f分别是a到d的有孩子。不难发现,二叉树的先序遍历就是先自上而下访问左子树上的节点,在自下而上访问左子树上的节点的右子树。我们第2中迭代就是根据这个思路来设计的。代码如下:

void pre_order2(const node* root, int(*visit)(const node*)){
    const node *p;
    stack<const node*> st;
    p = root;
    if(p != nullptr) st.push(p);
    while( ! st.empty() ){
        p = st.top();
        st.pop();
        while(p != nullptr){//从当前节点出发,逐批访问
            visit(p);//访问当前节点
            if(p->right != nullptr) st.push(p->right);//不为空的右孩子入栈
            p = p->left;//沿左子树深入一层
        }
    }
}

中序遍历

递归

与先序遍历类似,不做过多解释。代码如下:

void in_order_r(const node* root, int(*visit)(const node*)){
    //中序遍历,递归
    if(root == nullptr) return;

    in_order_r(root->left, visit);
    visit(root);
    in_order_r(root->right, visit);
}

迭代

对照先先序遍历第2种迭代版本,在宏观上,将中序遍历的顺序抽象为,先访问二叉树的左子树的最底层的节点,再访问该节点的右子树节点,再访问该节点的父节点,再访问该节点的父节点的右子树,直到全部节点都被访问结束。如下图:

数据结构:二叉树遍历6

按照这个思路可以实现中序遍历算法如下:

void in_order(const node* root, int(*visit)(const node*)){
    //中序遍历,非递归
    const node *p;
    stack<const node *> st;
    p= root;
    while(!st.empty() ) {
        if (p != nullptr){
            st.push(p);
            p = p->left;
        }
        else {
            p = st.top();
            st.pop();
            visit(p);
            p = p->right;
        }
    }
}

后序遍历

递归

与前面两个一样,直接上代码:

void post_order_r(const node* root, int(*visit)(const node*)){
    //后序遍历,递归
    if(root == nullptr) return;

    post_order_r(root->left,  visit);
    post_order_r(root->right, visit);
    visit(root);
}

迭代

要想使用迭代的方法实现后序遍历,则有一定难度,因为左右子树的递归遍历均严格不属于尾递归。但是我们可以套用前面的思想和方法,思考一下,后序遍历中,首先访问的是哪个节点?答案是二叉树的最高最左侧的叶子节点,这个最高最左测的叶子节点可能是左孩子节点,也可能是右孩子节点。

数据结构:二叉树遍历7

整个后序遍历可以分解为若干个片段,每个片段,分别起始于通路上的一个节点,并包括三步骤:访问当前节点,遍历以其右兄弟为根的子树,以及向上回溯到其父节点并转入下一个片段。基于这个理解,我们的代码如下:

void post_order(const node* root, int(*visit)(const node*)){
    //后序遍历,非递归
    const node *p, *q;//p正在访问的节点,q刚刚访问过的节点
    stack<const node *> st;
    p = root;
    do {
        while(p != nullptr){//往左走下去
            st.push(p);
            p = p->left;
        }
        q = nullptr;
        while( !st.empty() ){
            p = st.top();
            st.pop();
            if(p -> right == q) {//右孩子不存在或者已被访问过
                visit(p);
                q = p;//保存刚访问的节点
            }
            else{
                st.push(p);//当前节点不能访问
                p = p->right;//先处理右子树
                break;
            }
        }
    }while(! st.empty() );
}

我们对先序遍历的第一个实例进行后序遍历来了解这个迭代时如何工作的。

数据结构:二叉树遍历8

这个二叉树的遍历过程如下:

  1. 初始化一个空栈和指针p(表示正在访问的节点)q(刚刚访问过的节点)
  2. 从当前节点往左走,直到走到最后。
  3. 判断当前节点是否能被访问(即判断当前节点是否拥有右子树或是否已被访问),若当前节点没有右子树或者已被访问,则访问当前节点;反之将p指向当前节点的右子树,并返回第2步骤。
  4. 直至栈为空。遍历结束。

层次遍历

再开头对层次遍历的介绍中,我们说过层次遍历严格按照自上而下,自左向右的顺序访问二叉树的每一个节点。所以这里面我们需要队列作为辅助,代码如下:

void level_order(const node *root, int(*visit)(const node *)){
    //层次遍历(BFS)
    const node *p;
    queue<const node *> qu;
    p = root;
    if(p != nullptr) qu.push(p);
    while(! qu.empty() ) {
        p = qu.front();
        qu.pop();
        visit(p);
        if(p->left != nullptr) qu.push(p->left);
        if(p->right != nullptr) qu.push(p->right);
    }
}

完整代码

#include "iostream"
#include "queue"
#include "stack"

using namespace std;

struct node{
    int elem;
    node *left;
    node *right;
};

void pre_order_r(const node* root, int(*visit)(const node*)){
    //先序遍历,递归,root根结点,visit访问数据元素的函数指针
    if(root == nullptr) return;

    visit(root);
    pre_order_r(root->left, visit);
    pre_order_r(root->right, visit);
}

void in_order_r(const node* root, int(*visit)(const node*)){
    //中序遍历,递归
    if(root == nullptr) return;

    in_order_r(root->left, visit);
    visit(root);
    in_order_r(root->right, visit);
}

void post_order_r(const node* root, int(*visit)(const node*)){
    //后序遍历,递归
    if(root == nullptr) return;

    post_order_r(root->left,  visit);
    post_order_r(root->right, visit);
    visit(root);
}

void pre_order1(const node* root, int(*visit)(const node*)){
    //先序遍历,非递归
    const node *p;
    stack<const node*>st;
    p = root;
    if(p != nullptr) st.push(p);
    while(!st.empty()){
        p = st.top();
        st.pop();
        visit(p);
        if(p->right != nullptr) st.push(p->right);
        if(p->left != nullptr) st.push(p->left);
    }
}

void pre_order2(const node* root, int(*visit)(const node*)){
    const node *p;
    stack<const node*> st;
    p = root;
    if(p != nullptr) st.push(p);
    while( ! st.empty() ){
        p = st.top();
        st.pop();
        while(p != nullptr){
            visit(p);
            if(p->right != nullptr) st.push(p->right);
            p = p->left;
        }
    }
}
void in_order(const node* root, int(*visit)(const node*)){
    //中序遍历,非递归
    const node *p;
    stack<const node *> st;
    p= root;
    while(!st.empty() ) {
        if (p != nullptr){
            st.push(p);
            p = p->left;
        }
        else {
            p = st.top();
            st.pop();
            visit(p);
            p = p->right;
        }
    }
}

void post_order(const node* root, int(*visit)(const node*)){
    //后序遍历,非递归
    const node *p, *q;//p正在访问的节点,q刚刚访问过的节点
    stack<const node *> st;
    p = root;
    do {
        while(p != nullptr){//往左走下去
            st.push(p);
            p = p->left;
        }
        q = nullptr;
        while( !st.empty() ){
            p = st.top();
            st.pop();
            if(p -> right == q) {//右孩子不存在或者已被访问过
                visit(p);
                q = p;//保存刚访问的节点
            }
            else{
                st.push(p);//当前节点不能访问
                p = p->right;//先处理右子树
                break;
            }
        }
    }while(! st.empty() );
}

void level_order(const node *root, int(*visit)(const node *)){
    //层次遍历(BFS)
    const node *p;
    queue<const node *> qu;
    p = root;
    if(p != nullptr) qu.push(p);
    while(! qu.empty() ) {
        p = qu.front();
        qu.pop();
        visit(p);
        if(p->left != nullptr) qu.push(p->left);
        if(p->right != nullptr) qu.push(p->right);
    }
}

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

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

相关文章

一.MySQL的主从复制

目录 一.MySQL的主从复制 1.2主从复制的工作过程和原理 1.2.1主从复制工作过程为两日志和三线程 ​编辑 1.2.2主从复制的工作原理 1.3主从复制延迟的原因 1.4主从复制的架构 1.5.MySQL四种同步方式 1.5.1异步复制(Async Replication) 1.5.2 同步复制&#xff08;Sync Re…

Linux内核子系统--进程管理剖析

Linux 是一个计算需求不断变化的非常动态的系统。 Linux 计算需求的表示以进程的公共抽象为中心&#xff0c;进程可以是短期的&#xff08;从命令行执行的命令&#xff09;或长期的&#xff08;网络服务&#xff09;。因此&#xff0c;进程的总体管理及其调度非常重要。 在用户…

EMC学习笔记(十八)滤波器设计

滤波器设计 1.标准要求2.设计理论2.1 滤波器电路设计过程2.2 插入损耗定义2.3 原始噪声测量2.4 插入损耗计算2.5 滤波失配原则2.6 滤波拓扑选择2.7 滤波参数计算2.8 滤波参数确定 Tips&#xff1a;学习资料来自网络&#xff0c;仅供学习使用。 EMI滤波器设计&#xff08;汽车电…

JVM系列(9)——调优初体验

学习这篇文章之前&#xff0c;要了解&#xff1a; JVM系列&#xff08;2&#xff09;——垃圾回收 JVM系列&#xff08;3&#xff09;——内存分配与回收策略 先了解概念&#xff1a; 吞吐量&#xff1a;用户执行时间/(用户执行时间垃圾回收时间)&#xff1b;就是干正经事的时间…

C++语法(25)--- 异常与智能指针

C语法&#xff08;24&#xff09; C11_哈里沃克的博客-CSDN博客https://blog.csdn.net/m0_63488627/article/details/131054426?spm1001.2014.3001.5501 1.异常 try { // 保护的标识代码 }catch( ExceptionName e1 ) { // catch 块 }catch( ExceptionName e2 ) { // catch 块…

RT-Thread qemu mps2-an385 bsp 移植制作 :BSP 制作篇

下载 V2M-MPS2_CMx_BSP mps2 的资料很少&#xff0c;所以唯一能下载的是 ARM 官方的 V2M-MPS2_CMx_BSP&#xff0c;下载地址为&#xff1a; https://keilpack.azureedge.net/pack/Keil.V2M-MPS2_CMx_BSP.1.8.0.pack 其实这是个 Keil MDK5 的 Pack 包&#xff0c;安装后&#x…

JAVA-ReentrantLock(五)

概念 在Java中&#xff0c;“lock”&#xff08;锁&#xff09;是一种用于并发控制的机制。它用于确保在多线程环境中&#xff0c;同一时刻只有一个线程可以访问共享资源或临界区。当一个线程获得了锁&#xff0c;其他线程将被阻塞&#xff0c;直到持有锁的线程释放它。这样可…

Cocos Creator 3.8 后期效果 Shader 编写(1/2) 基础篇

原文链接&#xff1a;Cocos Creator 3.8 后期效果 Shader 编写&#xff08;1/2&#xff09; 基础篇 在 Cocos Creator 3.8 版本中&#xff0c;新增了不少实用的特性&#xff0c;其中我最喜欢的&#xff0c;就是它自带后期效果管线&#xff0c;并且还内置了许多高级效果。 有用…

XUbuntu22.04之Linux剪切板和selection primary区域(一百八十七)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

二、Java框架基础02 XML

二、XML 2.1 XML 简介 XML 即可扩展标记语言&#xff0c;一种简单的数据存储语言&#xff0c;使用一系列简单的标记来描述结构化数据 XML 的特点 XML 与操作系统&#xff0c;编程语言的开发平台无关规范统一&#xff0c;实现不同系统之间的数据交互 2.1.1 XML 的文档结构 以下…

vulnhub靶场之CengBox3

1.信息收集 输入命令&#xff1a;netdiscover -i eth0 -r 192.168.239.0 &#xff0c;发现181机器存活 输入命令nmap -p- -sV -O -Pn -A 192.168.239.181 &#xff0c;进行端口探测&#xff0c;发现存在22、80、443端口&#xff0c;还发现存在域名ceng-company.vm。 将域名c…

【linux基础】05-linux文件系统

概述 在Linux中,文件系统是一种分层结构,它将文件和目录组织成树状结构。文件系统从“根”目录开始,该目录由单个正斜杠(“/”)表示。 如下图所示: Linux 支持多种类型的文件系统,包括: Ext4:这是大多数 Linux 发行版的默认文件系统。它是一个日志文件系统,提供良…

拉格朗日乘数法(Lagrange)的推导

同济版高数上&#xff0c;关于拉格朗日乘数法&#xff0c;以及好多知识点说的语焉不详、模棱两可&#xff0c;在阅读了知乎等博主的几篇文章后&#xff0c;才算勉强弄懂了该知识的原理。 首先说一下高数上隐函数求导。所谓的隐函数求导&#xff0c;就是在方程中多个变量之间的…

如何使用Java 实现excel模板导出---多sheet导出?

实现多个sheet的excel导出功能 效果展示&#xff1a; maven依赖 <dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>3.17</version></dependency><dependency><groupId>or…

泛微E-Cology XXE漏洞复现(QVD-2023-16177)

0x01 产品简介 泛微协同管理应用平台E-Cology是一套兼具企业信息门户、知识文档管理、工作流程管理、人力资源管理、客户关系管理、项目管理、财务管理、资产管理、供应链管理、数据中心功能的企业大型协同管理平台。 0x02 漏洞概述 泛微e-cology某处功能点最初针对用户输入的…

STM32 点灯实现 7.18

嵌入式&#xff1a; 以应用为中心&#xff0c;以专用计算机为基础&#xff0c;软硬件可裁剪ARM A系列芯片&#xff1a;高端芯片&#xff0c;实现人机互动 R系列&#xff1a;实现时效性 M系列&#xff1a;低端芯片&#xff0c;控制硬件设备&#xff0c;灯&#xff0c;风扇....…

Springboot初识(一)

一.什么是Spring Boot Spring Boot是一个开源的、用于简化Spring应用程序开发的框架。它是Spring项目的一个子项目&#xff0c;旨在为Spring应用程序提供更快速、更便捷的开发体验。Spring Boot基于Spring框架&#xff0c;同时也整合了其他Spring项目和第三方库&#xff0c;使…

Unity-AssetBundle

一、AB 包介绍 ​ AB 包是特定于平台的资源压缩包&#xff0c;类似于压缩文件。其中资源可包括&#xff1a;模型、贴图、预设体、音效、材质球等等。 ​ 相较于 Resources 文件夹下的资源文件&#xff0c;AB 包能够更好管理资源&#xff1a; Resources 文件夹&#xff1a;打包…

【设计模式】23种设计模式——建造者模式Builder(原理讲解+应用场景介绍+案例介绍+Java代码实现)

介绍 建造者模式又叫生成器模式&#xff0c;是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别)&#xff0c;使这个抽象过程的不同实现方法可以构造出不同属性的对象建造者模式是一步一步创建一个复杂的对象&#xff0c;它允许用户只通过指定复杂对象的类型和…

【PHP面试题79】在Linux中如何设置MySQL和PHP服务开机启动

文章目录 &#x1f680;一、前言&#x1f680;二、设置MySQL服务开机启动&#x1f50e;2.1 打开终端&#x1f50e;2.2 编辑MySQL配置文件&#x1f50e;2.3 修改配置文件&#x1f50e;2.4 检查MySQL服务是否已启动&#x1f50e;2.5 设置MySQL服务开机启动 &#x1f680;三、设置…