数据结构(超详细讲解!!)第二十四节 二叉树(下)

1.遍历二叉树

 在二叉树的一些应用中,常常要求在树中查找具有某种特征的结点,或者对树中全部结点逐一进行某种处理。这就引入了遍历二叉树的问题,即如何按某条搜索路径访问树中的每一个结点,使得每一个结点仅且仅被访问一次。    

遍历二叉树:是指按照某种方法顺着某一条搜索路径寻访二叉树的结点,使得每个结点均被访问一次且仅被访问一次。

1.递归遍历

一棵二叉树由根结点、根结点的左子树和根结点的右子树3部分组成,因而只要依次遍历这3部分,就能遍历整棵二叉树。    

遍历的次序:假如以L、D、R分别表示遍历左子树、遍历根结点和遍历右子树,遍历整个二叉树则有DLR、LDR、LRD、DRL、RDL、RLD六种遍历方案。若规定先左后右,则只有前三种情况,分别规定为:      

DLR——先(根)序遍历,      

LDR——中(根)序遍历,      

LRD——后(根)序遍历。

1.先序遍历

先序遍历二叉树算法的框架是 :若二叉树为空,遍历结束,否则: 访问根结点; 先序遍历根结点的左子树; 先序遍历根结点的右子树。

void  PreOrder(BiTree bt)	 /* bt为指向根结点的指针*/
{if (bt)		/*如果bt为空,结束*/{visit (bt ); 	  /*访问根结点*/PreOrder (bt -> lchild);  /*先序遍历左子树*/PreOrder (bt -> rchild);  /*先序遍历右子树*/}
}

2.中序遍历

中序遍历二叉树算法的框架是: 若二叉树为空,遍历结束,否则: 中序遍历根结点的左子树; 访问根结点; 中序遍历根结点的右子树。

void  InOrder(BiTree bt)/* bt为指向二叉树根结点的指针*/
{if (bt )		/*如果bt为空,结束*/{InOrder (bt -> lchild);	/*中序遍历左子树*/Visit (bt);	/*访问根结点*/InOrder (bt -> rchild);	/*中序遍历右子树*/}
}

3.后序遍历

后序遍历二叉树算法的框架是:若二叉树为空,遍历结束,否则 后序遍历根结点的左子树; 后序遍历根结点的右子树; 访问根结点。

void  PostOrder(BiTree bt)
/* bt为指向二叉树根结点的指针*/
{if (bt )		/*如果bt为空,结束*/{PostOrder (bt -> lchild);/*后序遍历左子树*/PostOrder (bt -> rchild);/*后序遍历右子树*/visit (bt );	/*访问根结点*/}
}

通过上述三种不同的遍历方式得到三种不同的线性序列,它们的共同的特点是有且仅有一个开始结点和一个终端结点,其余各结点都有且仅有一个前驱结点和一个后继结点。    

从二叉树的遍历定义可知,三种遍历算法的不同之处仅在于访问根结点和遍历左右子树的先后关系。如果在算法中隐去和递归无关的语句visit(),则三种遍历算法是完全相同的。遍历二叉树的算法中的基本操作是访问结点,显然,不论按那种方式进行遍历,对含n个结点的二叉树,其时间复杂度均为O(n)。所含辅助空间为遍历过程中占的最大容量,即树的深度。最坏的情况下为n,则空间复杂度也为O(n)。

4.层序遍历

 二叉树的层次遍历:是指从二叉树的第一层(根结点)开始,从上至下逐层遍历,在同一层中,按从左到右的顺序对结点逐个进行访问。

利用队列来实现    :

算法思想:遍历从二叉树的根结点开始,首先将根结点入队列,然后执行下面的操作:       

1)取出队头元素;        

2) 访问该元素所指结点;        

3) 若该元素所指结点的左、右孩子结点非空,则将该元素所指结点的左孩子指针和右孩子指针入队。        

4)若队列非空,重复1)-3);当队列为空时,二叉树的层次遍历结束。

void LevelOrder( BiTree bt) /*层次遍历二叉树bt算法*/
{	初始化队列Q;if ( bt == NULL )  	return;bt入队列Q;while( 队列Q不空){	p出队元素;Visit( p); /*访问出队结点*/if ( p->lchild) /*队首结点左孩子不空,入队*/{ 	p->lchild入队Q	}if (p->rchild)  /*队首结点右孩子不空,入队*/{ 	p->rchild入队Q		}}
}

5.练习

1.找出分别满足下面条件的所有二叉树(非空形态):    

(a)前序序列和中序序列相同;      

(b)中序序列和后序序列相同;    

 (c)前序序列和后序序列相同;    

 (d)前序序列、中序序列和后序序列都相同。

