目录
截止上一篇博客我们已经学完了线性结构,接下来从本节开始学习树形结构!
树形结构在嵌入式开发中应用的不是很多,因为嵌入式开发中数据量不会特别的大,所以用到树形结构的地方不多,但是在笔试中出现的概率比较高。
树形结构的概念
树(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
本篇就到这里,下篇继续!欢迎点击下方订阅本专栏↓↓↓