嵌入式全栈开发学习笔记---数据结构(二叉树)

目录

树形结构的概念

二叉树的概念

二叉树的特点

特殊的二叉树

斜树(右斜树和左斜树)

满二叉树

完全二叉树

完全二叉树的特点

二叉树的性质

性质1

性质2

性质3

性质4

性质5

二叉树创建

Tree.c

二叉树的遍历(笔试重点)

先序遍历(根左右)

中序遍历(左根右)

后序遍历(左右根)

完整代码

tree.c

二叉树的笔试题

二叉搜索树

bstree.c


截止上一篇博客我们已经学完了线性结构,接下来从本节开始学习树形结构!

树形结构在嵌入式开发中应用的不是很多,因为嵌入式开发中数据量不会特别的大,所以用到树形结构的地方不多,但是在笔试中出现的概率比较高。

树形结构的概念

树(Tree)是n(n>=0)个结点的有限集。

n=0的时候称为空树,在任意一颗非空树中:

(1)有且仅有一个特定的称为根(root)的结点(如下图中的A结点)

(2)当n>1时,其余结点可以分为m(m>0)个互不相交的有限集T1、T2、T3.....Tm,其中,每一个集合本身又是一棵树,并且称为根的子树。

结点树的结点包含一个数据元素及若干个指向其子树的分支(类似链表中的结点)

结点拥有的子树称为结点的度(即分支的个数,比如A的度是两个,即分支有两个)

叶结点度为0的结点称为叶结点或者终端结点(比如上图中的EFGHIJ)

分支结点度不为0的结点称为非终端结点或者分支结点(比如上图中的ABCD)

孩子、双亲结点的子树的跟称为该结点的孩子,相应的,该结点称为孩子的双亲(比如上图中B是A的孩子,A就是B的双亲)

深度树中结点最大层次称为树的深度(可以理解为树的层数,比如上图中的树,深度是3)

我们用递归来实现这种思想,但是代码写起来比较麻烦,因此树形结构我们一般使用二叉树。

二叉树的概念

二叉树是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两颗互不相交的、分别称为根结点的左子树和右子树的二叉树组成。

二叉树的特点

1、每个结点最多有两棵子树,所以二叉树中不存在度大于2的结点(即一个结点最多只能有两个分支)

2、左子树和右子树是有顺序的,次序不能任意颠倒(如上图中B是A的左子树,C是A的右子树,B和C的顺序不能调换)。

3、即使树中某结点只有一棵子树,也要区分它是左子树还是右子树。

特殊的二叉树

斜树(右斜树和左斜树)

只有一个分支,类似链表

满二叉树

在一棵二叉树中,如果所有的分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树。

完全二叉树

对一个具有n个结点的二叉树按层编号,如果编号为i的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树。

完全二叉树的特点

叶子结点只能出现在最下两层(即倒数两层)

最下层的叶子一定集中在左部连续位置

倒数第二层,若有叶子结点,一定都在右部连续位置

如果结点度为1,则该结点只有左孩子,

同样结点树的二叉树,完全二叉树的深度最小

二叉树的性质

性质1

在二叉树的第i层上的结点的个数至多有

  

性质2

深度为k的二叉树的结点的个数至多有 

性质3

对任何一棵二叉树T,如果其终端结点数为n,度为2的结点数为m,则 n = m + 1

性质4

具有n个结点的完全二叉树的深度为

性质5

如果对1棵有n个结点的二叉树的结点按层序编号,对任一结点i:

如果i=1,则结点i是二叉树的根,无双亲,如果i>1,则其双亲是结点(i/2)取整;

如果2i>n,则结点i无左孩子,否则,其左孩子是2i

如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1。

               

以上这些性质在写代码的时候要用到,所以务必熟悉。

下面开始写代码

创建一个tree目录

二叉树创建

创建10个结点的二叉树

Tree.c

#include <stdio.h>
#include <stdlib.h>//malloc的头文件


//定义节点
typedef struct Node
{
	int data;//数据
	struct Node*left;//存放左边节点的地址
	struct Node*right;//存放右边节点的地址
}Node;