2.已知一棵二叉树的中序序列和后序序列分别为BDCEAFHG和DECBHGFA,画出这棵二叉树。

结论:

1)  由二叉树的前序序列和中序序列可以唯一确定这棵二叉树。

2)  由二叉树的后序序列和中序序列可以唯一确定这棵二叉树。

3)  由二叉树的前序序列和后序序列不能唯一确定这棵二叉树。

2.非递归遍历

二叉树前序遍历的非递归算法的关键:在前序遍历过某结点的整个左子树后,如何找到该结点的右子树的根指针。

解决办法:在访问完该结点后,将该结点的指针保存在栈中,以便以后能通过它找到该结点的右子树。

1.先序遍历

先序算法执行轨迹

步骤:

1.栈s初始化;

2.循环直到root为空或栈s为空

2.1 当root不空时循环

2.1.1 输出root->data;(可将输出变为任何处理)    

2.1.2 将指针root的值保存到栈中;    

2.1.3 继续遍历root的左子树

2.2 如果栈s不空,则

2.2.1 将栈顶元素弹出至root;

2.2.2 准备遍历root的右子树;

//先序遍历
Void Firstorder(BiTree bt)
{	 p=bt;       /*根结点为当前结点*/
S=Initial( );  /*初始化栈*/
While(p||!Empty(S))  
{
While(p) /*当前结点不空*/
{  visit(p);  /*访问结点*/
Push(S,p); /*当前结点入栈*/
p=p->Lchild; /*左孩子作为当前结点*/}
If(!Empty(S))  /*栈不空*/
{
p=pop(s);   /*出栈*/
p=p->Rchild; /*右孩子作为当前结点*/
}
}
}

2.中序遍历

//中序遍历
Void Inorder(BiTree bt)
{	 p=bt;       /*根结点为当前结点*/
S=Initial( );  /*初始化栈*/
While(p||!Empty(S))  
{
While(p) /*当前结点不空*/
{
Push(S,p); /*当前结点入栈*/
p=p->Lchild; /*左孩子作为当前结点*/}
If(!Empty(S))  /*栈不空*/
{
p=pop(s);   /*出栈*/
Visit(p);   /*访问结点*/
p=p-Rchild; /*右孩子作为当前结点*/
}
}
}

3.后序遍历

typedef enum{L,R} tagtype;     /*定义枚举类型*/
typedef struct {
Bitree ptr;
tagtype tag;
}stacknode;                  /*定义栈结点类型*/ 
typedef struct{
stacknode Elem[maxsize];
int top;
}SqStack;                     /*定义顺序栈*/void PostOrderUnrec(Bitree bt)   /*后序遍历算法*/{   p=bt;If(!p) return;     
do
{    while (p)        /*遍历左子树*/       
{
x.ptr = p;               
x.tag = L;           /*标记为左子树*/               
push(s,x);         /*入栈*/
p=p->lchild;      /*左孩子作为当前结点*/
}            
while (!StackEmpty(s) && s.Elem[s.top].tag==R) {     
x = pop(s);
p = x.ptr;
visite(p);         //tag为R,表示右子树访问完毕,故访问根结点               
}if (!StackEmpty(s)){                    
s.Elem[s.top].tag =R;             /*遍历右子树*/                    
p=s.Elem[s.top].ptr->rchild;   /*右孩子作为当前结点*/                      
}
}while (!StackEmpty(s));
}  

4.练习

1.交换二叉树各结点的左、右子树(递归算法)

void  unknown ( BiTree   T ){BiTreeNode  *p = T,  *temp;if ( p != NULL ) {temp = p->lchild; p->lchild = p->rchild;p->rchild = temp;unknown ( p->lchild );unknown ( p->rchild );}}

2.不用栈消去递归算法中的第二个递归语句 (即消去尾递归)

void unknown ( BiTree T ) 
{BiTreeNode *p = T, *temp;while ( p != NULL ) {temp = p->lchild; p->lchild = p->rchild;p->rchild = temp;unknown ( p->lchild );p = p->rchild;}}

3.使用栈消去递归算法中的两个递归语句

void unknown ( BiTree  T ) 
{BiTreeNode  *p,  *temp,S[Max]; int top=-1; if ( T != NULL ) 
{top++;S[top]= T;while ( top>-1 ){p=S[top]; top--;    /*栈中退出一个结点*/temp = p->lchild;     /*交换子女*/p->lchild = p->rchild;p->rchild = temp;if ( p->rchild != NULL )top++;S[top]= p->rchild;if ( p->lchild != NULL )top++;S[top]= p->p->lchild;}} 
}

2.应用

1.设计算法输出二叉树的所有叶子结点的值。

基本思想:        

若二叉树为空树,则叶子数目为0。        

