目录
- 一、树形结构特征
- 二、二叉树:概念和性质
- 三、二叉树的list实现
- 四、优先队列
- 五、离散事件模拟
- 六、二叉树的类实现(链接实现)
- 七、哈夫曼树
- 八、树和树林
一、树形结构特征
树形结构是由结点和结点之间的连接关系构成,它与表线性结构不同,特征包括:
- 一个结构如果不为空,其中就存在着唯一的起始结点,称为树根。
- 按照结构外的连接关系,树根外的其余结点都有且只有一个前驱,可以有0个或者多个后继。
- 结构里所有结点都在树根结点通过后继关系可达的节点集合里。
- 结点之间的联系不会形成循环关系。
- 从这种结构的任意两个不同的节点出发,通过后继关系可达的两个结点集合,或者互不相交,或者一个集合是另外一个集合的子集。
二、二叉树:概念和性质
二叉树是树中的每个结点最多关联两个后继结点。
- 一个结点的关联数是0或1或2
- 一个结点的后继结点明确分左右,或为左结点,或为右结点。
1、概念和性质
定义和图示
二叉树的子树可以为空,并且子树有明确的左右之分,如果根结点的两颗子树都为空,那么这就是一棵只包含根结点的二叉树。
几个基本概念
- 不包含任何结点的二叉树称为空树
- 只包含一个结点的二叉树是一棵单点树
- 一棵二叉树的根结点称为该树的子树根结点的父结点。子树的根结点称为二叉树树根结点的子结点。
- 一个二叉树里有些结点的两个子树都是空,没有子结点,这种结点称之为树叶。树中其余结点称为分支结点。
- 一个结点的子结点个数称为该结点的度数。二叉树中树叶结点的度数为0,分支结点的度数为1或者2。
路径、结点的层和树的高度
- 从一个祖先结点到任何子孙结点都有一系列边,这样的一系列边首尾相连就成为了一条路径,我们把边的个数称为该路径的长度。
- 从树根到树中任一结点的路径长度就是该结点所在的层数。
- 一棵二叉树的高度是树中结点的最大层数(也就是这棵树里的最长路径的长度)。
二叉树的性质
二叉树最重要的性质就是树的高度和树中可以容纳的最大结点个数之间的关系。
如果将树跟线性表做比较,那么树的高度就类似于表的长度。但是在长为n的表中只能容纳n个结点。而在高为n的二叉树中可以容纳2n+1个结点。
- 性质1:在非空二叉树第i层中最多有2i个结点(i>=0)
- 性质2:高度为h的二叉树最多有(2h+1-1)个结点(h>=0)
- 性质3:对于任何非空二叉树,如果叶结点的个数为n0,度数为2的结点个数为n2。那么n0=n2+1
满二叉树,扩充二叉树
如果一个二叉树中所有的分支结点的度数都是2,那么它就是一棵满二叉树。
满二叉树的叶节点比分支结点多1。跟二叉树的性质3一样。
对于二叉树T,加入足够多的新叶结点,使T的原有结点都变成度数为2的分支结点,得到的二叉树称为T的扩充二叉树。扩充二叉树中新增的结点称为其外部结点,原树T的结点称为其内部结点。
扩充二叉树的外部路径长度E是从树根到树中各外部结点的路径长度之和,内部路径长度I是从树根到树中各内部结点的路径长度之和。如果该树拥有n个内部结点,那么E=I+2*n。
完全二叉树
对于一棵高度为h的二叉树,如果其第0层到第h-1层的结点都满,并且如果下一层的结点都不满,则所有结点在最左边连续排列,空位都在右边,那么这样的二叉树就是完全二叉树。
* 性质1:n个结点的完全二叉树高度h不大于log2n的最大整数。
* 性质2:完全二叉树如果n个结点的完全二叉树的结点按照层次从左到右的顺序从0开始编号,对于任一结点都有:
1. 序号为0的结点是根。
2. 对于i>0,其父节点的编号是(i-1)/2。
3. 2*i+1<n,其左子节点的序号为2*i+1,否则它没有左子结点。
4. 2*i+2<n,其右子结点的序号为2*i+2,否则它没有右子结点。
这说明从完全二叉树到线性结构有很自然的双向映射,可以很方便地从相应线性结构恢复完全二叉树。
2、抽象数据类型
结点是二叉树的基础,通常主要用结点保存与应用有关的信息。此外,还需要记录二叉树的结构信息,保证可以检查结点的父子关系。
二叉树的基本操作应该包括创建二叉树。构造一棵二叉树需要基于两棵已有的二叉树和希望保存在树根结点的一项数据。
3、遍历二叉树
每个二叉树都有唯一的根结点,可以将其看做是这个二叉树的唯一标识,它是基于树结构处理过程的入口。遍历二叉树,就是按照某种系统化的方式访问二叉树里的每个结点一次。
遍历二叉树就像前面讨论过的状态搜索,以根为起始点,存在两种基本方式:
- 深度优先遍历,顺着一条路径尽可能向前探索,必要的时候回溯。对于二叉树,最基本的回溯情况是检查完一个叶节点。由于无路可走,只能回头。
- 宽度优先遍历,在所有的路径上齐头并进。
深度优先遍历
按照深度优先方式变量一棵二叉树,需要做三件事情:遍历左子树(L),遍历右子树(R),和访问根节点(D)。
根据这三种工作的不同顺序可以分为三种遍历顺序(假设总是先处理左子树,不然就是6种):
- 先根序遍历(按照DLR顺序)
- 中根序遍历(按照LDR顺序)
- 后根序遍历(按照LRD顺序)
例子:下图
- 先根序遍历是ABDHEICFJKG
- 中根序遍历是DHBEIAJFKCG
- 后根序变量是HDIEBJKFGCA
按照先根序遍历得到的结点序列称为先根序列。
按照中根序遍历得到的结点序列称为中根序列。
按照后根序遍历得到的结点序列称为后根序列。
如果二叉树中每个结点都有唯一标识,就可以同结点标识描述这些序列。显然,给定一个二叉树唯一确定了它的先根序列、后根序列和中根序列,但是给定一棵二叉树的任一一种遍历序列,都无法唯一确定相应的二叉树。
如果知道了一棵二叉树的对称序列,又知道另一遍历序列,就可以唯一确定这个二叉树。
宽度优先遍历
宽度优先是按照路径长度由近到远访问,这种遍历不能写成一个递归过程。宽度优先遍历又称为按层次顺序遍历,这样遍历产生的结点序列称为二叉树的层次序列。
上面例子中按照宽度优先遍历的层次序列是:ABCDEFGHIJK
遍历与搜索
一次二叉树遍历也就是一次覆盖整个状态的空间搜索,因此,前面关于空间状态搜索的方法和实现技术都可以原样移植到二叉树遍历问题中。
二叉树的特点是一个结点最多有两个子结点,因此从一条路径走下去不会与另外一条路径相交,就不会出现循环问题。因此算法比空间状态搜索要简化一些。
三、二叉树的list实现
二叉树的结点就是一个三元组,元素是左右子树和本结点数据。因此list和tuple都可以实现,如果要实现一种非变动的二叉树,用tuple,要实现可以修改结构的二叉树,用list。
1、设计和实现
利用list实现二叉树很容易。采用下面的设计
- 空树用None表示
- 非空子树用包含三个元素的表[d,l,r]表示。其中d表示根结点元素,l表示左子树,r表示右子树。
例如:下图
如果使用list来表示,就是['A',
['B', None, None],
['C',
['D', ['F', None, None], ['G', None, None]],
['E', ['I', None, None], ['H', None, None]],
]
class BTree: def __init__(self, data, left=None, right=None): if not isinstance(right, BTree) or if not isinstance(left, BTree): raise TypeError self.tree = [data, left, right] def is_empty_bintree(self): return self.tree is None def root(self): return self.tree[0] def left(self): return self.tree[1] def right(self): return self.tree[2] def set_root(self, data): self.tree[0] = data def set_left(self, left): if not isinstance(left, BTree): raise TypeError self.tree[1] = left def set_right(self, right): if not isinstance(right, BTree): raise TypeError self.tree[2] = right