您的位置:首页 > 理论基础 > 数据结构算法

树[数据结构]

2016-01-29 20:15 519 查看

一.树的定义

1.什么是树

有且只有一个根节点,有若干个互不相交的子树。



2.专业术语



如上图:我们解释父节点、子节点、兄弟节点、堂兄弟节点。

父节点:A为父节点

子节点:B、C是A的子节点

兄弟节点:B和C是兄弟节点

堂兄弟节点:D与E是堂兄弟节点

深度:树中节点的最大层次(从根节点到最底层节点的层数,图中深度为3)

叶子节点:没有子节点的节点(B、E是叶子节点)

度:子节点的个数

二.树的分类

1.一般树

一般树:任意一个节点的子节点个数都不受限制。

2.二叉树

二叉树:任意一个节点的子节点个数最多为2个,且子节点位置不可更改。

3.森林

森林:n个互不相交的树的集合。

扩展:二叉树的分类

1.一般二叉树

2.满二叉树:在不增加树层次的前提下,不可添加新节点

3.完全二叉树:将满二叉树最底层最右边的连续若干个节点

如图,是一个满二叉树:



变成完全二叉树,可以不删除节点(完全二叉树包含满二叉树),也可以删除 G、GE、GEF、GEFD.

三.树的存储

1.连续存储:

连续存储必须是完全二叉树的形式

为什么呢?

如下图,一颗普通树:



假设我们使用层次的关系(从左到右,从上到下的关系来存储)

该树在数组中的存储结构如下:



我们根据此数组将树还原:

到底是这种形式呢?



还是这种形式呢?



有歧义,不能确定节点的逻辑关系。

也就是,如果一棵树不是完全二叉树的形式,我们不能根据连续的存储结构来还原一棵树。

所以,连续的存储结构必须是完全二叉树的形式。

我们可以如下存储:



至此,我们总结一下连续结构存储完全二叉树的优缺点:

优点:

1.查找某个节点的父节点与子节点很快

2.可以通过编号确定节点在第几层

缺点:

浪费空间(如上面存储,还得存储空节点)

2.链式存储

我们将树的节点分为3部分构成:

pLeft指向左子树、pRight指向右子树、data存放节点数据。

如下图:



我们可以使用 先序遍历、中序遍历、后序遍历 遍历这棵树。

链式存储相对于连续存储来说,更节省空间,所以使用范围广。

3.非链式存储

对于下面树的存储:



1.双亲表示法



A:-1(代表A是根节点)

B:0(代表B的父节点是A)

C:0(代表C的父节点是A)

D:1(代表D的父节点是B)

E:1(代表E的父节点是B)

特点:求父节点很方便,但是求子节点麻烦。

2.孩子表示法



特点:求子节点很方便,但是求父节点麻烦。

3.双亲-孩子表示法



特点:求子节点很方便,求父节点也很方便,但是浪费空间。

4.二叉树表示法

对于一般树的存储,我们可以使用双亲表示法、孩子表示法、双亲-孩子表示法。

我们也可以将一棵树转换成二叉树来存储。

因为现在,我们对二叉树的算法比较成熟,对普通树的算法不成熟。所以我们通常将普通树转换成二叉树来存储,便于以后的操作。

我们将一颗普通树转变为二叉树来存储的规则是:左指针域指向第1个孩子,右指针域指向兄弟。



总结:

连续存储结构:浪费空间(必须是完全二叉树),但是查找速度快

链式存储结构:节约空间,查找速度还可以

非链式存储结构:最浪费空间

我们最常用的存储结构是:链式二叉树

四.树的操作

1.遍历



1.1前序遍历

前序遍历的顺序是:根节点-左子树-右子树

上面图片树的前序遍历为:A-B-D-E-C-F

1.2中序遍历

中序遍历的顺序是:左子树-根节点-右子树

上面图片树的前序遍历为:D-B-E-A-F-C

1.3后序遍历

后序遍历的顺序是:左子树-右子树-根节点

上面图片树的前序遍历为:D-E-B-F-C-A

2.根据遍历结果还原一棵树

我们可以根据:

先序遍历+中序遍历—>还原一棵树

中序遍历+后序遍历—>还原一棵树

但是不能:

前序遍历+后序遍历—>还原一棵树

因为:

前序遍历可以确定根节点,但是不能确定左子树、右子树

后序遍历可以确定根节点,但是不能确定左子树、右子树

中序遍历可以确定左子树、右子树,但是不能确定根节点

所以:

先序遍历或后序遍历只有和中序遍历结合,才能确定一棵树的根节点、左子树、右子树。

我们在根据 先序+中序—>还原一棵树 或 中序+后序—>还原一棵树 的时候,有如下规律:

先序:最先出现的是根节点

后序:最后出现的是根节点

我们根据先序和后序的规律,再结合中序,就能轻松的还原一棵树。

具体的练习就不写了,其实并不难。

五.树的应用

1.是数据库中数据组织的一种重要方式(如图书管理数据库)

2.操作系统子父进程的关系就是一棵树。

3.面向对象中类的继承关系

等等…

六.编码实战

我们创建下面图中的这棵树,并且实现它的前序遍历,中序遍历、后序遍历。



#include <stdio.h>
#include <stdlib.h>
struct BTNode
{
char data;
struct BTNode * PTLchild;
struct BTNode * PTRchild;
};
struct BTNode *createBTree();
void preTraverseBTree();
void inTraverseBTree();
void postTraverseBTree();
int main()
{
struct BTNode * pT=createBTree();
preTraverseBTree(pT);
inTraverseBTree(pT);
postTraverseBTree(pT);
return 0;
}
//创建一棵树
struct BTNode *createBTree()
{
struct BTNode * pA=(struct BTNode *)malloc(sizeof(struct BTNode));
struct BTNode * pB=(struct BTNode *)malloc(sizeof(struct BTNode));
struct BTNode * pC=(struct BTNode *)malloc(sizeof(struct BTNode));
struct BTNode * pD=(struct BTNode *)malloc(sizeof(struct BTNode));
struct BTNode * pE=(struct BTNode *)malloc(sizeof(struct BTNode));
pA->data='A';
pB->data='B';
pC->data='C';
pD->data='D';
pE->data='E';
pA->PTLchild=pB;
pA->PTRchild=pC;
pB->PTLchild=NULL;
pB->PTRchild=NULL;
pC->PTLchild=pD;
pC->PTRchild=NULL;
pD->PTLchild=NULL;
pD->PTRchild=pE;
pE->PTLchild=NULL;
pE->PTRchild=NULL;
return pA;
}
//先序遍历
void preTraverseBTree(struct BTNode * pT)
{
if(pT!=NULL)
{
printf("%c\n",pT->data);
if(pT->PTLchild!=NULL)
{
preTraverseBTree(pT->PTLchild);
}
if(pT->PTRchild!=NULL)
{
preTraverseBTree(pT->PTRchild);
}
}
}
//中序遍历
void inTraverseBTree(struct BTNode * pT)
{
if(pT!=NULL)
{
if(pT->PTLchild!=NULL)
{
inTraverseBTree(pT->PTLchild);
}
printf("%c\n",pT->data);
if(pT->PTRchild!=NULL)
{
inTraverseBTree(pT->PTRchild);
}
}

}
//后序遍历
void postTraverseBTree(struct BTNode * pT)
{
if(pT!=NULL)
{
if(pT->PTLchild!=NULL)
{
postTraverseBTree(pT->PTLchild);
}
if(pT->PTRchild!=NULL)
{
postTraverseBTree(pT->PTRchild);
}
printf("%c\n",pT->data);
}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c语言 数据结构