数据结构与算法教程,数据结构C语言版教程!(第六部分、数据结构树,树存储结构详解)三

第六部分、数据结构树,树存储结构详解

数据结构的树存储结构,常用于存储逻辑关系为 "一对多" 的数据。

树存储结构中,最常用的还是二叉树,本章就二叉树的存储结构、二叉树的前序、中序、后序以及层次遍历、线索二叉树、哈夫曼树等,详细介绍二叉树。

树是数据结构中的重点,同时更是难点,没有捷径,需要初学者静下心,死扣各个知识点。

五、由浅入深讲二叉树4种遍历算法的由来

遍历二叉树可以算作是对树存储结构做的最多的操作,既是重点,也是难点。本节将从初学者的角度给大家分析一下 4 种遍历二叉树算法的由来。

1、遍历二叉树的算法

图 1 二叉树示意图

图 1 是一棵二叉树,对于初学者而言,遍历这棵二叉树无非有以下两种方式。

(1)层次遍历

前面讲过,树是有层次的,拿图 1 来说,该二叉树的层次为 3。通过对树中各层的节点从左到右依次遍历,即可实现对正棵二叉树的遍历,此种方式称为层次遍历

比如,对图 1 中二叉树进行层次遍历,遍历过程如图 2 所示:

图 2 层次遍历二叉树示意图

(2)普通遍历

其实,还有一种更普通的遍历二叉树的思想,即按照 "从上到下,从左到右" 的顺序遍历整棵二叉树。

还拿图 1 中的二叉树举例,其遍历过程如图 3 所示:

图 3 普通方式遍历二叉树

以上仅是从初学者的角度,对遍历二叉树的过程进行了分析。接下来我们从程序员的角度再对以上两种遍历方式进行剖析。

这里,我们要建立一个共识,即成功遍历二叉树的标志是能够成功访问到二叉树中所有的节点。

2、二叉树遍历算法再剖析

首先观察图 2 中的层次遍历,整个遍历过程只经过各个节点一次,因此在层次遍历过程,每经过一个节点,都必须立刻访问该节点,否则错失良机,后续无法再对其访问。

若对图 1 中二叉树进行层次遍历,则访问树中节点的次序为:

1 2 3 4 5 6 7

而普通遍历方式则不同,通过观察图 3 可以看到,整个遍历二叉树的过程中,每个节点都被经过了 3 次(虽然叶子节点看似只经过了 2 次,但实际上可以看做是 3 次)。以图 3 中的节点 2 为例,如图 4 所示,它被经过了 3 次。

图 4 遍历节点 2 的过程示意图

因此,在编程实现时,我们可以设定真正访问各个节点的时机,换句话说,我们既可以在第一次经过各节点时就执行访问程序,也可以在第二次经过各节点时访问,甚至可以在最后一次经过各节点时访问。

这也就引出了以下 3 种遍历二叉树的算法:

  1. 先序遍历:每遇到一个节点,先访问,然后再遍历其左右子树(对应图 4 中的 ①);
  2. 中序遍历:第一次经过时不访问,等遍历完左子树之后再访问,然后遍历右子树(对应图 4 中的 ②);
  3. 后序遍历:第一次和第二次经过时都不访问,等遍历完该节点的左右子树之后,最后访问该节点(对应图 4 中的 ③);

以图 1 中的二叉树为例,其先序遍历算法访问节点的先后次序为:

1 2 4 5 3 6 7

中序遍历算法访问节点的次序为:

4 2 5 1 6 3 7

后序遍历访问节点的次序为:

4 5 2 6 7 3 1

以上就是二叉树 4 种遍历算法的由来,其各个算法的具体实现过程其代码实现后续章节会详解介绍。


六、二叉树先序遍历(递归与非递归)及C语言实现

二叉树先序遍历的实现思想是:

  1. 访问根节点;
  2. 访问当前节点的左子树;
  3. 若当前节点无左子树,则访问当前节点的右子树;

图 1 二叉树

以图  1 为例,采用先序遍历的思想遍历该二叉树的过程为:

  1. 访问该二叉树的根节点,找到 1;
  2. 访问节点 1 的左子树,找到节点 2;
  3. 访问节点 2 的左子树,找到节点 4;
  4. 由于访问节点 4 左子树失败,且也没有右子树,因此以节点 4 为根节点的子树遍历完成。但节点 2 还没有遍历其右子树,因此现在开始遍历,即访问节点 5;
  5. 由于节点 5 无左右子树,因此节点 5 遍历完成,并且由此以节点 2 为根节点的子树也遍历完成。现在回到节点 1 ,并开始遍历该节点的右子树,即访问节点 3;
  6. 访问节点 3 左子树,找到节点 6;
  7. 由于节点 6 无左右子树,因此节点 6 遍历完成,回到节点 3 并遍历其右子树,找到节点 7;
  8. 节点 7 无左右子树,因此以节点 3 为根节点的子树遍历完成,同时回归节点 1。由于节点 1 的左右子树全部遍历完成,因此整个二叉树遍历完成;

因此,图 1 中二叉树采用先序遍历得到的序列为:

1 2 4 5 3 6 7

1、递归实现

二叉树的先序遍历采用的是递归的思想,因此可以递归实现,其 C 语言实现代码为:

#include <stdio.h>

#include <string.h>

#define TElemType int

//构造结点的结构体

typedef struct BiTNode{

        TElemType data;//数据域

        struct BiTNode *lchild,*rchild;//左右孩子指针

}BiTNode,*BiTree;

//初始化树的函数

void CreateBiTree(BiTree *T){

        *T=(BiTNode*)malloc(sizeof(BiTNode));

        (*T)->data=1;

        (*T)->lchild=(BiTNode*)malloc(sizeof(BiTNode));

        (*T)->rchild=(BiTNode*)malloc(sizeof(BiTNode));

        (*T)->lchild->data=2;

        (*T)->lchild->lchild=(BiTNode*)malloc(sizeof(BiTNode));

        (*T)->lchild->rchild=(BiTNode*)malloc(sizeof(BiTNode));

        (*T)->lchild->rchild->data=5;

        (*T)->lchild->rchild->lchild=NULL;

        (*T)->lchild->rchild->rchild=NULL;

        (*T)->rchild->data=3;

        (*T)->rchild->lchild=(BiTNode*)malloc(sizeof(BiTNode));

        (*T)->rchild->lchild->data=6;

        (*T)->rchild->lchild->lchild=NULL;

        (*T)->rchild->lchild->rchild=NULL;

        (*T)->rchild->rchild=(BiTNode*)malloc(sizeof(BiTNode));

        (*T)->rchild->rchild->data=7;

        (*T)->rchild->rchild->lchild=NULL;

        (*T)->rchild->rchild->rchild=NULL;

        (*T)->lchild->lchild->data=4;

        (*T)->lchild->lchild->lchild=NULL;

        (*T)->lchild->lchild->rchild=NULL;

}

//模拟操作结点元素的函数,输出结点本身的数值

void displayElem(BiTNode* elem){

        printf("%d ",elem->data);

}

//先序遍历

void PreOrderTraverse(BiTree T){

        if (T) {

                displayElem(T);//调用操作结点数据的函数方法

                PreOrderTraverse(T->lchild);//访问该结点的左孩子

                PreOrderTraverse(T->rchild);//访问该结点的右孩子

        }

        //如果结点为空,返回上一层

        return;

}

int main() {

        BiTree Tree;

        CreateBiTree(&Tree);

        printf("先序遍历: \n");

        PreOrderTraverse(Tree);

}

运行结果:

先序遍历:
1 2 4 5 3 6 7

2、非递归实现

而递归的底层实现依靠的是栈存储结构,因此,二叉树的先序遍历既可以直接采用递归思想实现,也可以使用栈的存储结构模拟递归的思想实现,其 C 语言实现代码为:

#include <stdio.h>

#include <string.h>

#define TElemType int

int top=-1;//top变量时刻表示栈顶元素所在位置

//构造结点的结构体

typedef struct BiTNode{

        TElemType data;//数据域

        struct BiTNode *lchild,*rchild;//左右孩子指针

}BiTNode,*BiTree;

//初始化树的函数

void CreateBiTree(BiTree *T){

        *T=(BiTNode*)malloc(sizeof(BiTNode));

        (*T)->data=1;

        (*T)->lchild=(BiTNode*)malloc(sizeof(BiTNode));

        (*T)->rchild=(BiTNode*)malloc(sizeof(BiTNode));

        (*T)->lchild->data=2;

        (*T)->lchild->lchild=(BiTNode*)malloc(sizeof(BiTNode));

        (*T)->lchild->rchild=(BiTNode*)malloc(sizeof(BiTNode));

        (*T)->lchild->rchild->data=5;

        (*T)->lchild->rchild->lchild=NULL;

        (*T)->lchild->rchild->rchild=NULL;

        (*T)->rchild->data=3;

        (*T)->rchild->lchild=(BiTNode*)malloc(sizeof(BiTNode));

        (*T)->rchild->lchild->data=6;

        (*T)->rchild->lchild->lchild=NULL;

        (*T)->rchild->lchild->rchild=NULL;

        (*T)->rchild->rchild=(BiTNode*)malloc(sizeof(BiTNode));

        (*T)->rchild->rchild->data=7;

        (*T)->rchild->rchild->lchild=NULL;

        (*T)->rchild->rchild->rchild=NULL;

        (*T)->lchild->lchild->data=4;

        (*T)->lchild->lchild->lchild=NULL;

        (*T)->lchild->lchild->rchild=NULL;

}

//前序遍历使用的进栈函数

void push(BiTNode** a,BiTNode* elem){

        a[++top]=elem;

}

//弹栈函数

void pop( ){

        if (top==-1) {

                return ;

        }

        top--;

}

//模拟操作结点元素的函数,输出结点本身的数值

void displayElem(BiTNode* elem){

        printf("%d ",elem->data);

}

//拿到栈顶元素

BiTNode* getTop(BiTNode**a){

        return a[top];

}

//先序遍历非递归算法

void PreOrderTraverse(BiTree Tree){

        BiTNode* a[20];//定义一个顺序栈

        BiTNode * p;//临时指针

        push(a, Tree);//根结点进栈

        while (top!=-1) {

                p=getTop(a);//取栈顶元素

                pop();//弹栈

                while (p) {

                        displayElem(p);//调用结点的操作函数

                        //如果该结点有右孩子,右孩子进栈

                        if (p->rchild) {

                                push(a,p->rchild);

                        }

                        p=p->lchild;//一直指向根结点最后一个左孩子

                }

        }

}

int main(){

        BiTree Tree;

        CreateBiTree(&Tree);

        printf("先序遍历: \n");

        PreOrderTraverse(Tree);

}

运行结果

先序遍历:
1 2 4 5 3 6 7

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

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

相关文章

Python 命令行工具开发入门

在实际应用中,我们常常需要编写一些命令行工具,以便在终端或脚本中执行特定的任务。本文将介绍如何使用 Python 编写一个简单的命令行工具,并展示一些常见的实用技巧。 1. 概述 我们的命令行工具将具备以下功能: 输出文件内容到标准输出显示 Python 版本号显示帮助信息2.…

QT实现USB摄像头接入显示

一、UVC协议简介 UVC全称是USB Video Class&#xff08;USB视频类&#xff09;&#xff0c;是一种标准化的USB视频设备通信协议&#xff0c;它定义了摄像头与主机之间的数据传输协议和格式。 UVC协议的出现&#xff0c;解决了摄像头厂商之间互不兼容&#xff0c;以及摄像头应…

(二十八)ATP应用测试平台——使用electron集成vue3桌面应用程序

前言 Electron 是一个开源的框架&#xff0c;它允许使用 Web 技术&#xff08;HTML、CSS 和 JavaScript&#xff09;构建跨平台的桌面应用程序。通过 Electron&#xff0c;开发者可以使用前端技术栈来创建具有原生应用程序体验的桌面应用。 Electron可以在 Windows、Mac 和 L…

Nacos源码解析:String.intern()方法的巧妙应用

引言&#xff1a; 在阅读Nacos源码时&#xff0c;发现其中使用了String.intern()方法&#xff0c;这个使用并不是简单的拼接字符串&#xff0c;而是在特定场景下的优化手段。本文将深入探讨Nacos源码中String.intern()方法的应用&#xff0c;以及为什么要使用这个方法。 1. N…

【前端web入门第一天】02 HTML图片标签 超链接标签 音频标签 视频标签

文章目录: 1.HTML图片标签 1.1 图像标签-基本使用1.2 图像标签-属性1.3 路径 1.3.1 相对路径 1.3.2 绝对路径 2.超链接标签 3.音频标签 4.视频标签 1.HTML图片标签 1.1 图像标签-基本使用 作用:在网页中插入图片。 <img src"图片的URL">src用于指定图像…

《Python 简易速速上手小册》第8章:Python 网络编程与 Web 开发(基于最新版 Python3.12 编写)

注意&#xff1a;本《Python 简易速速上手小册》 核心目的在于让零基础新手「快速构建 Python 知识体系」 文章目录 <mark >注意&#xff1a;本《Python 简易速速上手小册》<mark >核心目的在于让零基础新手「快速构建 Python 知识体系」 8.1 Python 中的网络通信…

HCS 华为云Stack产品组件

HCS 华为云Stack产品组件 Cloud Provisioning Service(CPS) 负责laas的云平台层的部署和升级是laas层中真正面向硬件设备&#xff0c;并将其池化软件化的部件。 Service OM 资源池(计算/存储/网络)以及基础云服务(ECS/EVS/PC)的管理工具。 ManageOne ManageOne包括服务中心…

数据结构(1)--> 顺序表

定义&#xff1a; 顺序表存储定义&#xff1a; 把逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构&#xff0c;顺序表功能的实现借助于数组&#xff0c;通过对数组进行封装&#xff0c;从而实现增删查改的功能&#xff0c;严格意义上来说&#xff08;数组无法实现…

如何发布自己的npm包,详细流程