对于一棵非空二叉树,如果它的左子树和右子树都为空,那么此二叉树只有一个结点,就是叶子,此时叶子数目为1;否则,二叉树的叶子数目为左子树叶子数目和右子树叶子数目的总和。

int BitreeLeaf ( BiTree bt )
{if ( bt == NULL ) return 0 ;	/* 空树,叶子数为0 */if ( bt->lchild ==NULL&& bt->rchild == NULL)	return 1 ; /*只有一个根结点,叶子数为1*/return ( BitreeLeaf ( bt -> lchild ) + BitreeLeaf ( bt -> rchild )) ;
}

2.设计算法求二叉树的深度。

基本思想:      

若二叉树为空,约定二叉树的深度为0;      

对于一棵二叉树,如果它的左子树和右子树都为空,那么此二叉树只有一个根结点,此时二叉树的深度为1;否则,先求出其左、右子树的深度depthL和depthR,那么整棵二叉树的深度为1+max(depthL,depthR)。

int BitreeDepth ( BiTree bt )
{	int d = 0,depthL, depthR;  /*depthL和depthR分别为左、右子树的深度*/if ( bt == NULL ) return 0 ;		/*空树,深度为0 */if ( bt -> lchild ==NULL && bt -> rchild == NULL)				return 1;		/*叶子结点,深度为1 */depthL = BitreeDepth ( bt -> lchild ) ;	/*左子树深度 */depthR = BitreeDepth ( bt -> rchild ) ;	/*右子树深度 */d = max (depthL , depthR )	/*d为左右子树中较深者的深度*/return d+1 ; 	/* 整棵二叉树的深度为左、右子树中较深者的深度+1 */
}

3.创建二叉树

创建二叉树的方法有两种,一种是给定一棵二叉树的先序遍历序列和中序遍历序列创建二叉树,另一种是给定一棵二叉树的“扩展先序遍历序列”创建二叉树。

(1)结合先序遍历序列和中序遍历序列创建二叉树            

基本思想:

先序遍历的第一个结点一定是二叉树的根结点,而根据中序遍历规则,这个结点将同一棵二叉树的中序遍历序列分成了左、右两部分,左边部分是二叉树的根结点的左子树的中序遍历序列,右边部分是二叉树的根结点的右子树的中序遍历序列。根据这两个子序列,在先序序列中找到对应的子序列,左子序列的第一个结点为左子树的根结点,右子序列的第一个结点为右子树的根结点。对左右子树,再反复利用这个方法,最终根据先序序列和中序序列能唯一地确定出一棵二叉树。

(2)结合“扩展先序遍历序列”创建二叉树。    

扩展先序遍历序列:

就是先对原有二叉树用空子树进行扩展,使每个结点的左右子树(包括空子树)都存在,然后再对扩展后的二叉树进行先序遍历。遍历序列中用特定的符号表示空子树。

其扩展先序遍历序列为:

5 8 9 0 0 7 0 0 6 0 3 4 0 0 0    

其中“0”表示空子树。

BiTree CreateBiTree(char str[])
{	BiTree bt;static int i=0;char c = str[i++];if( c==‘.’ )	bt = NULL;/* 创建空树 */else{	bt = (BiTree)malloc(sizeof(BiTreeNode)); bt->data = c; 	/* 创建根结点 */bt->lchild  = CreateBiTree(str); /* 创建左子树 */bt->rchild = CreateBiTree(str); /* 创建右子树 */}return bt;
} 

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

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

相关文章

python3实现tailf命令

由于windows上面没有类似linux上面的tailf命令,所以下面的python脚本来代替其能力。 tailf.py import re import timeimport os import argparsedef follow(thefile):thefile.seek(0, os.SEEK_END)while True:_line thefile.readline()if not _line:time.sleep(0…

RabbitMQ 搭建和工作模式

MQ基本概念 1. MQ概述 MQ全称 Message Queue([kjuː])(消息队列),是在消息的传输过程中保存消息的容器。多用于分布式系统之间进行通信。 (队列是一种容器,用于存放数据的都是容器&#xff0…

docker部署微服务

目录 docker操作命令 镜像操作命令 拉取镜像 导出镜像 删除镜像 加载镜像 推送镜像 部署 pom文件加上 在每个模块根目录加上DockerFile文件 项目根目录加上docker-compose.yml文件 打包,clean,package 服务器上新建文件夹 测试docker-compo…

基于springboot和微信小程序的流浪动物管理系统

基于springboot和微信小程序的流浪动物管理系统 内容简介 基于微信小程序实现的流浪动物管理系统,该系统针对用户与管理员两种角色进行开发。 1、提供流浪动物的信息查询功能,包括品种、年龄、性别、健康状况等,提供救助活动报名功能。 2…

5.1 PBR基础 BRDF介绍

基于物理的渲染(Physically Based Rendering,PBR)是指使用基于物理原理和微平面理论建模的着色/光照模型,以及使用从现实中测量的表面参数来准确表示真实世界材质的渲染理念。 一、反射率方程 理论基础放在参考链接里。 直接开始…

【uniapp】uniapp开发小程序定制uni-collapse(折叠面板)

需求 最近在做小程序,有一个类似折叠面板的ui控件,效果大概是这样 代码 因为项目使用的是uniapp,所以打算去找uniapp的扩展组件,果然给我找到了这个叫uni-collapse的组件(链接:uni-collapse&#xff09…

超详细的接口测试

本文主要分为两个部分: 第一部分:主要从问题出发,引入接口测试的相关内容并与前端测试进行简单对比,总结两者之前的区别与联系。但该部分只交代了怎么做和如何做?并没有解释为什么要做? 第二部分&#xf…

ShellCode漏洞

ShellCode漏洞 可以查看如下网址: https://www.cnblogs.com/kakadewo/p/12996878.html 定义: shellcode是一段用于利用软件漏洞而执行的代码,shellcode为16进制之机械码,以其经常让攻击者获得shell而得名。shellcode常常使用机…

老鸟总结,软件测试工程师职业发展规划路线,入门到冲击大厂...

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 1、测试工程师发展…

YOCTO 下载repo工具失败解决办法

curl https://mirrors.tuna.tsinghua.edu.cn/git/git-repo -o repocp repo ~/binchmod ax ~/bin/repo如果使用时报错, 切换ubuntu 到 python3 版本。gedit repo 修改repo默认链接地址:REPO_URL "https://gerrit.googlesource.com/git-repo"…

Spring AOP-面向切面编程概念

Spring AOP-面向切面编程概念 AOP(面向切面编程)是编程范式的一种,它允许程序员将横切关注点(cross-cutting concerns)模块化。在面向切面编程中,这些横切关注点通常体现为在多个点重复出现的代码&#xf…

Android设计模式--适配器模式

至诚之道,可以前知 一,定义 适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。 适配器模式在我们的开发中使用率极高,ListView,GridView&am…

面试cast:reinterpret_cast/const_cast/static_cast/dynamic_cast

目录 1. cast 2. reinterpret_cast 3. const_cast 3.1 加上const的情况 3.2 去掉const的情况 4. static_cast 4.1 基本类型之间的转换 4.2 void指针转换为任意基本类型的指针 4.3 子类和父类之间的转换 5. dynamic_cast 5.1 RTTI(Run-time Type Identification) 1.…

Selenium实现多页面切换

当使用 Selenium 进行自动化测试或爬取数据时,有时需要处理多个页面之间的切换。以下是一些可能需要多页面切换的情况: 1、打开新窗口/页面: 在当前页面上点击链接、按钮或执行某些操作时,可能会打开一个新的窗口或页面。此时&a…

【element优化经验】怎么让element-ui中表单多语言切换排版不乱

目录 前言: 痛点: 1.左对齐,右对齐在中文和外语情况下字数不同,固定宽度会使名称换行,不在整行对齐,影响美观。 2.如果名称和输入框不在一行,会使页面越来越长 3.label-width值给变量&#…

随笔记录-springmvc_ResourceHandlerRegistry+ResourceHttpRequestHandler

环境:springboot-2.7.5 配置文件配置静态资源映射 springboot配置静态资源映射方式是通过 WebMvcAutoConfiguration 实现的 spring: # resources: # # 自springboot 2.5.5之后,该属性已经被废弃,使用spring.web.resources.static-locat…

爬虫逆向你应该懂得Javascript知识

背景 大家在学习爬虫逆向的时候,一般都会涉及到对js源文件进行代码扣去,但是有的时候,你最好有js基础,能发现加密或者解密在那个位置,或者是能用python改写js代码,这就对个人的Javascript的能力有一定要求…

Switch的使用及其注意事项

注意第五点要看清,case执行完后匹配没有成功,如过有Default,将会执行Default,如果有case在Default之后,而且Default没有break语句,那么将会继续执行case的语句,此时case中的常量表达式只起语句标…

【Skynet 入门实战练习】游戏模块划分 | 基础功能模块 | timer 定时器模块 | logger 日志服务模块

文章目录 游戏模块基础功能模块定时器模块日志模块通用模块 游戏模块 游戏从逻辑方面可以分为下面几个模块: 注册和登录网络协议数据库玩法逻辑其他通用模块 除了逻辑划分,还有几个重要的工具类模块: Excel 配置导表工具GM 指令测试机器人…