1.树的定义:
树是一种非线性的数据结构, 是n(n>=0)个有限个数据的元素集合,形状像一颗倒过来的树。如下图所示:
2、树的基本术语
(1)
结点:树中每一个小圆圈都代表树的一个节点(A,B,C等都是节点),节点不仅包含了数据元素,而且包含指向子树的分支。例如,A节点既包含数据元素A,又包含2个指向子树的指针。
(2)
树的度:树中各节点度的最大值。
(3)根结点:树的第一个结点(例如:A结点)。
(4)结点的度:结点拥有的子节点个数。
(5)叶结点:没有子结点的结点(度为0)。
(6)双亲:与孩子的定义相对(例如A、B、I中A为双亲)。
(7)兄弟:同一个双亲的孩子之间互为兄弟(例如B和I,C和H等)。
(8)祖先:从根到某节点的路径上的所有节点,都是这个节点的祖先(例如G的祖先结点是C、B、A)。
(9)子孙:以某节点为根的子树中的所有节点,都是该节点的子孙(例如G是C、B、A的子孙结点)。
(10)孩子:节点的子树的根(例如A、B、I中B和I为孩子) 。
(11)层次:从根开始,根为第一层,根的孩子为第二层,根的孩子的孩子为第三层,以此向下类推。
(12)堂兄弟:双亲在同一层的节点互为堂兄弟(例如L和N、H和J)。
(13)有序树:树中节点的子树从左到右是有次序的,不能随意交换,这样的树称作有序树。
(14)无序树:树中节点的子树没有次序,可以任意交换,这样的树称作无序树。
(15)森林:若干棵互不相交的树的集合。如去掉A节点之后剩下的2棵子树可构成一个森林。
(16)丰满树:丰满树即理想平衡树,要求除最底层外,其他层都是满的。
(17)树的高度:也叫做树的深度,树中节点的最大层次。
(18)非终端结点:又叫做分支节点,度不为0的节点。非终端节点中根节点之外的其他节点又称作内部节点。
下面给出几幅来自网上的树图来加深理解
3、树的存储结构
说到树的存储结构我们就会想到顺序存储和链式存储两种。
先来说一下顺序结构,顺序结构就是用一段地址连续的存储单元依次存储线性表中数据元素,这对于线性表来说是很自然的,但是对于树这种一对多的结构而言是否适合呢?下面我们对于树的不同表示法给出对应的分析如下:
树中每个结点的孩子可以有多个,这就意味着,无论用哪种顺序将树中所有的结点存储到数组中,结点的存储位置都无法直接反映逻辑关系,试想一下,数据元素挨个存储,那么谁是谁的双亲,谁是谁的孩子呢?所以简单的顺序存储是不能满足树的实现要求的。
不过通过充分利用顺序存储和链式存储结构的特点,完全可以实现对树的存储结构的表示。
下面介绍三种不同的树的表示法:双亲表示法,、孩子表示法,、
孩子兄弟表示法。
1)双亲表示法
我们假设以一组连续空间存储树的结点,同时在每个结点中,附设一个指示器指向其双亲结点到链表中的位置。也就是说每个结点除了知道自己之外还需要知道它的双亲在哪里。
他的存储结构如下所示:
以下是双亲表示法
定义的代码
/*树的双亲表示法结点结构定义 */
#define MAXSIZE 100
typedef int ElemType; //树结点的数据类型,暂定为整形
typedef struct PTNode //结点结构
{
ElemType data; //结点数据
int parent; //双亲位置
}PTNode;
typedef struct
{
PTNode nodes[MAXSIZE]; //结点数组
int r, n; //根的位置和结点数
}PTree;
下面创建一个树,双亲表示法进行存储举例如下所示:
2)孩子表示法
换一种完全不同的考虑方法。由于树中每个结点可能有多棵子树,可以考虑用多重链表,即每个结点有多个指针域,其中每个指针指向一棵子树的根结点,我们把这种方法叫做多重链表表示法。不过,树的每个结点的度,也就是它的孩子个数是不同的。所以可以设计两种方案来解决。
方案A:
第一种是指针域的个数就等于树的度(树的度是树的各个结点度的最大值)
其结构如图所示:
其中data是数据域。child1到childn是指针域,用来指向该结点的孩子结点
不过这种结构由于每个结点的孩子数目不同,当差异较大时,很多结点的指针域就都为空,显然是浪费空间的,不过若树的各结点度相差很小时,那就意味着开辟的空间都被利用了,这时这种缺点反而变成了优点。
方案B:
第二种方案每个结点指针域的个数等于该结点的度,我们专门取一个位置来存储结点指针域的个数,其结构如下所示;
其中data是数据域,degree表示度,child1到childn是指针域,用来指向该结点的孩子结点
这种方法克服了浪费空间的缺点,对空间的利用率是很高了,但是由于各个结点的链表是不相同的结构,加上要维护结点的度的数值,在运算上就会带来时间上的损耗。
能否有更好的方法呢,既可以减少空指针的浪费,又能是结点结构相同。
说到这大家肯定就知道是有的麦,那就是孩子表示法。
具体办法是,把每个结点的孩子排列起来,以单链表做存储结构,则n个结点有n个孩子链表,如果是叶子结点则此单链表为空。然后n个头指针有组成一个线性表,采用顺序存储结构,存放进入一个一维数组中。
为此,设计两种结点结构,
一个是孩子链表的孩子结点,如下所示:
其中data是数据域,用来存储某个结点在表头数组中的下标。parent是指针域,用来存储指向某结点的下一个孩子结点的指针。
另一个是表头结点,如下所示:
其中data是数据域,存储某结点的数据信息。firstchild是头指针域,存储该结点的孩子链表的头指针。
那么能否有更好的方法呢,既可以减少空指针的浪费,又能是结点结构相同。
以下是孩子表示法定义的代码:
/*树的孩子表示法结点结构定义 */
#define MAXSIZE 100
typedef int ElemType; //树结点的数据类型,暂定为整形
typedef struct CTNode //孩子结点
{
int child;
struct CTNode *next;
}*ChildPtr;
typedef struct //表头结构
{
ElemType data;
ChildPtr firstchild;
}CTBox;
typedef struct //树结构
{
CTBox nodes[MAXSIZE]; //结点数组
int r, n; //根结点的位置和结点数
}CTree;
下面创建一个树,孩子表示法进行存储举例如下所示:
3、孩子兄弟表示法
我们发现,任意一颗树,它的结点的第一个孩子如果存在就是的,它的右兄弟如果存在也是唯一的。因此,我们设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟。
其结点结构如图所示:
以下是孩子兄弟表示法定义的代码:
/*树的孩子兄弟表示法结构定义 */
typedef struct CSNode
{
ElemType data;
struct CSNode *firstchild, *rightsib;
}CSNode, *CSTree;
下面创建一个树,孩子兄弟表示法进行存储举例如下所示: