目录
树的定义
先了解一下树型结构(非线性结构),它的结点之间有分支;结构具有层次关系;元素之间的关系是一对多。
定义:树(Tree)是n(n>=0)个结点的有限集。
若n=0,则为空树;
若n>0,则满足如下两个条件:
(1)有且仅有一个特定的称为根(Root)的结点;
(2)其余结点可分为m(m>=0)个互不相交的有限集T1,T2,……,其中每个集合本身又是一棵树,并称为根的子树(SubTree)。
其中子树又可以看作有一个根结点,其下又有有限个互不相交的有限集,即子树的子树。
从中可以看出,树的定义是一个递归的定义。
树的其它表示方法
- 嵌套集合
- 凹入表示
- 广义表 (A(B(E(K,L),F),C(G),D(H(M),I,J)))
树的基本术语
根结点:非空树中无前驱结点的结点 (如图,结点A为根结点)
结点的度:结点拥有的子树个数 (如图,A结点的度为3;B的度为2)
树的度:树内各结点的度的最大值(如图,该树中A与D的结点度数最大为3,所以此树的度为3)
叶子结点(终端结点):度为0,即无后继元素(如图,叶子结点为K,L,F,G,M,I,J)
分支结点(非终端结点):根结点以外的分支结点称为内部结点
在树中,结点之间前驱与后继的关系分别用双亲和孩子来表示:结点的子树的根称为该结点的孩子,该结点称为孩子的双亲。(如图,A的孩子为B,C,D;B,C,D的双亲为A)
兄弟结点:拥有共同双亲的结点(如图,E,F为兄弟结点,其拥有共同的双亲B)
堂兄弟结点:双亲在同一层的结点(如图,F,G,H为堂兄弟结点)
结点的祖先:从根到该结点所经分支上的所有结点(如图,H的祖先为A,D)
结点的子孙:以某结点为根的子树中的任一结点(如图,D的子孙为H,I,J,M)
树的深度:树中结点的最大层次(如图,该树有4层)
有序树:树中结点的各子树从左至右有次序(最左边的为第一个孩子)
无序树:树中结点的各子树无次序,即子树无论怎么排列,这棵树都不会变
森林:是m(m>=0)棵互不相交的树的集合
一棵树可以看成是一个特殊的森林;给森林中的各子树加上一个双亲结点,森林就变成了树
所以,树一定是森林,但森林不一定是树。
树的存储结构
双亲表示法
除了存储数据元素本身之外,还要存储其双亲
实现:定义结构数组存放树的结点,每个结点含两个域:
数据域:存储结点本身的信息
双亲域:指示本结点的双亲结点在数组中的位置
如:
数组下标 | data | parent |
0 | A | -1 |
1 | S | 0 |
2 | D | 0 |
3 | F | 0 |
4 | G | 1 |
5 | H | 1 |
6 | J | 3 |
7 | K | 6 |
8 | L | 6 |
9 | B | 6 |
其中A没有对应的双亲结点,所以A元素在根结点的位置;S、D、F的双亲为数组下标为0的元素,即结点A为S、D、F的双亲结点;G、H的双亲结点为位于数组下标为1的结点,即结点S……这样依次可以找出其它剩余结点的双亲结点。
为了处理方便,我们还存储两个值,一个是根结点对应的下标值,另一个是该树的结点个数。
对应上面表格,即r=0;n=10
双亲表示法的特点:找双亲容易,找孩子难。如果要找双亲结点,可用此存储结构。
C语言的类型描述:
存储树中的每个结点:
结点结构
data(结点本身信息) | parent(双亲所在下标) |
typedef struct Node{
TElemType data; //存储的元素类型取决于实际要存储的元素类型
int parent; //每个结点双亲的下标
}TNode;
树的结构:可定义一个数组,将树中的结点存放在数组中
#define maxsize 100 //定义树中结点数(可修改)
typedef struct{
TNode nodes[maxsize]; /*树中的每个元素都是结构类型(TNode类型),即既有数据本
身,又有双亲下标*/
int r,n; //根结点的位置和结点个数
}Tree;
孩子链表
把每个结点的孩子结点排列起来,看成是一个线性表,用单链表存储。
则n个结点有n个孩子链表(叶子结点的孩子链表为空表)。
而n个头指针又组成一个线性表,用顺序表(含n个元素的结构数组)存储。
如:
C语言的类型描述:
孩子结点结构:
child(孩子结点下标的位置) | next(指向下一个孩子的指针) |
typedef struct TNode{
int child; //孩子结点本身的下标位置
struct TNode *next; /*一个指针,指向struct TNode 类型(其中包含下一个孩子结点的
下标以及指向下下一个孩子结点地址的指针)*/
}*ChildPtr;
双亲结点结构:
data(存储数据元素本身) | firstchild(指向第一个孩子的指针) |
typedef struct{
TElemType data; //存储结点本身信息
ChildPtr firstchild; //孩子链表头指针
}CTBox;
树结构:
typedef struct {
CTBox nodes[maxsize];
int n,r; //结点数和根结点的位置
}CTree;
孩子链表的特点:找孩子容易,找双亲难
带双亲的孩子链表
这种存储方式,将双亲表示法和孩子链表结合起来:在结构数组中再增加一个成员,这个成员就是双亲结点的下标
孩子兄弟表示法
孩子兄弟表示法又叫:二叉树表示法,二叉链表表示法
实现:用二叉链表作树的存储结构,链表中每个结点的两个指针域分别指向其第一个孩子结点和下一个兄弟结点
typedef struct CNode{
Element data; //用来存储数据
struct CNode *firstchild,*nextbrother;/*两个指针,指向存储有一个数据、两个指针
的结构类型,即struct CNode类型*/
}CNode,*CTree; //定义了一个结点类型,以及指向struct CNode类型的指针
在这个二叉链表中,从根结点出发,若想找它的孩子,从它的第一个指针域出发,就可以找到它的第一个孩子;再顺着第二个指针域,就可以找到它的孩子的兄弟结点,即它的第二个孩子。
所以找一个结点的孩子结点,就可以先找到它的第一个孩子,然后顺着它孩子的右指针一直找到它的所有孩子结点
这种存储方法找双亲比较困难,可以再增加一个指针域,用来指向此结点的双亲结点。
树与二叉树的转换
将树转换成二叉树
树的操作比较复杂,所以通常需要将树转变为二叉树进行操作(二叉树是所有树里最简单的)。
将树转化为二叉树进行处理,利用二叉树的算法对树进行操作。
由于树和二叉树都可以用二叉链表作存储结构,则以二叉链表为媒介可以导出树与二叉树之间的一个对应关系。
方法:
- 加线:在兄弟之间加一根线
- 抹线:对每个结点,除了左孩子之外,抹去与其余孩子之间的关系
- 旋转:以树的根结点为轴心,将整棵树顺时针选择45度
口诀:兄弟相连留长子
将二叉树转换为树
方法:
- 加线:若p结点是双亲结点的左孩子,则将p结点的右孩子,右孩子的右孩子……沿分支找到所有的右孩子,都与p的双亲连线
- 抹线:抹掉原来双亲与右孩子之间的连线
- 调整:将结点按层次排列,形成树的结构
口诀:左孩右右连双亲,去掉原来右孩线
森林转换为二叉树
- 将各棵树分别转换成二叉树
- 将每棵树的根结点相连
- 以第一棵树的根结点为二叉树的根结点,再以根结点为轴心,顺时针旋转,构成二叉树
口诀:树变二叉根相连
二叉树转换成森林
- 抹线:将二叉树中根结点与其右孩子连线,及沿右分支搜索到的所有右孩子间连线全部抹掉,使之变成孤立的二叉树
- 还原:将孤立的二叉树还原成树
口诀:去掉全部右孩线,孤立二叉再还原
树与森林的遍历
树的遍历(三种方式)
- 先根(次序)遍历:若树不空,则先访问根结点,然后依次先根遍历各棵子树。
- 后根(次序)遍历:若树不空,则先依次后根遍历各棵子树,然后访问根结点。
- 层次遍历:若树不空,则自上而下自左至右访问树中每个结点。
例:
先根遍历:ABEFCDGHIJ
后根遍历:EFBCHIJGDA
层次遍历:ABCDEFGHJ
森林的遍历
- 先序遍历:
若森林不空,则:
- 访问森林中第一棵树的根结点;
- 先序遍历森林中第一棵树的子树森林;
- 先序遍历森林中剩余树构成的森林;
即依次从左至右对森林中的每一棵树进行先根遍历。这是一个递归的思想。
- 中序遍历:
若森林不空,则:
- 中序遍历森林中的第一棵树的子树森林;
- 访问森林中第一棵树的根结点;
- 中序遍历森林中剩余树构成的森林;
即依次从左往右对森林中的每棵树进行后根遍历。
例:
先序遍历:ABCDEFHK
中序遍历:BCDAFKHE
注意:树无中序遍历。
欢迎大家指正。