//创建树的函数
Node *CreateTree(int *a,int size)
{
	if(NULL==a)
		return NULL;

	//将10个元素的地址记录在一个指针数组里面
	Node *arr[11]={0};
	int i;
	for(i=0;i<size;i++)
	{
		//申请10个节点的空间,把地址放到指针数组里面
		arr[i]=(Node*)malloc(sizeof(Node)*1);
		if(NULL==arr[i])
		{
			printf("malloc failure\n");
			return NULL;//返回空
		}

		//初始化10个节点
		arr[i]->data=a[i];//把获取的数据放到节点的数据域里面
		arr[i]->left=NULL;
		arr[i]->right=NULL;
	}
	
	//把各个分支节点和叶节点的地址放到各自的双亲节点里面
	for(i=0;i<size/2;i++)
	{
		arr[i]->left=arr[2*i+1];
		arr[i]->right=arr[2*i+2];
	}
	return arr[0];//把根节点的地址返回

}

int main()
{
	//获取10个元素
	int num[10]={0};
	int i;
	for(i=0;i<10;i++)
	{
		scanf("%d",&num[i]);
	}
	
	//将这10个元素形成二叉树,创建好之后返回根节点的地址,之后要遍历二叉树就可以根据根节点的地址去遍历
	Node*root=CreateTree(num,10);


	return 0;
}

二叉树的遍历(笔试重点)

二叉树的遍历有常见的三种算法

先序遍历;

中序遍历;

后序遍历。

先序遍历(根左右)

规则是若二叉树为空, 则空操作返回,否则先访问根结点,然后先序遍历左子树,再先序遍历左子树,再先序遍历右子树,如图:ABDGHCEIF

代码:

运行效果

这就是用递归来实现的结果,但是注意,一般用递归的地方效率都不是很高,能不用递归就不用递归,而且递归一定要有返回的条件,上面的入参判断就是本次递归的返回条件。

中序遍历(左根右)

