1.什么是树
树是由n(n>=1)个有限结点组成一个具有层次关系的集合。
其每个结点有零个或多个子结点;没有父结点的结点称为根结点;每一个非根结点有且只有一个父结点;除了根结点外,每个子结点可以分为多个不相交的子树。
2.常见概念
节点的层次:从根节点开始,根节点为第一层,根的子节点为第二层。
树的深度(树的高度):一棵树中节点的最大层数就是树的深度。上图中树的深度为4。
父节点(双亲节点):若一个节点含有子节点,则这个节点称为其子节点的父节点。上图中B是E、F的父节点。
子节点(孩子节点):一个节点含有的子树的根节点称为该节点的子节点。上图中E、F是B的子节点。
兄弟节点:拥有共同父节点的节点互称为兄弟节点。上图中E、F互称为兄弟节点。
节点的度:节点的子树数目就是节点的度。上图中节点A的度是3,节点B的度是2。
叶子节点(终端节点):度为0的节点就是叶子节点。上图中K、L、G、M、I、J都是叶子节点。
分支节点(非终端节点):度不为0的节点就是分支节点。上图中A、B、C、D、E、H都是分支节点。
祖先:从根节点到该节点(包括节点自己)的所有节点都是这个节点的祖先。上图中E的祖先有A、B、E。
后代:从该节点(包括节点自己)到叶子节点的所有节点都是这个节点的后代。上图中E的后代有E、K、L。
森林:m(m>=0)颗互不相交的树构成的集合就是森林。
树的度:一棵树中,最大的节点的度称为树的度。上图中最大的节点的度是A或D,均为3,所以树的度是3。
节点深度:从根节点到该节点的路径长度。上图中根节点A的节点深度为0,L节点的节点深度为3。
节点高度:从叶子节点到该节点的路径长度。上图中根节点A的节点高度为3,L节点的节点高度为0。
3.树的遍历
遍历表达法有4种方法:先序遍历(前序遍历)、中序遍历、后序遍历、层次遍历。
先序遍历:对树按照根、左、右的规律进行访问。上图的遍历结果为ABCDEFG。
中序遍历:对树按照左、根、右的规律进行访问。上图的遍历结果为DBAGECF。
后序遍历:对树按照左、右、根的规律进行访问。上图的遍历结果为DBGEFCA。
层次遍历:对树按照从上到下、从左到右的规律进行访问。上图的遍历结果为ABCDEFG。
4.树的种类
通常我们按照每个节点的子节点数量的限制,将树分为二叉树与多叉树。二叉树是一个节点最多只有两个子节点的树结构;多叉树一个节点可以有多于两个的子节点。
二叉树常见的有满二叉树、完全二叉树、二叉查找树、平衡二叉树(AVL树)、最优二叉树(霍夫曼树)、红黑树。
多叉树常见的有B树、B+树、B*树、Trie树(字典树)。
另外也可以根据节点的有序性,将树分为无序树、有序树。这里主要按照子节点数量的限制展开来详述。
4.1.二叉树
二叉树的性质有:
二叉树的第 i 层最多有 2i-1 个结点。
二叉树的深度为k,那么它最多有 2k-1 个结点。
二叉树中,叶子结点数为 n0,度为 2 的结点数为 n2,则 n0=n2+1。
4.1.1.满二叉树
除叶子节点外,每个节点的度都为2。
满二叉树除具备二叉树的性质外,还有如下性质:
满二叉树中第 i 层的节点数为 2n-1 个。
深度为 k 的满二叉树必有 2k-1 个节点 ,叶子数为 2k-1。
满二叉树中不存在度为 1 的节点,每一个分支点中都有两棵深度相同的子树,且叶子节点都在最底层。
具有 n 个节点的满二叉树的深度为 log2(n+1)。
4.1.2.完全二叉树
除了最后一层外,其他各层的节点都有两个子节点,且最后一层的所有节点都集中在最左边的,称为完全二叉树。可以将满二叉树视为特殊的完全二叉树。
完全二叉树除具备二叉树的性质外,还有如下性质:
n 个结点的完全二叉树的深度为⌊log2n⌋+1。⌊log2n⌋ 表示取小于 log2n 的最大整数。
由于只能在最后一层的最右一个不为空的位置上进行增删操作的树,导致了完全二叉树最右下角的节点之前没有任何一个空位置。
使用完全二叉树,我们可以直接将一个数组映射成一棵树,然后通过这棵树对数组操作。
4.1.3.二叉查找树
二叉查找树(Binary Search Tree,BST),也称为二叉搜索树、有序二叉树(ordered binary tree)或排序二叉树(sorted binary tree)。二叉查找树具有如下性质:
若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
任意节点的左、右子树也都是二叉查找树。
二叉查找树优势在于查找、插入的时间复杂度较低,为O(log n)。
4.1.4.平衡二叉树
平衡二叉树(AVL树,用其发明者Adelson-Velshi和Landis的名字命名)是一颗高度平衡的二叉查找树;左右两个子树的高度差绝对值不超过1,且左右两个子树都是平衡二叉树。
为了保证平衡,AVL树中的每个节点都有一个平衡因子(balance factor,BF),它表示这个结点的左、右子树的高度差,即左子树高度减去右子树高度的结果值。AVL 树上所有结点的BF值只能是-1、0、1,如果二叉树上一个结点的BF绝对值大于1,则该二叉树就不是 AVL 树。
平衡二叉树优势在于查找的时间复杂度较低,为O(log n)。 对插入删除操作不友好。
4.1.5.最优二叉树
在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长度。若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权值。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权值的乘积。树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL。
给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也叫霍夫曼树或哈夫曼树。
上图中最优二叉树的WPL = 7 * 1 + 5 * 2 + 2 * 3 + 4 * 3 = 35。
最优二叉树的时间复杂度为O(n log n),在哈夫曼编码、哈夫曼译码、数据压缩与解压方面有很广泛的应用。
4.1.6.红黑树
红黑树(Red-Black Tree)是一种自平衡的二叉查找树。在平衡二叉树的基础上每个节点又增加了一个颜色的属性,节点的颜色只能是红色或黑色。
红黑树有以下特性:
- 根节点只能是黑色;
- 所有的叶子节点后面再接上左右两个空节点,这样可以保持算法的一致性,而且所有的空节点都是黑色;
- 其他的节点要么是红色,要么是黑色,红色节点的父节点和左右孩子节点都是黑色,及黑红相间;
- 在任何一棵子树中,从根节点向下走到空节点的路径上所经过的黑节点的数目相同,从而保证了是一个平衡二叉树。
红黑树的新增、删除、查询,时间复杂度都为O(log n)。在Java的集合中有广泛应用。
4.2.多叉树
4.2.1.B树
B树也叫B-树(-只是个符号),是一种平衡多路查找树。
B树的阶:节点最多有多少个子节点。即为这个B树的阶数,一般用字母M表示。
一棵M阶B树满足下列条件:
- 每个节点至多有M棵子树;
- 除根节点外,其他分支节点至少有⌈M/2⌉(向上取整,如⌈5/2⌉=3)棵子树;
- 根节点至少有两棵子树,除非B树只有一个节点;
- 有j个孩子节点的非叶节点有j−1个关键字,关键字按非降序排列。如上图,{45, 60, 82}这个节点有4个孩子,该节点有3个关键字45、60、82,这三个数非降序排列;
- 所有叶子节点具有相同的深度,这也说明B树是平衡的。
B树访问节点的次数与树的高度呈正比,而B树与二叉查找树相比,虽然高度都是对数级的,但是显然B树中底数比2大。因此,和二叉树相比,极大地减少了读取的次数。
B树与红黑树很相似,但在降低磁盘I/O操作方面要更好一些,因而多用于做文件系统的索引。
4.2.2.B+树
B+树是B树的变种,有着比B树更高的查询效率。也是一种多路查找树。
在B+树里面,非叶子节点不再存储数据,仅仅存储索引,而叶子节点存储具体的数据,并且最底层的数据直接之间从左到右按照从小到大的顺序分布,且是一个双链表的结构。
一棵M阶的B+树定义如下:
- 每个结点至多有M个孩子;
- 除根结点外,每个结点至少有⌈M/2⌉(向上取整,如⌈5/2⌉=3)个子节点,根结点至少有两个子节点;
- 有k个子节点的结点必有k个关键字。
B+树的非叶子节点不保存具体的数据,相比于B树的非叶子节点更小。故相同容量的磁盘能容纳的内部节点数目更多,磁盘I/O的读写次数也会降低;
B+树查询必须查找到叶子节点,而B树只要匹配到即可而不用管元素位置(B树最好情况下查找到根节点,最坏情况下查找到叶子结点,所说性能很不稳定),因此B+树查找更稳定。
在基于范围的查询上,B+树首先通过二分查找,找到范围下限,然后遍历叶子节点链表直至找到上限;而B树首先二分查找到范围下限,再不断通过中序遍历,直到查找到范围的上限。因此B+树更适合做文件索引和数据库的范围查询的索引。
4.2.3.B*树
B树在B+树的基础上,给非根和非叶子结点再增加指向兄弟的指针。
B树的定义中,要求非叶子结点关键字个数至少为(2/3) * M。而B树的非叶子结点(除了根节点)至少有⌈M/2⌉个子节点,也代表了有⌈M/2⌉个关键字。因而,B*树比B+树的空间使用率更高。
4.2.4.Trie树
Trie树也叫字典树。是专门处理字符串匹配的数据结构,解决在一组字符串集合中快速查找某个字符串。经常被搜索引擎系统用于文本词频统计。
Trie树有3个基本性质:
- 根节点不包含字符,除根节点外每一个节点都只包含一个字符;
- 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串;
- 每个节点的所有子节点包含的字符都不相同。
构建Trie树时间复杂度是 O(n),其中n是Trie树中所有元素的个数;查询Trie树时间复杂度是 O(k),其中k表示要查找的字符串的长度。
5.二叉树线索化
这里先引申两个概念。
前驱结点:一个节点的前一个节点称为前驱节点。
后继节点:一个节点的后一个节点称为后继节点。
对于一个有n个节点的二叉链表,每个节点有指向左右节点的2个指针域,整个二叉链表存在2n个指针域。n个节点的二叉链表有n-1条分支线,那么空指针域的个数=2n-(n-1)=n+1个空指针域。利用二叉链表中的空指针域,存放指向该节点在某种遍历次序下的前驱和后继节点的指针。加上了这种指针的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded Binary Tree)。
对二叉树以某种遍历方式(如先序、中序、后序或层次等)进行遍历,使其变为线索二叉树的过程称为对二叉树进行线索化。
5.1.结构
上图中共有10个节点,指针域有20个(每个节点左右各一个),9条分支线,11个空指针域(带^的域)。可以看出,线索化二叉等于是把一棵二叉树转变成了一个“特殊的双向链表“。
5.2.分类
根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树。
5.3.优势
可以把二叉树看作一个链表结构,从而可以像遍历链表那样来遍历二叉树,进而提高效率。
线索链表解决了无法直接找到该结点在某种遍历序列中的前驱和后继结点的问题,解决了二叉链表找左、右孩子困难的问题。
利用线索二叉树进行中序遍历时,不必采用堆栈处理,速度较一般二叉树的遍历速度快,节约存储空间。