树是非线性存储结构,存储的是具有“一对多”关系的数据元素的集合。
使用树结构存储的集合 {A,B,C,D,E,F,G,H,I,J,K,L,M} 的示意图。对于数据 A 来说,和数据 B、C、D 有关系;对于数据 B 来说,和 E、F 有关系。这就是“一对多”的关系。 将具有“一对多”关系的集合中的数据元素按照图中的形式进行存储,整个存储形状在逻辑结构上看,类似于实际生活中倒着的树,所以称这种存储结构为“树型”存储结构。
使用树结构存储的每一个数据元素都被称为“结点”。例如,图 1中,数据元素 A 就是一个结点;
对于图 1中的结点 A、B、C、D 来说,A 是 B、C、D 结点的父结点(也称为“双亲结点”),而 B、C、D 都是 A 结点的子结点(也称“孩子结点”)。对于 B、C、D 来说,它们都有相同的父结点,所以它们互为兄弟结点。
每一个非空树都有且只有一个被称为根的结点。图 1中,结点A就是整棵树的根结点。 树根的判断依据为:如果一个结点没有父结点,那么这个结点就是整棵树的根结点。
如果结点没有任何子结点,那么此结点称为叶子结点(叶结点)。例如图 1中,结点 K、L、F、G、M、I、J 都是这棵树的叶子结点。
如图 1中,整棵树的根结点为结点 A,而如果单看结点 B、E、F、K、L 组成的部分来说,也是棵树,而且节点 B 为这棵树的根结点。所以称 B、E、F、K、L 这几个结点组成的树为整棵树的子树;同样,结点 E、K、L 构成的也是一棵子树,根结点为 E。 注意:单个结点也是一棵树,只不过根结点就是它本身。图 1中,结点 K、L、F 等都是树,且都是整棵树的子树。 知道了子树的概念后,树也可以这样定义:树是由根结点和若干棵子树构成的。
如果集合本身为空,那么构成的树就被称为空树。 空树中没有结点。 补充:在树结构中,对于具有同一个根结点的各个子树,相互之间不能有交集。例如,图 1中,除了根结点 A,其余元素又各自构成了三个子树,根结点分别为 B、C、D,这三个子树相互之间没有相同的结点。如果有,就破坏了树的结构,不能算做是一棵树。结点的度和层次 对于一个结点,拥有的子树数(结点有多少分支)称为结点的度(Degree)。例如,图 1中,根结点 A 下分出了 3 个子树,所以,结点 A 的度为 3。 一棵树的度是树内各结点的度的最大值。图 1表示的树中,各个结点的度的最大值为 3,所以,整棵树的度的值是 3。
从一棵树的树根开始,树根所在层为第一层,根的孩子结点所在的层为第二层,依次类推。对于图 1来说,A 结点在第一层,B、C、D 为第二层,E、F、G、H、I、J 在第三层,K、L、M 在第四层。 一棵树的深度(高度)是树中结点所在的最大的层次。图 1树的深度为 4。 如果两个结点的父结点虽不相同,但是它们的父结点处在同一层次上,那么这两个结点互为堂兄弟。例如,图 1中,结点 G 和 E、F、H、I、J 的父结点都在第二层,所以之间为堂兄弟的关系。
如果树中结点的子树从左到右看,谁在左边,谁在右边,是有规定的,这棵树称为有序树;反之称为无序树。 在有序树中,一个结点最左边的子树称为"第一个孩子",最右边的称为"最后一个孩子"。 图 1来说,如果是其本身是一棵有序树,则以结点 B 为根结点的子树为整棵树的第一个孩子,以结点 D 为根结点的子树为整棵树的最后一个孩子。
由 m(m >= 0)个互不相交的树组成的集合被称为森林。图 1中,分别以 B、C、D 为根结点的三棵子树就可以称为森林。
树可以理解为是由根结点和若干子树构成的,而这若干子树本身是一个森林,所以,树还可以理解为是由根结点和森林组成的。用一个式子表示为: Tree =(root,F),其中,root 表示树的根结点,F 表示由 m(m >= 0)棵树组成的森林。
数据结构中为了存储和查找的方便,用各种树结构来存储文件,我们首先介绍下基本的树的种类:二叉查找树(二叉排序树)、平衡二叉树(AVL树)、红黑树、B-树、B+树、字典树(trie树)、后缀树、广义后缀树。
二叉查找树是一种动态查找表,具有这些性质: (1)若它的左子树不为空,则左子树上的所有节点的值都小于它的根节点的值; (2)若它的右子树不为空,则右子树上所有节点的值都大于它的根节点的值; (3)其他的左右子树也分别为二叉查找树; (4)二叉查找树是动态查找表,在查找的过程中可见添加和删除相应的元素,在这些操作中需要保持二叉查找树的以上性质。
含有相同节点的二叉查找树可以有不同的形态,而二叉查找树的平均查找长度与树的深度有关,所以需要找出一个查找平均长度最小的一棵,那就是平衡二叉树,具有以下性质: (1)要么是棵空树,要么其根节点左右子树的深度之差的绝对值不超过1; (2)其左右子树也都是平衡二叉树; (3)二叉树节点的平衡因子定义为该节点的左子树的深度减去右子树的深度。则平衡二叉树的所有节点的平衡因子只可能是-1,0,1。
红黑树是一种自平衡二叉树,在平衡二叉树的基础上每个节点又增加了一个颜色的属性,节点的颜色只能是红色或黑色。具有以下性质: (1)根节点只能是黑色; (2)红黑树中所有的叶子节点后面再接上左右两个空节点,这样可以保持算法的一致性,而且所有的空节点都是黑色; (3)其他的节点要么是红色,要么是黑色,红色节点的父节点和左右孩子节点都是黑色,及黑红相间; (4)在任何一棵子树中,从根节点向下走到空节点的路径上所经过的黑节点的数目相同,从而保证了是一个平衡二叉树。
B-树是一种平衡多路查找树,它在文件系统中很有用。一棵m阶B-树(图为4阶B-树),具有下列性质: (1)树中每个节点至多有m棵子树; (2)若根节点不是叶子节点,则至少有2棵子树; (3)除根节点之外的所有非终端节点至少有 m/2 棵子树; (4)每个节点中的信息结构为(A0,K1,A1,K2......Kn,An),其中n表示关键字个数,Ki为关键字,Ai为指针; (5)所有的叶子节点都出现在同一层次上,且不带任何信息,也是为了保持算法的一致性。
B+数是B-树的一种变形,它与B-树的差别在于(图为3阶B+树): (1)有n棵子树的节点含有n个关键字; (2)所有的叶子节点包含了全部关键字的信息,及指向这些关键字记录的指针,且叶子节点本身按关键字大小自小到大顺序链接; (3)所有非终端节点可以看成是索引部分,节点中仅含有其子树(根节点)中最大(或最小)关键字,所有B+树更像一个索引顺序表; (4)对B+树进行查找运算,一是从最小关键字起进行顺序查找,二是从根节点开始,进行随机查找。
字典树是一种以树形结构保存大量字符串。以便于字符串的统计和查找,经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来节约存储空间,最大限度地减少无谓的字符串比较,查询效率比哈希表高。具有以下特点: (1)根节点为空; (2)除根节点外,每个节点包含一个字符; (3)从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。 (4)每个字符串在建立字典树的过程中都要加上一个区分的结束符,避免某个短字符串正好是某个长字符串的前缀而淹没。
所谓后缀树,就是包含一则字符串所有后缀的压缩了的字典树。先说说后缀的定义。给定一长度为n的字符串S=S 1 S 2 ..S i ..S n ,和整数i,1 <= i <= n,子串S i S i+1 ...S n 都是字符串S的后缀。以字符串S=XMADAMYX为例,它的长度为8,所以S[1..8], S[2..8], ... , S[8..8]都算S的后缀,我们一般还把空字串也算成后缀。这样,我们一共有如下后缀。对于后缀S[i..n],我们说这项后缀起始于i。
所有这些后缀字符串组成一棵字典树:
上图,我们可以看到不少值得压缩的地方。比如蓝框标注的分支都是独苗,没有必要用单独的节点同边表示。如果我们允许任意一条边里包含多个字母,就可以把这种没有分叉的路径压缩到一条边。另外每条边已经包含了足够的后缀信息,我们就不用再给节点标注字符串信息了。我们只需要在叶节点上标注上每项后缀的起始位置。于是我们得到下图:
这样的结构丢失了某些后缀。比如后缀X在上图中消失了,因为它正好是字符串XMADAMYX的前缀。为了避免这种情况,我们也规定每项后缀不能是其它后缀的前缀。要解决这个问题其实挺简单,在待处理的子串后加一个空字串就行了。例如我们处理XMADAMYX前,先把XMADAMYX变为 XMADAMYX$,于是就得到suffix tree。
这就形成一棵后缀树了。关于如何建立一棵后缀树,已有很成熟的算法,能在o(n)时间内解决。
广义后缀树是好几个字符串的的所有后缀组成的字典树,同样每个字符串的所有后缀都具有一个相同的结束符,不同字符串的结束符不同。
传统的后缀树只能处理一个单词的所有后缀。广义后缀树存储任意多个单词的所有后缀。例如字符串“abab”和“baba”,首先将它们使用特殊结束符链接起来,如表示成"ababbaba#",然后求连接后的新字符的后缀树,遍历所得后缀树,如遇到特殊字符,如"baba#",然后求连接后的新字符的后缀树,遍历所得后缀树,如遇到特殊字符,如"","#"等则去掉以该节点为跟的子树,最后所得后缀树即为原字符串组的广义后缀树。其实质是将两个字符串的所有后缀,即:abab,bab,bab,ab,b,b,baba#,aba#,ba#,a#,组成字典树,再进行压缩处理。
广义后缀树的一个常应用就是判断两个字符串的相识度。
简单地理解,满足以下两个条件的树就是二叉树:
二叉树的性质经过前人的总结,二叉树具有以下几个性质:
性质 3 的计算方法为:对于一个二叉树来说,除了度为 0 的叶子结点和度为 2 的结点,剩下的就是度为 1 的结点(设为 n1),那么总结点 n=n 0 +n 1 +n 2 。 同时,对于每一个结点来说都是由其父结点分支表示的,假设树中分枝数为 B,那么总结点数 n=B+1。而分枝数是可以通过 n1 和 n2 表示的,即 B=n 1 +2 n 2 。所以,n 用另外一种方式表示为 n=n 1 +2 n 2 +1。 两种方式得到的 n 值组成一个方程组,就可以得出 n 0 =n 2 +1。
二叉树还可以继续分类,衍生出满二叉树和完全二叉树。
如果二叉树中除了叶子结点,每个结点的度都为 2,则此二叉树称为满二叉树。
满二叉树除了满足普通二叉树的性质,还具有以下性质:
如果二叉树中除去最后一层节点为满二叉树,且最后一层的结点依次从左到右分布,则此二叉树被称为完全二叉树。
如图 3a) 所示是一棵完全二叉树,图 3b) 由于最后一层的节点没有按照从左向右分布,因此只能算作是普通的二叉树。 完全二叉树除了具有普通二叉树的性质,它自身也具有一些独特的性质,比如说,n 个结点的完全二叉树的深度为 [log 2 n ]+1。
[log 2 n ]表示取小于[log 2 n ]的最大整数。例如,[[log 2 4 ]] = 2,而 [[log 2 5 ]] 结果也是 2。
对于任意一个完全二叉树来说,如果将含有的结点按照层次从左到右依次标号(如图 3a)),对于任意一个结点 i ,完全二叉树还有以下几个结论成立: