引言
对于大量的输入数据,链表的线性访问时间太慢,不宜使用。本篇讨论一种简单的数据结构——树,它大部分操作的运行时间平均为。数据结构中有很多树的结构,其中包括二叉树、二叉搜索树、2-3树、红黑树等等。
树和森林的区别
基本概念
树(tree) 是一些节点的集合。
该集合可以是空集,若不是空集,则树由称为根(root) r
的节点以及0个或多个非空的子树组成。
每颗子树的根就做根r
的儿子(child),而r
是每颗子树的根的父亲。
上图中,A是根,节点F有一个父亲A并且有儿子K、L和M。每个节点可以有零个或任意个儿子。没有儿子的节点称为叶子节点(leaf)。
上图中的B、C、H等是叶子节点。
具有相同父亲的节点称为兄弟(siblings),因此B、C、D、E、F、G都是兄弟。这种定义和家谱很像,类似地,我们可以得到祖父和孙子的定义。J是A的孙子,A是I的祖父。
从节点到的**路径(path)**定义为节点的一个序列。
这条路径的 长(length) 是为该路径上的边的条数,即。
从每一个节点到它自己有一条长为0的路径。注意,在一颗树中从根到每个节点恰好存在一条路径。
对任意节点的 深度(depth) 为从根到的唯一的路径的长。因此,根的深度为0。
的 高(height) 是从到叶子节点的最长路径的长。因此所有的叶子节点(树叶)高都是0.
一棵树的高等于它的根的高。
对于上文中那颗树,E的深度为1(A-E)而高为2(E-J-Q)。该树的高为3。一棵树的深度等于它的最深的树叶的深度;该深度总是等于这棵树的高。
树这种数据结构有广泛的应用,比如操作系统中用到了红黑树,数据库用到了B+树,编译器用到了语法树。下面用Java来实现各种常见的树。
二叉树
二叉树(binary tree) 是一颗每个节点都不能多于两个孩子(儿子)的树。通常叫它的儿子为左孩子和右孩子(或左子树和右子树)。
二叉树的性质 :
- 在非空二叉树中,第i层的节点总数不超过,i>=1
- 深度为h的二叉树最多有个节点
- 对于任何一颗二叉树,如果其叶子节点数位,度为2(同时有两个孩子节点)的节点数为,则
- 具有n个节点的完全二叉树的深度为
- 给定n个节点,能构成h(n)种不同的二叉树,其中h(n)为卡特兰数的第n项,
满二叉树:除最后一层无任何子节点外,每一层上的所有节点都有两个子节点。
满二叉树的性质:
- 一颗树深度为h,最大层数为k,深度与最大层数相同,k=h
- 叶子数为
- 第k层的节点数是:
- 总节点数是:,且总节点数一定是奇数
完全二叉树:若设二叉树的深度为h,除第 h 层外,其它各层 (1~(h-1)层) 的节点数都达到最大个数,第h层所有的节点都连续集中在最左边,这就是完全二叉树。
我们定义通用二叉树接口:
二叉查找树
见图解二叉查找树
实现好了二叉查找树后,我们利用它来学习二叉树的遍历
二叉查找树的查询效率很依赖于树的深度。
若生成的二叉查找树像这样,其实它就退化为链表。下面介绍一种能自动平衡左右节点的二叉查找树。
AVL树
AVL树通常被称为平衡二叉(搜索)树。
见图解AVL树
AVL树虽然能够自动平衡,但是它有如下显著的缺点:
- 插入/删除后的旋转,成本不低
- 删除操作,最坏情况下需要次旋转
伸展树
伸展树是基于AVL树的,但它并没有AVL的平衡要求,任意节点的左右子树可以相差任意深度。与二叉搜索树类似,伸展树的单次搜索也可能需要n次操作。但伸展树可以保证,m次的连续搜索操作的复杂度为mlog(n)的量级,而不是mn量级。
实现见图解伸展树
B树系列
B-树
B树也称B-树,它是一颗平衡的多路搜索树。我们描述一颗B树时需要指定它的阶数,阶数表示一个节点最多有多少个孩子节点,一般用字母m表示阶数。当m取2时,就是我们常见的二叉搜索树。
有时也称M路B树,即有M个分支的B树
多级存储系统中使用B-树,可针对外部查找,大大减少I/O次数。
可以看到,B树是一颗矮胖的平衡多叉树。
详细图解与Java代码实现见图解B-树
红黑树
敬请期待