数据结构学习笔记(3)——树
机械工业出版社《数据结构与算法——Python语言实现》学习笔记。
画图工具:draw.io
目录
1 树的基本概念
1.1 树的定义和属性
定义
我们将树T定义为存储一系列元素的有限节点集合,这些节点具有parent-children关系并且满足如下属性:
- 如果树T不为空,则它一定具有一个称为**根节点(root)**的特殊节点,并且该节点没有父节点。
- 每个非根节点 都具有唯一的父节点 ,每个具有父节点 的节点都是节点 的一个孩子。
同一父节点的孩子节点之间是兄弟关系。一个没有孩子的节点 称为外部节点(叶子节点)。一个有一个或多个孩子的节点 称为内部节点。
边和路径
树 T 的边指的是一对节点 , 是 的父节点或 是 的父节点。树 T 当中的路径指的是一系列的节点,这些节点中任意两个连续的节点之间都是一条边。
有序树
如果树中每个节点的孩子节点都有特定的顺序,则称该树为有序树。通常我们按照从左到右的顺序对兄弟节点进行排序。
1.2 树的抽象数据类型
一棵树的位置对象支持如下方法:
- p.element():返回存储在位置p中的元素。
树的抽象数据类型支持如下访问方法。允许使用者访问一棵树的不同位置:
- T.root():返回树T的根节点的位置。如果树为空,则返回None。
- T.is_root():如果位置p是树T的根,则返回True。
- T.parent(p):返回位置为p的父节点的位置。如果p的位置为树的根节点,则返回None。
- T.num_children(p):返回位置为p的孩子节点的编号。
- T.children(p):产生位置为p的孩子节点的一个迭代。
- T.is_leaf(p):如果位置p没有任何孩子,则返回True。
- len(T):返回树T所包含的元素的数量。
- T.is_empty():如果树T不包含任何节点,则返回True。
- T.positions():生成树T的所有位置的迭代。
- iter(T):生成树T中存储的所有元素的迭代。
1.3 计算深度和高度
深度定义和算法
- 如果p是根节点,那么p的深度为0
- 否则,p的深度就是其父节点的深度加1
def depth(self, p):
'''返回位置p处的深度'''
if self.is_root(p):
return 0
else:
return 1 + self.depth(self.parent(p))
高度定义和算法
- 如果p是一个叶子节点,那么它的高度为0。
- 否则,p的高度是它孩子节点中的最大高度加1。
def _height(self, p):
'''返回根为p的子树的高度'''
if self.is_leaf(p):
return 0
else:
return 1 + max(self._height(c) for c in self.children(p))
def height(self, p=None):
'''返回根为p的子树的高度,如果p=None则返回整个树的高度'''
if p is None:
p = self.root()
return self._height(p) #调用_height()函数
2 二叉树
二叉树是具有以下属性的有序树:
- 每个节点最多有2个孩子节点。
- 每个孩子节点被命名为左孩子或右孩子。
- 对于每个节点的孩子节点,在顺序上,左孩子先于右孩子。
若子树的根为内部节点 的左孩子或右孩子,则该子树相应地被称为节点 的左子树或右子树。除了最后一个节点的父节点外,若每个节点都有零个或两个子节点,则这样的二叉树被称为完全二叉树(满二叉树)。若二叉树不完全,则称为不完全二叉树。
如图,决策树是一种完全二叉树。
2.1 二叉树的抽象数据类型
在树的基础上,支持三种额外的访问方法:
- T.left(p):返回 p 左孩子的位置,若 p 没有左孩子,则返回None。
- T.right(p):返回 p 右孩子的位置,若 p 没有右孩子,则返回None。
- T.sibling(p):返回 p 兄弟节点的位置,若 p 没有兄弟节点,则返回None。
2.2 二叉树的属性
- 二叉树的 层至多有 个节点
命题: 设 T 为非空二叉树, 和 分别表示 T 的节点数、外部节点数、内部节点数和高度,则 T 具有如下性质:
若 T 是完全二叉树,则 T 具有如下性质:
完全二叉树中内部节点与外部节点的关系:
命题 在非空完全二叉树 T 中,有 个外部节点和 个内部节点,则有
3 树的实现
3.1 链式二叉树结构
省略详细代码
链式二叉树性能
操作 | 运行时间 |
---|---|
len, is_empty | O(1) |
root, parent, left, right, sibling, children, num_children | O(1) |
is_root, is_leaf | O(1) |
depth§ | O() |
height | O(n) |
add_root, add_left, add_right, replace, delete, attach | O(1) |
3.2 数组表示的二叉树
二叉树的一种表示法是对 T 的位置进行编号。对于 T 的每个位置 p,设 层编号函数 为整数且定义如下:
- 若 p 是 T 的根节点,则 为0。
- 若 p 是位置 q 的左孩子,则
- 若 p 是位置 q 的右孩子,则
数组表示的二叉树的性能
- 优势: 位置 p 能用简单的整数 来表示,且基于位置的方法(如root、parent、left和right方法)能采用对编号 进行简单算术操作的方法来执行。
- 缺点: 空间使用情况极大地依赖于树的形状。不能有效地支持树的一些更新方法(如删除节点且提升自己的孩子节点的编号需要花费O(n)的时间)
3.3 一般树的链式存储结构
与二叉树的链式结构类似,但是对于一般树,一个节点所拥有的孩子节点之间没有优先级限制。因此可以使每个节点都配置一个容器,该容器存储存储指向每个孩子的引用。
4 树的遍历算法
4.1 树的先序和后序遍历
树 T 的 先序遍历(深度优先遍历) 中,首先访问 T 的根,然后递归地访问子树的根。
伪代码:
Algorithm preorder(T, p):
perform the "visit" action for position p
for each child c in T,children(p) do
preorder(T, c)
后序遍历优先遍历子树的根,然后访问根。
Algorithm postorder(T, p):
for each child c in T,children(p) do
postorder(T, c)
perform the "visit" action for position p
4.2 树的广度优先遍历
在访问深度 d+1 的位置之前先访问深度 d 的位置。
算法:
Algorithm breadthfirst(T):
Initialize queue Q to contain T.root()
while Q not empty do
p = Q.dequeue()
perform the "visit" action for position p
for each child c in T.children(p) do
Q.enqueue(c)
4.3 (仅)二叉树的中序遍历
Algorithm inorder(p):
if p has a left child lc then
inorder(lc)
perform the "visit" action for position p
if p has a right child rc then
inorder(rc)
中序遍历算法的应用
- 使用二叉树表达一个算术表达式,中序遍历访问的位置与标准的顺序表达式的顺序一致,如上图:。
- 二叉搜索树 T 中的中序遍历可以按照非递减次序访问元素。
4.4 欧拉图
可以非正式地定义为沿着 T “走”,从根开始“走”向最后一个孩子,我们保持在左边,像“墙”一样查看 T 的边缘(下图红色路径)。
根据欧拉图,我们可以在每个子树遍历前、后以及在遍历左子树之后,右子树之前)分别设计所需要调用的方法。对于叶子节点,会连续调用3个方法:
- 子树遍历前:previsit()
- 子树遍历后:postvisit()
- 遍历左子树之后,右子树之前(仅可在二叉树中使用):invisit()