一、树的基本知识
(1)概念
**树**:树是一些节点的集合。这个集合可以是空集;若集合不是空集,则树由根节点r以及0个或多个子树组成。
**边**:两个节点之间的连线就是边。
**树叶**:没有儿子的节点称为树叶。
**节点的度**:节点拥有的子树数称为节点的度。
**度**:节点的度的最大值称为树的度。
**兄弟**:具有相同父亲节点的节点称为兄弟。
**路径**:从节点n1到节点nk的路径定义为n1,n2,…,nk的一个序列,但是ni必须是n(i+1)的父节点。这也要求了路径必须从根节点往下遍历,
而不能逆向而上。而且对于任意节点,有且只有一条路径。
**路径的长**:某条路径上的边的个数就是路径的长。
**深度**:ni的深度表示从根节点到ni的路径的长。根的深度为0。
**高度**:ni的高度为从ni到其树叶的最大的深度。因此,所有树叶的高度都是0。一棵树的高等于它根的高,
一棵树的深度等于它最深的叶子节点的深度。
(2)遍历
对于一般的树来说,只有先序遍历与后序遍历,木有中序遍历——因为子树的个数不确定,无法决定访问根节点的时机。
**先序遍历**:
1、先访问根节点。
2、依次先序遍历根节点的每棵子树。
同时这也是一个递归定义。
**后序遍历**:
1、先依次后序遍历每棵子树。
2、再访问根节点。
(3)性质
**普通性质**:
1、有n个节点的树必然有n-1条边。 因为除根节点以外,每一个节点都有一条边通向它。
2、每一个节点有k个指向子树的域以及一个值域,那么在一棵有n个节点且度为k的树中,必有n(k-1)+1个空域。
证明:空域的个数为k*n0+(k-1)*n1+...+n[k-1]。而n = n0+n1+...+nk,同时边数为n-1,边数也为k*n0+(k-1)*n1+...+n[k-1],三式结合即可得证。
**二叉树性质**:
1、二叉树的第i层上最多有2的(i-1)次方个节点,注:i大于等于1-----根节点为第一层。
2、深度为k的二叉树最多有2^k-1个节点。注:此处根的深度为1。
3、记叶子节点的个数为n0,度为2的节点个数为n2,则n0=n2+1。证明过程如下:
设n1表示度为1的节点数,则总节点数n=n1+n0+n2-------式1。
同理,也可知总边数为n-1=n1+2*n2-------式2。则式2带入式1中就可证明性质3。
4、含有n个节点的二叉链表必含有n+1个空域(二叉链表——树的每一个节点都有三个域:
值域,左子树域和右子树域,左右子树域用来指向左右子树根节点)。
证明:空域的个数为2*n0+n1。而n = n0+n1+n2,同时n2=n0-1,所以2*n0+n1-1=n,性质得证。
(4)树的表示方法
(a)对任意一棵树,常用的表示方法为孩子兄弟法——以二叉链表做树的存储结构,
左链域指向第一个孩子节点,右链域指向下一个兄弟节点。该表示法又称二叉链表表示法或二叉树表示法。
(b)以二叉链表表示一棵树,可以将任意一棵树转换为二叉树,
而且该二叉树的根节点无右子树(因为原树的根结点没有兄弟节点)。
利用这个性质,可以将一个森林转换成二叉树。
(5)森林转二叉树
将森林中第二棵树的根节点看成第一棵树根结点的兄弟结点,则可以将森林转为二叉树。
1,先将各树转为二叉树。
2,将第i+1棵树的作为第i棵树的右子树。
或者:
1,第一棵树的根节点root为整个二叉树的根节点。
2,root的左子树为第一棵树的root的子树组成的二叉树。
3,root右子树为森林中除第一棵树之外的树组成的二叉树。
很明显第二种方法是一种递归方法,使用递归很容易写出相应的代码。
二、树的其他知识点
(a)、二叉树可以为空树
(b)、n0 = n2 + 1
二叉树中满足上式,其中n0表示度为0的即叶节点的个数,n2表示度为2的节点个数
(c)、树转化为二叉树的步骤:
(1) 将节点的所有兄弟节点用横线连接起来
(2) 除最左子节点的连接外,删掉每个节点所有与子节点间的连接
(3) 将树顺时针旋转45°,即得到二叉树
从二叉树还原树逆序执行上面步骤即可
(d)、线索二叉树的优缺点:
定义:对于n个结点的二叉树,在二叉链存储结构中有n+1个空链域,利用这些空链域存放在某种遍历次序下该结点的前驱结点和后继结点的指针,这些指针称为线索,加上线索的二叉树称为线索二叉树。
这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种。
优点:进行中序遍历时,不需要使用堆栈和递归处理,但一般二叉树需要;
由于充分使用空链接,所以避免了链接闲置浪费的情况,而且使得中序遍历的速度更快;
任一个节点都容易找出它的中序先行者和中序后继者。
缺点: 由于链接设置变多,导致加入和删除节点时的速度比一般二叉树慢;
线索链表解决了无法直接找到该结点在某种遍历序列中的前趋和后继结点的问题,但出现了二叉链表找左、右孩子困难的问题。
线索子树间不能共用。
(e)、平衡树,又称为AVL(由Adelson-Velskii和Landis发明的)树,它本身也是一棵二叉查找树。它或者是一颗空树,或者是具有下列性质的二叉树:它的左子树和右子树都是平衡二叉树,且左右子树的深度之差的绝对值不超过1。
(f)、B树,又叫多路平衡查找树,是一种高度大于等于1的m阶查找树,它也是一种平衡树概念的延伸。它有如下特点:
每一个节点都是m阶节点;
每一个m阶节点存放的键值最多为m-1个;
每一个m阶节点度数均小于等于m;
除非是空树,否则树根节点至少必须有两个以上的子节点;
除了树根和树叶节点外,每一个节点最多不超过m个子节点,但至少包含m/2(>= m/2)个子节点;
所有的树叶节点都在同一层;
大规模数据存储中,实现索引查询这样一个实际背景下,
树节点存储的元素数量是有限的(如果元素数量非常多的话,查找就退化成节点内部的线性查找了),
这样导致二叉查找树结构由于树的深度过大而造成磁盘I/O读写过于频繁,
进而导致查询效率低下(为什么会出现这种情况,待会在外部存储器-磁盘中有所解释),
那么如何减少树的深度(当然是不能减少查询的数据量),
一个基本的想法就是:采用多叉树结构(由于树节点元素数量是有限的,自然该节点的子树数量也就是有限的)。
B树的各种操作能使B树保持较低的高度,从而达到有效避免磁盘过于频繁的查找存取操作,从而有效提高查找效率。
(g)、二叉树的三种遍历使用了递归。深度和广度优先遍历也都使用了递归,但另外,树(乃至图)的深度优先遍历使用堆栈,广度优先遍历则使用了队列