发布自己的npm包需要遵循以下具体流程&#xff1a; 创建npm账号&#xff1a;打开浏览器&#xff0c;访问npm官网&#xff0c;注册一个npm账号。 创建项目文件夹并进入&#xff1a;在本地创建一个项目文件夹&#xff0c;并使用终端进入该文件夹。 初始化包信息管理文件&#x…

第六课:Prompt

文章目录 第六课&#xff1a;Prompt1、学习总结&#xff1a;Prompt介绍预训练和微调模型回顾挑战 Pre-train, Prompt, PredictPrompting是什么?prompting流程prompt设计 课程ppt及代码地址 2、学习心得&#xff1a;3、经验分享&#xff1a;4、课程反馈&#xff1a;5、使用Mind…

由两个有限项的等差数列B, C, 求有多少个有限项的等差数列A,满足C是A, B的所有公共项,若有无穷个A满足条件,输出-1

题目 思路&#xff1a; #include <bits/stdc.h> using namespace std; #define int long long #define pb push_back const int maxn 1e6 5, inf 1e9 5, maxm 4e4 5, mod 1e9 7, N 1e6; // int a[maxn], b[maxn]; int n, m; string s;int qpow(int a, int b){i…

Unity中创建Ultraleap 3Di交互项目

首先&#xff0c;创建新的场景 1、创建一个空物体&#xff0c;重命名为【XP Leap Provider Manager】&#xff0c;并在这个空物体上添加【XR Leap Provider Manager】 在物体XP Leap Provider Manager下&#xff0c;创建两个子物体Service Provider(XR)和Service Provider(…

C语言与操作符相关的经典例题

目录 一道变态的面试题&#xff1a;不能创建临时变量&#xff08;第三个变量&#xff09;&#xff0c;实现两个数的交换。 编写代码实现&#xff1a;求一个整数存储在内存中的二进制中1的个数。 二进制位置0或者置1 如果以下的知识点不是很清楚的可以去看这篇文章&#xff1…

Deepin基本环境查看 - 目录/大纲

第一次整理本文材料才发现 原来写博客和写代码一样的 多章节的内容&#xff0c;必须将目录单独取出来 这样才方便作者&#xff0c;也方便读者 奇怪的知识又增加了 ^^ Deepin基本环境查看 - 目录Deepin基本环境查看&#xff08;一&#xff09;【基本信息】Deepin基本环境查看&am…

阿里云部署配置幻兽帕鲁Palworld联机服务器详细教程

阿里云作为国内领先的云计算服务提供商&#xff0c;为企业和个人提供了丰富的云服务。本文将为大家详细介绍如何在阿里云上配置幻兽帕鲁Palworld联机服务器&#xff0c;以便与更多玩家共同体验游戏的乐趣。 第一步&#xff1a;登录服务器创建页 1、进入幻兽帕鲁联机服务快速部…

数据结构——顺序队列(循环)

采用顺序表的方式实现循环队列。其中关键在于如何判断队列已满。通常情况下&#xff0c;当对头和队尾指向同一个节点时&#xff0c;可以判断为队空。但是&#xff0c;倘若队尾不断增加&#xff0c;最后队尾也会指向对头&#xff0c;此时队满和队空的判断条件一致。以下有三种对…

剖析线程池ForkJoinPool

文章目录 一、引言二、ForkJoinPool概述三、工作原理四、案例及分析案例背景案例分析实现 五、注意事项六、总结 一、引言 在并发编程中&#xff0c;线程池是一个常见的工具&#xff0c;用于管理和复用线程&#xff0c;以避免频繁地创建和销毁线程带来的开销。ForkJoinPool是J…

11. 双目视觉之立体视觉基础

目录 1. 深度恢复1.1 单目相机缺少深度信息1.2 如何恢复场景深度&#xff1f;1.3 深度恢复的思路 2. 对极几何约束2.1 直观感受2.2 数学上的描述 1. 深度恢复 1.1 单目相机缺少深度信息 之前学习过相机模型&#xff0c;最经典的就是小孔成像模型。我们知道相机通过小孔成像模…

力扣LCR 180. 文件组合(双指针)

Problem: LCR 180. 文件组合 文章目录 题目描述思路及解法复杂度Code 题目描述 思路及解法 本题目可以利用滑动窗口的技巧&#xff08;滑动窗口就是双指针的运用&#xff09;解决&#xff0c;具体实现如下 1.逻辑上生成窗口&#xff1a;让两个指针i&#xff0c;j分别初始化为1…

Java基本数据类型-字符型,布尔型

目录 字符型转义字符实例运行结果 ASCII码实例运行结果 布尔型实例运行结果 字符型 Java中使用单引号来表示字符常量&#xff0c;字符型在内存中占2个字节。char 类型用来表示在Unicode编码表中的字符。Unicode编码被设计用来处理各种语言的文字&#xff0c;它占2个字节&#…