二叉树是一种最简单的树形结构,特点是树中每个结点至多关联到两个后继结点,一个结点的关联结点数可以为0、1或2;另一个特点是后继结点明确的分左右:左关联结点或右关联结点。
基本概念
二叉树是结点的有穷集合,或者为空集,或者为只有一个根结点,其余结点分属两棵不相交的二叉树,左子树和右子树。
二叉树的根结点称为该子树根结点的父结点,子树的根结点称为二叉树根节点的子结点;父结点和子结点的概念是相对的。
从父结点到子结点的连线称为父结点到子结点的边,这种边有方向,可以定义其传递关系。相同父结点的两个子节点互为兄弟结点。两棵子树都为空的结点称为树叶,其余结点称为分支结点。
一个结点的子结点个数称为该结点的度数,树叶节点的度数为0,分支节点的度数为1或2。
路径、结点的层和树的高度
从一个祖先结点到其任何子孙结点都存在一系列边,称为路径,路径中边的条数称为路径的长度。
二叉树是一种层次结构,树根为最高层,为0,对于位于k层的结点,其子结点是k+1层的元素。二叉树的高度为树种结点的最大层数。
二叉树的性质
- 在非空二叉树第ii层至多有个结点。
- 高度为hh的二叉树至多有个结点。
- 对于任何非空二叉树,如果叶结点个数为n0n0,度数为2的结点个数为n2n2,那么n0=n2+1n0=n2+1。
满二叉树
如果二叉树所有的分支节点的度数都是2,则称它为一棵满二叉树。
- 满二叉树的叶结点比分支节点多一个。
扩充二叉树
对于二叉树TT加入足够多的新叶节点,使T的原有结点都变成度数为2的分支节点,得到的二叉树为的扩充二叉树。扩充二叉树中新增的结点称为其外部节点,原树TT的结点称为其内部结点。
- 扩充二叉树的外部结点个数比内部节点多1。
- 扩充二叉树的外部路径长度是从树根到树中各外部结点的路径长度之和,内部路径长度II是从树根到树中各内部结点的路径长度之和。如果该树有个内部结点,那么E=I+2∗nE=I+2∗n。
完全二叉树
对于一棵高为hh的二叉树,如果第层至第h−1h−1层的结点都满,下一层的结点不满,且所有结点都在最左次连续排列,空位都在右边,这样的二叉树为完全二叉树。
- nn个结点的完全二叉树高度为不大于的最大整数。
- 如果nn个结点的完全二叉树按层次从左到右从开始编号,对任意结点都有:
1.序号为00的结点为根结点。
2.对于,其父结点为(i−1)/2(i−1)/2取整。
3.若2∗i+1<n2∗i+1<n,其左子结点序号为2∗i+12∗i+1,否则它无左子结点。
4.若2∗i+2<n2∗i+2<n,其右子结点序号为2∗i+22∗i+2,否则它无右子结点。
完全二叉树可以方便的存入一个表或数组,直接根据元素的下标就能找到它的子结点或父结点。从完全二叉树到线性结构有自然的双向映射,可以方便的从相应线性结构恢复完全二叉树。
抽象数据类型
结点是二叉树的基础,通常主要用结点保存与应用有关的信息,此外还需要记录二叉树的结构信息,至少要保证能检查结点的父子关系,例如:能从一个结点找到其左/右子结点。
ADT BinTree:
BinTree(self,data,left,right) #构造一个新树
is_empty(self) #判断自己是否为空
num_nodes(self) #求二叉树的结点个数
data(self) #获取二叉树根存储的数据
left(self) #获取二叉树的左子树
right(self) #获取二叉树的右子树
set_left(self, btree) #用btree取代原来的左子树
set_right(self, btree) #用btree取代原来的右子树
traversal(self) #遍历二叉树中各结点数据的迭代器
forall(self, op) #对二叉树中的每个结点的数据执行op操作
遍历二叉树
每棵二叉树有唯一的结点,可以将其看作这棵二叉树的唯一标识。从根结点出发应该能找到树中所有的信息,其基础是从父结点找到两个子结点,因此,实际中常用二叉树的根结点代表这棵二叉树,其左右子树由它们的根结点代表。
对任何汇集结构都有逐一处理其中所存数据元素的问题,即遍历。遍历二叉树以根为起点,存在两种基本方式:
- 深度优先遍历,顺着一条路径尽可能向前探索,必要时回溯。
- 宽度优先遍历,在所有路径上齐头并进。
二叉树的List实现
二叉树是递归结构,ListList也是递归结构,基于ListList类实现二叉树可以采用下面的设计:
- 空树用NoneNone表示。
- 非空二叉树用包含三个元素的表[d,l,r][d,l,r]表示,其中:dd表示存在于根结点的元素,和rr是两棵子树,采用于整个二叉树同样结构的list表示。
显然,这样做把二叉树映射到一种分层的结构,每棵二叉树都有与之对应的listlist,例如下面是一棵二叉树的listlist表示:
['A', ['B', None, None],
['C', ['D', ['F', None, None]
['G', None, None]],
['E', ['H', None, None]
['I', None, None]]]]
下面是实现相关二叉树基本操作的一组函数定义:
def BinTree(data, left=None, rigth=None):
return [data, left, right]
def is_empty_BinTree(btree):
return btree is None
def root(btree):
return btree[0]
def left(btree):
return btree[1]
def rigth(btree):
return btree[2]
def set_root(btree, data):
btree[0] = data
def set_left(btree, left):
btree[1] = data
def set_right(btree, right):
btree[2] = data
基于上述构造函数的嵌套调用,可以做出任意复杂度的二叉树,例如:
t1 = BinTree(2, BinTree(4), BinTree(8))
#这相当于:
t1 = [2, [4, None, None], [8, None, None]]
#可以修改二叉树中的任意部分:
set_left(left(1), BinTree(5)) #把t1左子树的左子树换成BinTree(5)
[2, [4, [5, None, None], None], [8, None, None]]
二叉树的简单应用:表达式树
数学表达式具有递归结构,一个运算符作用于相应的运算对象,其运算对象又可以是任意复杂的表达式,二叉树中结点与子树的关系可用于表示运算符和运算对象的作用关系。
下面只考虑二元表达式,可以很自然的映射到二叉树:
- 以基本运算对象作为叶结点中的数据。
- 以运算符作为分支结点的数据:1.两棵子树是它的运算对象。2.子树可以是基本的运算对象,也可以是任意复杂的二元表达式。
一个结构正确的二元表达式对应于一棵满二叉树。
- 先根序遍历得到:∗−ab+/cde∗−ab+/cde
- 后根序遍历得到:ab−cd/e+∗ab−cd/e+∗
- 对称序遍历得到:a−b∗c/d+ea−b∗c/d+e
构造表达式
因为数学表达式是不变的,这里用tuple来实现二叉树;为使表达更简洁清晰,基本的运算对象直接放在空树的位置,即:
('*', 3, ('+', 2, 5))
下面定义几个表达式构造函数:
def make_sum(a, b):
return ('+', a, b)
def make_prod(a, b):
return ('*', a, b)
def make_diff(a, b):
return ('-', a, b)
def make_div(a, b):
return ('/', a, b)
#下面语句构造出一个简单的基础表达式:
e1 = make_prod(3, make_sum(2, 5))
#这种结构可以构造出任意复杂结构的表达式
make_sum(make_prod('a', 2), make_prod('b', 3))
#在定义表达式处理函数时,需要经常区分基本表达式(直接处理)和复合表达式(递归处理),为分辨这种情况,定义一个判别是否为基本表达式的函数:
def is_basic_exp(a):
return not isinstance(a, tuple)
#为简单起见,这里认为只有int、float、complex三个具体数值类型的对象,判断是否为数值的函数可以用下面的定义:
def is_number(x):
return isinstance(x, int) or isinstance(x, float) or isinstance(x, complex)
表达式求值
根据前面的定义,可以实现各种表达式操作,例如,求表达式值得函数:
表达式求值规则:
- 对表达式里的数和变量,其值就是他们自身。
- 其他表达式根据运算符的情况处理,可以定义专门的处理函数。
- 如果一个运算符的两个运算对象都是数,就可以求出一个值。
def eval_exp(e):
if is_basic_exp(e):
return e
op, a, b = e[0], eval_exp(e[1]), eval_exp(e[2]) #递归自身,
if op == '+':
return eval_sum(a, b)
elif op == '-':
return eval_diff(a, b)
elif op == '*':
return eval_prod(a, b)
elif op == '/':
return eval_div(a, b)
else:
raise ValueError("Unknown operator:", op)