草庐IT

数据结构:二叉树

Az1r 2023-03-28 原文

定义

特点

  • 每个节点最多有两棵子树,所以二叉树中不存在度大于2的节点。
  • 左子树和右子树是有区别的,次序不能颠倒。
  • 即使某个节点只有1个子节点,也是有左右之分的。

特殊的二叉树:

  1. 斜树:正如上图的树1和树2,向一边斜的二叉树。
  2. 满二叉树:叶子节点都在最后一层,也就是说,非叶子节点都有左右子树

  1. 完全二叉树:

    对一棵具有n个节点的二叉树按层序编号,如果编号为 i(1<=i<=n)的节点与同样深度的满二叉树中编号为 i的节点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树,如下图所示

    ​ 就是说,这样 1开始,从左向右,从上至下的编号,如果所有有子节点的节点 i满足:

    ​ 左儿子 j:$ j = 2 * i$ ,右儿子 k: $ k = 2 * i + 1$,那么它就是一棵完全二叉树

    1. 完全二叉树不一定是满二叉树
    2. 满二叉树一定是完全二叉树
    3. 手写的堆就是一棵完全二叉树

性质

  1. 二叉树第 i层最多有\(2^{i-1}\)个节点。
  2. 深度为 k的二叉树最多有\(2^k-1\)个节点。(1 + 2 + 4 + 8 + ··· + \(2^{k - 1}\)
  3. 有 n个节点的完全二叉树的深度为 \(|log_2n| + 1\)\(|x|\)代表对x向下取整。证明略。
  4. 从1开始,将完全二叉树从左到右,从上至下,依次标号(如上图),那么对于节点 i,左儿子 j:$ j = 2 * i$ ,右儿子 k: $ k = 2 * i + 1$。

存储结构

顺序存储结构

就是刚刚的性质4,存储的是完全二叉树。(也可以不是)

用数组模拟,下标代表树的标号。

主要的应用:线段树,堆

链式存储结构

又名二叉链表,即在结构体中定义两个指针,分别指向它的左儿子和右儿子。

typedef struct BiTNode  /* 结点结构 */
{
   TElemType data;		/* 结点数据 */
   struct BiTNode *lchild,*rchild; /* 左右孩子指针 */
}BiTNode,*BiTree;

遍历二叉树

前序遍历

若二叉树为空则返回,否则先访问根节点,再前序遍历左子树,再前序遍历右子树

总结起来:根左右

中序遍历

若二叉树为空则返回,否则先中序遍历左子树,再访问根节点,再中序遍历右子树

总结起来: 左根右

后序遍历

若二叉树为空则返回,否则先后序遍历左子树,再后序遍历右子树,最后访问根节点

总结起来:左右根

层序遍历

顾名思义,一层一层的遍历二叉树。

前三种是递归的遍历,要用到栈,层序遍历则是循环的遍历,要用到队列!

具体实现方法见代码。


作业

作业题目: 二叉树存储结构的建立、遍历和应用树和二叉树遍历是树形结构的最基础、最重要的核心算法。本作业要求掌
握和巩固二叉树的存储结构的建立方法、二叉树的遍历方法、过程及应用。


作业要求:

  1. 编写建立二叉树的动态(或者静态) 二叉链表存储结构(左右链表示) 的程序,并以适当的形式显示和保存二叉树;
  2. 采用二叉树的上述二叉链表存储结构,编写程序实现二叉树的先序、中序和后序遍历的递归非递归算法以及层序遍历算法, 并以适当的形式显示和保存二叉树及其相应的遍历序列;
  3. 设计并实现判断任意一棵二叉树是否为完全二叉树的算法。
  4. 设计并实现计算任意一棵二叉树的宽度的(递归或非递归)算法。 二叉树的宽度是指其各层结点数的最大值。

思路:

  1. 非递归算法则是手写一个栈用于存储节点信息。
  2. 判断是否为完全二叉树,可采用层序遍历的方式,无论子节点是否为空,都加入到队列中。当遍历到空节点时,退出,判断队列剩余元素是否全为空。如果是则为完全二叉树。
  3. 计算宽度也是用的层序遍历的方法,每一层计算下队头到队尾的元素数,更新最大值即可。

栈的实现

typedef struct Stack
{
	BiTree s[MAXSIZE];//存储二叉树节点
	int top;	//栈顶下标
}Stack;

队列的实现

typedef struct Queue
{
	BiTree q[MAXSIZE];//存储节点
	int head;//头
	int tail;//尾
}Queue;

读入

读入前序遍历的二叉树,空节点使用 # 来表示

比如:ABDH#K###E##CFI###G#J##

然后再按照前序遍历的方式依次读入即可,遇到 #不递归。

代码

点击查看代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>  
#include <time.h>

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

#define MAXSIZE 100 /* 存储空间初始分配量 */

typedef int Status;		/* Status是函数返回的类型*/

/* 用于构造二叉树 */
int treeIndex = 1;
typedef char String[MAXSIZE]; /*  0号单元存放串的长度 */
String str; // str为初始化的字符串 
FILE *fp;   // 保存用的文件指针 

typedef char TElemType;
TElemType Nil=' '; /* 字符型以空格符为空 */

typedef struct BiTNode  /* 结点结构 */
{
   TElemType data;		/* 结点数据 */
   struct BiTNode *lchild,*rchild; /* 左右孩子指针 */
}BiTNode,*BiTree;

/* 非递归使用的栈 */
typedef struct Stack
{
	BiTree s[MAXSIZE];
	int top;	
}Stack;

/* 层序遍历使用的队列 */
typedef struct Queue
{
	BiTree q[MAXSIZE];
	int head;
	int tail;
}Queue;

Status StrAssign(String T,char *chars);	// 前序遍历初始化 
Status visit(TElemType e);				// 访问树的节点元素	
Status InitBiTree(BiTree *T);			// 初始化 
void DestroyBiTree(BiTree *T);			// 释放二叉树 
void CreateBiTree(BiTree *T);			// 建立二叉树
Status BiTreeEmpty(BiTree T);			// 判空 
int BiTreeDepth(BiTree T);				// 求二叉树的深度 
TElemType Root(BiTree T);				// 求二叉树的根节点 
TElemType Value(BiTree p);				// 返回该节点的值 
void Assign(BiTree p,TElemType value);	// 给二叉树的节点赋值 
void PreOrderTraverseRec(BiTree T);		// 前序遍历递归版 
void PreOrderTraverse(BiTree T);		// 前序遍历非递归版 
void InOrderTraverseRec(BiTree T);		// 中序遍历递归版 
void InOrderTraverse(BiTree T);			// 中序遍历非递归版 
void PostOrderTraverseRec(BiTree T);	// 后序遍历递归版 
void PostOrderTraverse(BiTree T);		// 后序遍历非递归版 
void SeqTraverse(BiTree T);				// 层序遍历 
int CalcuWidth(BiTree T);				// 返回二叉树的最大宽度 
Status JudgeCBT(BiTree T);				// 判断是否为完全二叉树 


int main(int args, char * argv[])
{
	BiTree T;
	InitBiTree(&T);
	
	FILE *infile = fopen("in.txt","r");
	
	fp = fopen("out.txt","w");
	if(fp == NULL || infile == NULL)
	{
		(void) fprintf (stderr, "File open error!\n");//错误输出流 
		exit(EXIT_FAILURE);
	}
	
	String input;
	fprintf(stdout,"please input BinaryTree with Preorder:(Empty Node with #)\n");
	fscanf(infile,"%s", input);
	StrAssign(str,input);
	
	fprintf(stdout,"%s\n",input);
	fprintf(fp,"%s\n",input);
	
	CreateBiTree(&T);
	
	fprintf(stdout,"\nPreOrderTraverseRec:");
	fprintf(fp,"\nPreOrderTraverseRec:");
	PreOrderTraverseRec(T);
	
	fprintf(stdout,"\nPreOrderTraverse:");
	fprintf(fp,"\nPreOrderTraverse:");
	PreOrderTraverse(T);
	
	fprintf(stdout,"\nInOrderTraverseRec:");
	fprintf(fp,"\nInOrderTraverseRec:");
	InOrderTraverseRec(T);
	
	fprintf(stdout,"\nInOrderTraverse:");
	fprintf(fp,"\nInOrderTraverse:");
	InOrderTraverse(T);
	
	fprintf(stdout,"\nPostOrderTraverseRec:");
	fprintf(fp,"\nPostOrderTraverseRec:");
	PostOrderTraverseRec(T);
	
	fprintf(stdout,"\nPostOrderTraverse:");
	fprintf(fp,"\nPostOrderTraverse:");
	PostOrderTraverse(T);
	
	fprintf(stdout,"\nSeqTraverse:");
	fprintf(fp,"\nSeqTraverse:");
	SeqTraverse(T);
	
	fprintf(stdout,"\nCBT? %d(1:yes 0:no)\n",JudgeCBT(T)); 
	fprintf(fp,"\nCBT? %d(1:yes 0:no)\n",JudgeCBT(T)); 
	
	fprintf(stdout,"BiTree`s width:%d\n",CalcuWidth(T));
	fprintf(fp,"BiTree`s width:%d\n",CalcuWidth(T));

	DestroyBiTree(&T);
	
	fclose(fp);
	fclose(infile);
	
	return 0;
}



Status StrAssign(String T,char *chars)
{ 
	int i;
	if(strlen(chars) > MAXSIZE)
		return ERROR;
	else
	{
		T[0] = strlen(chars);
		for(i = 1;i <= T[0]; i ++ )
			T[i] = *(chars + i - 1);
		return OK;
	}
}

Status visit(TElemType e)
{
	printf("%c ",e);
	return OK;
}

/* 构造空二叉树T */
Status InitBiTree(BiTree *T)
{ 
	*T=NULL;
	return OK;
}

/* 初始条件: 二叉树T存在。操作结果: 销毁二叉树T */
void DestroyBiTree(BiTree *T)
{ 
	if(*T) /* 递归的销毁,类似于后序遍历 */ 
	{
		if((*T)->lchild) /* 有左孩子 */
			DestroyBiTree(&(*T)->lchild); /* 销毁左孩子子树 */
		if((*T)->rchild) /* 有右孩子 */
			DestroyBiTree(&(*T)->rchild); /* 销毁右孩子子树 */
		free(*T); /* 释放根结点 */
		*T = NULL; /* 空指针赋NULL */
	}
}

/* 按前序输入二叉树中结点的值(一个字符) */
/* #表示空树,构造二叉链表表示二叉树T。 */
void CreateBiTree(BiTree *T)
{ 
	TElemType ch;
	
	/* scanf("%c",&ch); */
	ch=str[treeIndex ++];

	if(ch == '#') 
		*T = NULL;
	else
	{
		*T = (BiTree)malloc(sizeof(BiTNode));
		if(!*T)
		{
			(void) fprintf (stderr, "Cannot allocate memory!\n");//错误输出流 
			exit(OVERFLOW);
		} 
			
		(*T)->data=ch; /* 生成根结点 */
		CreateBiTree(&(*T)->lchild); /* 构造左子树 */
		CreateBiTree(&(*T)->rchild); /* 构造右子树 */
	}
}

/* 初始条件: 二叉树T存在 */
/* 操作结果: 若T为空二叉树,则返回TRUE,否则FALSE */
Status BiTreeEmpty(BiTree T)
{ 
	if(T)
		return FALSE;
	else
		return TRUE;
}


/* 初始条件: 二叉树T存在。操作结果: 返回T的深度 */
int BiTreeDepth(BiTree T)
{
	int i, j;
	if(!T)
		return 0;
	if(T->lchild)
		i = BiTreeDepth(T->lchild);
	else
		i = 0;
	if(T->rchild)
		j = BiTreeDepth(T->rchild);
	else
		j = 0;
	return i > j ? i+1 : j+1;
}

/* 初始条件: 二叉树T存在。操作结果: 返回T的根 */
TElemType Root(BiTree T)
{ 
	if(BiTreeEmpty(T))
		return Nil;
	else
		return T->data;
}

/* 初始条件: 二叉树T存在,p指向T中某个结点 */
/* 操作结果: 返回p所指结点的值 */
TElemType Value(BiTree p)
{
	return p->data;
}

/* 给p所指结点赋值为value */
void Assign(BiTree p,TElemType value)
{
	p->data=value;
}

/* 前序递归遍历 */
void PreOrderTraverseRec(BiTree T)
{ 
	if(T == NULL)
		return;
	fprintf(stdout,"%c",T->data);/* 显示结点数据,可以更改为其它对结点操作 */
	fprintf(fp,"%c",T->data);
	PreOrderTraverseRec(T->lchild); /* 再先序遍历左子树 */
	PreOrderTraverseRec(T->rchild); /* 最后先序遍历右子树 */
}

/* 前序非递归遍历 */
void PreOrderTraverse(BiTree T)
{
	Stack stk;
	stk.top = 0;
	
	while(T || stk.top)
	{
		if(T)
		{
			fprintf(stdout,"%c",T->data);// 先打印根节点 
			fprintf(fp,"%c",T->data);
			stk.s[++ stk.top] = T;// 入栈 
			T = T->lchild;//  递归左节点 
		}else
		{
			T = stk.s[stk.top --]; // 回溯 
			T = T->rchild; 		   // 递归右节点 
		}
	}
}

/* 中序递归遍历 */
void InOrderTraverseRec(BiTree T)
{ 
	if(T == NULL)
		return;
	InOrderTraverseRec(T->lchild); /* 中序遍历左子树 */
	fprintf(stdout,"%c",T->data);/* 显示结点数据,可以更改为其它对结点操作 */
	fprintf(fp,"%c",T->data);
	InOrderTraverseRec(T->rchild); /* 最后中序遍历右子树 */
}
/* 中序非递归遍历 */
void InOrderTraverse(BiTree T)
{
	Stack stk;
	stk.top = 0;
	
	while(T || stk.top)
	{
		if(T)
		{
			stk.s[++ stk.top] = T; /* 入栈,递归左节点  */ 
			T = T->lchild;
		}else
		{
			T = stk.s[stk.top --]; /* 出栈,回溯,打印根节点 */ 
			fprintf(stdout,"%c",T->data);
			fprintf(fp,"%c",T->data);
			T = T->rchild;		   /* 递归右节点 */ 
		}
	}
}

/* 后序递归遍历 */
void PostOrderTraverseRec(BiTree T)
{
	if(T == NULL)
		return;
	PostOrderTraverseRec(T->lchild); /* 先后序遍历左子树  */
	PostOrderTraverseRec(T->rchild); /* 再后序遍历右子树  */
	fprintf(stdout,"%c",T->data);/* 显示结点数据,可以更改为其它对结点操作 */
	fprintf(fp,"%c",T->data);
}

/* 后序非递归遍历 */
void PostOrderTraverse(BiTree T)
{
	Stack stk;
	stk.top = 0;

	BiTree right = NULL; /*上一次遍历到的右节点 */ 
	while(T || stk.top)
	{
		if(T)
		{
			stk.s[++ stk.top] = T; /* 不断向左递归 */ 
			T = T->lchild;
		}else
		{
			T = stk.s[stk.top];
			
			if(T->rchild && T->rchild != right) /* 向右 */ 
			{
				T = T->rchild;
			}else /* 左右都不行的话,回溯,打印当前结点 */ 
			{
				stk.top --;
				fprintf(stdout,"%c",T->data);
				fprintf(fp,"%c",T->data);
				right = T;
				T = NULL;/* 防止死循环 */ 
			}
		}
	}
}

/* 层序遍历 */ 
void SeqTraverse(BiTree T)
{
	Queue qu;
	qu.head = 1;
	qu.tail = 0;
	qu.q[++ qu.tail] = T;
	
	while(qu.head <= qu.tail) /* 打印队头的元素,再将队头的元素的左右儿子入队 */ 
	{
		int lenth = qu.tail - qu.head + 1;
		while(lenth --)
		{
			BiTree root = qu.q[qu.head ++];
			fprintf(stdout,"%c",root->data);
			fprintf(fp,"%c",root->data);
			if (root->lchild)
				qu.q[++ qu.tail] = root->lchild;
			if(root->rchild)
				qu.q[++ qu.tail] = root->rchild;
		}
	}
}

/* 宽度计算 */ 
int CalcuWidth(BiTree T)
{
	if(T == NULL) return 0;
	Queue qu;
	qu.head = 1;
	qu.tail = 0;
	qu.q[++ qu.tail] = T;
	int res = 0; 
	
	while(qu.head <= qu.tail) 
	{
		int lenth = qu.tail - qu.head + 1;//一行一行的取宽度最大值
		if(res < lenth) res = lenth;
		while(lenth --)
		{
			BiTree root = qu.q[qu.head ++];
			if (root->lchild)
				qu.q[++ qu.tail] = root->lchild;
			if(root->rchild)
				qu.q[++ qu.tail] = root->rchild;
		}
	}
	return res;
}
/* 判断是否为完全二叉树 */ 
Status JudgeCBT(BiTree T)
{
	if(T == NULL)
	{
		return TRUE;
	}
	
	Queue qu;
	qu.head = 1;
	qu.tail = 0;
	qu.q[++ qu.tail] = T;
	
	while(qu.head <= qu.tail) /* 打印队头的元素,再将队头的元素的左右儿子入队 */ 
	{
		BiTree root = qu.q[qu.head ++];
		
		if(root == NULL)
		{
			break;
		}

		qu.q[++ qu.tail] = root->lchild;
			
		qu.q[++ qu.tail] = root->rchild;
	}
	
	while(qu.head <= qu.tail)
	{
		if(qu.q[qu.head ++] != NULL)
		{
			return FALSE;
		}
	}
	return TRUE;
}


参考文献

程杰. 大话数据结构:溢彩加强版[M]. 北京: 清华大学出版社, 2020.

有关数据结构:二叉树的更多相关文章

  1. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - Ruby 有 `Pair` 数据类型吗? - 2

    有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳

  4. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  5. ruby - 我如何添加二进制数据来遏制 POST - 2

    我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_

  6. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  7. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

  8. 使用canal同步MySQL数据到ES - 2

    文章目录一、概述简介原理模块二、配置Mysql使用版本环境要求1.操作系统2.mysql要求三、配置canal-server离线下载在线下载上传解压修改配置单机配置集群配置分库分表配置1.修改全局配置2.实例配置垂直分库水平分库3.修改group-instance.xml4.启动监听四、配置canal-adapter1修改启动配置2配置映射文件3启动ES数据同步查询所有订阅同步数据同步开关启动4.验证五、配置canal-admin一、概述简介canal是Alibaba旗下的一款开源项目,Java开发。基于数据库增量日志解析,提供增量数据订阅&消费。Git地址:https://github.co

  9. ruby-on-rails - 创建 ruby​​ 数据库时惰性符号绑定(bind)失败 - 2

    我正在尝试在Rails上安装ruby​​,到目前为止一切都已安装,但是当我尝试使用rakedb:create创建数据库时,我收到一个奇怪的错误:dyld:lazysymbolbindingfailed:Symbolnotfound:_mysql_get_client_infoReferencedfrom:/Library/Ruby/Gems/1.8/gems/mysql2-0.3.11/lib/mysql2/mysql2.bundleExpectedin:flatnamespacedyld:Symbolnotfound:_mysql_get_client_infoReferencedf

  10. STM32读取串口传感器数据(颗粒物传感器,主动上传) - 2

    文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,

随机推荐