规则是若树为空,则空操作返回,否则从根结点开始(注意并不是访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树,如图:GDHBAEICF

代码:

运行结果

后序遍历(左右根)

规则是若树为空,则空操作返回,否则从左到右,先叶子后结点的方式遍历访问左右子树,最后是访问根结点,如图:GHDBIEFCA

代码:

运行结果

完整代码

tree.c

#include <stdio.h>
#include <stdlib.h>//malloc的头文件


//定义节点
typedef struct Node
{
	int data;//数据
	struct Node*left;//存放左边节点的地址
	struct Node*right;//存放右边节点的地址
}Node;

//创建树的函数
Node *CreateTree(int *a,int size)
{
	if(NULL==a)
		return NULL;

	//将10个元素的地址记录在一个指针数组里面
	Node *arr[11]={0};
	int i;
	for(i=0;i<size;i++)
	{
		//申请10个节点的空间,把地址放到指针数组里面
		arr[i]=(Node*)malloc(sizeof(Node)*1);
		if(NULL==arr[i])
		{
			printf("malloc failure\n");
			return NULL;//返回空
		}

		//初始化10个节点
		arr[i]->data=a[i];//把获取的数据放到节点的数据域里面
		arr[i]->left=NULL;
		arr[i]->right=NULL;
	}
	
	//把各个分支节点和也节点的地址放到各自的双亲节点里面
	for(i=0;i<size/2;i++)
	{
		arr[i]->left=arr[2*i+1];
		arr[i]->right=arr[2*i+2];
	}
	return arr[0];//把根节点的地址返回

}

//先序遍历
void PreOrderTree(Node *root)
{
	if(NULL==root)
		return;

	//直接打印根节点元素
	printf("%d ", root->data);
	PreOrderTree(root->left);//把左子树作为根,调用函数自己
	PreOrderTree(root->right);//把右子树作为根
}

//中序遍历
void MidOrderTree(Node*root)
{
	if(NULL==root)
		return;

	MidOrderTree(root->left);//把左子树作为根,调用函数自己
	printf("%d ", root->data);
	MidOrderTree(root->right);//把右子树作为根	
}

//后续遍历
void PostOrderTree(Node *root)
{
	if(NULL==root)
		return;

	PostOrderTree(root->left);//把左子树作为根,调用函数自己
	PostOrderTree(root->right);//把右子树作为根		
	printf("%d ", root->data);
}


int main()
{
	//获取10个元素
	int num[10]={0};
	int i;
	for(i=0;i<10;i++)
	{
		scanf("%d",&num[i]);
	}
	
	//将这10个元素形成二叉树,创建好之后返回根节点的地址,之后要遍历二叉树就可以根据根节点的地址去遍历
	Node *root=CreateTree(num,10);

	//先序遍历
	PreOrderTree(root);
	printf("\n");

	//中序遍历
	MidOrderTree(root);
	printf("\n");
	
	//后序遍历
	PostOrderTree(root);
	printf("\n");

	return 0;
}

二叉树的笔试题

已知:

先序遍历:adcefghb

中序遍历:cdfeghab

求后续遍历?

提示:先序遍历(根左右)的结果一定是根节点在最前面,中序遍历(左根右)的结果一定是根在中间。

比如我们刚刚遍历出来的结果中先序遍历0作为根节点被第一个打印出来,而后序遍历的结果中,0夹在了中间,0的左边全是左子树,0的右边全是右子树。

我们就可以根据这两个特点把二叉树画出来

所以后序遍历(左右根)的结果是:cfhgedba

解题过程可以看这位博主的解答:

已知二叉树前序遍历是ADCEFGHB,中序遍历是CDFEGHAB,要求画出二叉树的结构或写出后序遍历_已知二叉树前序遍历结果是acdefbgh-CSDN博客

二叉搜索树

什么是二叉搜索树

比如这样一串数字:6 7 2 0 1 3 4 9 5 8

以数字6作为树的根,之后小于6的放在左边,大于6的放在右边(也可以小的放右边,大的放左边,主要取决于最终的结果是想要倒序排列还是顺序排列)。

按照这个规则我们初步得到这样的结果

接下来0比6小放右边,而0又比2小,最终放在2的右边

然后1比6小,放6的右边,又比2小,放2的右边,又比0大,则最终放在0的左边

依次类推,最终得到这样一颗树就叫二叉搜索树

如果我们对这样一颗二叉搜索树做一个中序遍历,我们会得到这样一个结果:0 1 2 3 4 5 6 7 8 9

所以我们对一个二叉搜索树进行中序遍历的话,就能得到一个有序的序列,因此我们就把这种树状结构叫做二叉搜索树

我们用代码实现一些,核心思想还是递归

注:bstree(binary search tree)

bstree.c

#include <stdio.h>
#include <stdlib.h>

//定义节点
typedef struct Node
{
	int data;
	struct Node*left;//存放左边的节点地址
	struct Node*right;//存放右边的节点地址
}Node;

Node *CreateBstree(Node*root,int num)
{
	if(NULL==root)//第一次进来的时候是个空指针
	{
		//给根节点申请空间
		root=(Node*)malloc(sizeof(Node));
		//初始化根节点
		root->data=num;
		root->left=NULL;
		root->right=NULL;
	}
	else//如果不是第一次进来
	{
		if(num>root->data)//如果数字比根节点的数大
		{
			root->right=CreateBstree(root->right,num);//把它放根节点的右边
		}
		else
		{
			root->left=CreateBstree(root->left,num);//把它放根节点的左边
		}
	}

	return root;
}


//中序遍历
void MidOrderTree(Node *root)
{
	if(NULL==root)
		return;
	MidOrderTree(root->left);
	printf("%d ",root->data);
	MidOrderTree(root->right);
}


int main()
{
	//获取数字
	int num,i;
	Node *root=NULL;//接收二叉搜索树的根节点的地址

	for(i=0;i<10;i++)//获取10个数字
	{
		scanf("%d",&num);
		//创建二叉搜索树
		root=CreateBstree(root,num);
	}

	//对结果进行中序遍历
	MidOrderTree(root);
	printf("\n");

	return 0;
}

运行结果:

下节开始学习排序!

QQ交流群:963138186

本篇就到这里,下篇继续!欢迎点击下方订阅本专栏↓↓↓

  • 10
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vera工程师养成记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值