树的定义
- 树是用来处理一对多的数据结构
- 树是n(n>=0)个节点的有限集
- n = 0, 叫做空树
- n > 0:
- 根节点是唯一的
- 子树的个数没有限制,但是一定是不相交的
- 这里面每个元素我们叫作“节点”;用来连线相邻节点之间的关系,我们叫作“父子关系”。
树的度
节点拥有的子树数量称为节点的度。 根据各个节点的度的不同,节点可以分为:
- 叶子节点(Leaf) [也叫做终端节点]: 度为0的节点
- 分支节点[也叫做非终端节点]: 度不为0
- 内部节点: 除了根节点之外,分支节点也叫做内部节点
树的度是树内各节点的度的最大值
节点间关系
- 当前节点的子树的根称为该节点的孩子(子节点)
- 当前节点称为其子节点的双亲【因为当前节点只有一个,所以叫做双亲节点,而不是父/母节点】
- 同一个双亲的孩子之间互称为兄弟
- 节点的祖先是从根节点到当前节点所经分支上的所有节点。 比如下图, 相对于H节点,D,B,A都是它的祖先
- 以某节点为根的子树中任一节点都称为该节点的子孙。比如B的子孙有D,G,H,I
比如下面这幅图,A 节点就是 B 节点的父节点,B 节点是 A 节点的子节点。B、C、D 这三个节点的父节点是同一个节点,所以它们之间互称为兄弟节点。我们把没有父节点的节点叫作根节点,也就是图中的节点 E。我们把没有子节点的节点叫作叶子节点或者叶节点,比如图中的 G、H、I、J、K、L 都是叶子节点。
树的层
- 节点的层(level): 从根节点开始,根节点为第一层,跟的孩子为第二层。。。 若某节点为第i层,则其子树的跟就在第i+1层
- 其双亲在同一层的节点互为堂兄弟。 比如下图中的,D,E,F是堂兄弟,G,H,I,J是堂兄弟
- 树中节点的最大层次称为树的深度。比如下图中树的深度为4
高度(Height)、深度(Depth)、层(Level)这三个概念比较难以区分,它们的定义是这样的:
举个例子:
如何记忆:
- 在我们生活中,“高度”这个概念,其实就是从下往上度量,比如我们要度量第 10 层楼的高度、第 13 层楼的高度,起点都是地面。所以,树这种数据结构的高度也是一样,从最底层开始计数,并且计数的起点是0
- “深度”这个概念在生活中是从上往下度量的,比如水中鱼的深度,是从水平面开始度量的。所以,树这种数据结构的深度也是类似的,从根结点开始度量,并且计数起点也是 0。
- “层数”跟深度的计算类似,不过,计数起点是 1,也就是说根节点的位于第 1 层。
树的有序性
如果树中结点的各子树从左向右是有序的,子树间不能互换位置,则称该树为有序树,否则为无序树。
树的表示方法
双亲表示法
取一块连续的内存空间,在层次每个节点的同时,各自都附加一个记录其父节点位置的变量
- 在树结构中,除了树根外,每个结点都只有一个父结点(又叫“双亲结点”)。
#define tree_size 100//宏定义树中结点的最大数量
#define TElemType int//宏定义树结构中数据类型
typedef struct PTNode{ //节点结构
TElemType data;//节点数据
int parent;//双亲位置下标
}PTNode;
typedef struct { // 树结构
PTNode nodes[tree_size];//节点数组
int r,n;//根的位置下标和结点数
}PTree;
例如,使用双亲表示法存储图 1(A)中的树结构时,数组存储结果为(B):
当算法中需要在树结构中频繁地查找某结点的父结点时,使用双亲表示法最合适。当频繁地访问结点的孩子结点时,双亲表示法就很麻烦,采用孩子表示法就很简单
孩子表示法
孩子表示法: 把每个节点的孩子节点排列起来,以单链表作存储结构,则n个节点有n个孩子链表,如果是叶子节点则单链表为空。然后n个头指针有组成一个线性表,采用顺序存储结构,存放进一个一维数组中
#define TElemType int
#define Tree_Size 100
//孩子表示法
typedef struct CTNode{
int child;//链表中每个结点存储的不是数据本身,而是数据在数组中存储的位置下标
struct CTNode * next;
}*ChildPtr;
typedef struct {
TElemType data;//结点的数据类型
ChildPtr firstchild;//孩子链表的头指针
}CTBox;
typedef struct{
CTBox nodes[Tree_Size];//存储结点的数组
int n,r;//结点数量和树根的位置
}CTree;
使用孩子表示法存储的树结构,正好和双亲表示法相反,适用于查找某结点的孩子结点,不适用于查找其父结点。
双亲孩子表示法
将双亲表示法和孩子表示法结合起来
孩子兄弟表示法
使用链式存储结构存储普通树。链表中每个结点由 3 部分组成:
其中孩子指针域,表示指向当前结点的第一个孩子结点,兄弟结点表示指向当前结点的下一个兄弟结点。
#define ElemType int
typedef struct CSNode{
ElemType data;
struct CSNode * firstchild,*nextsibling;
}CSNode,*CSTree;
通过孩子兄弟表示法,普通树转化为了二叉树,所以孩子兄弟表示法又被称为“二叉树表示法”或者“二叉链表表示法”。
常见树
- 二叉搜索树的节点放置规则是: 任何节点的键值一定大于其左子树中的每一个节点的键值,并小于其右子树中的每一个节点的键值。因此:
- 从根节点一直往左走,直到无左路可走,就可以得到最小元素
- 从根节点一直往右走,直到无右路可走,就可以得到最大元素
- 二叉搜索树(binary search tree)可以提供对数时间的元素插入和访问。
- 访问元素很简单
- 麻烦的是插入和删除
平衡二叉搜索树
- 如果输入值不够随机,或者经过某些插入和删除操作,二叉搜索数可能会失去平衡,导致效率低下。
- 为此,引入了平衡二叉树,包括AVL-tree、RB-tree、AA-tree等。
比较
树与线性表
树与森林
一棵树为树,两棵树及以上则称为森林
总结
树相关概念
- 树由节点(node)和边(edge)构成
- 整棵树有一个最上端节点,叫做根节点(root)
- 每个节点可以可以拥有具有方向性的边(directed edges),用来和其他节点相连。
- 相连节点中,在上面的叫做父节点(parent),在下面的叫做子节点(child)。
- 没有子节点的叫做叶节点(leaf)
- 子节点可以存在多个,如果最多只有两个子节点,那么就是二叉树
- 不同的节点如果拥有相同的父节点,那么彼此互为兄弟节点(siblings)
- 根节点到任何节点之间有唯一路径(path),路径所经过的边数,叫做路径长度(length)
- 跟节点到任意节点的路径长度,为该节点的深度(depth)。根节点的深度永远是0
- 某节点到最深子节点(叶节点)的路径长度,叫做该节点的高度(height)。整颗树的高度,以根节点的高度来代表。