Python数据结构与算法(6.1)——树
Python数据结构与算法(4.1)——树
- 0. 学习目标
- 1. 树的基本概念
- 1.1 常用术语
- 1.2 树的抽象数据类型
- 1.3 树的应用场景
- 2. 树的实现
- 2.1 链式存储实现
- 2.2 顺序存储实现
- 2.3 实现对比
- 3. 树的应用
- 3.1 链树
- 3.2 顺序树
- 小结
0. 学习目标
树 (Tree
) 是程序设计和算法中最重要的非线性数据结构之一,它以层次化的方式组织数据,广泛应用于文件系统、数据库索引、表达式解析、网络路由等场景。本节将首先介绍树的基本概念及其抽象数据类型 (ADT
),然后讲解常见的两种存储实现方式,并通过示例验证其操作;最后结合实际问题,演示如何利用树实现复杂算法。
通过本节学习,应掌握以下内容:
- 树的基本概念、术语及常见类型
- 树的抽象数据类型定义及核心操作
- 链式存储与顺序存储(数组表示)的实现方法及时间空间复杂度分析
- 常用遍历算法(前序、中序、后序、层次)及其实现
- 利用树解决实际问题(表达式求值等)
1. 树的基本概念
在计算机科学中,树 (Tree
) 是一种表示层次关系的非线性抽象数据结构,由节点 (Node
) 和连接节点的边 (Edge
) 组成。每个节点最多只有一个父节点,但可以连接任意数量的子节点,这意味着树中不存在环路或循环结构。根节点 (Root
) 是没有父节点的节点,作为树的起始点;而没有子节点的节点称为叶节点 (Leaf
)。树中每个节点可以拥有零个或多个子节点,适用于文件系统、组织结构等场景。
1.1 常用术语
树 (Tree
):由节点 (Node
) 和连接节点的边 (Edge
) 组成的层次结构。
根节点 (Root
):没有父节点的唯一节点。
子节点 (Child
) 与父节点 (Parent
):父节点是直接与子节点相连且位于上层的节点,子节点是其下层节点。
叶子节点 (Leaf
):没有子节点的节点。
内部节点(Internal Node
):至少有一个子节点的非根节点。
深度 (Depth
):从根节点到某节点的边数;根的深度为 0
。
高度 (Height
):从某节点到最深叶子的最长边数;整棵树的高度是根的高度。
度 (Degree
):节点拥有的子节点个数;树的度是所有节点度的最大值。
二叉树 (Binary Tree
):度不超过 2
的有序树,子节点分别称为左子树和右子树。
1.2 树的抽象数据类型
ADT Tree
数据对象: D = n i ∣ n i ∈ Node , i = 1 , 2 , … D={n_i\mid n_i\in\text{Node},i=1,2,\dots} D=ni∣ni∈Node,i=1,2,…
数据关系: R = ⟨ n i , n j ⟩ ∣ a i , a i + 1 ∈ D , i = 1 , 2 , . . . , n − 1 R={\langle n_i,n_j\rangle|a_i,a_{i+1}∈D,i=1,2,...,n-1} R=⟨ni,nj⟩∣ai,ai+1∈D,i=1,2,...,n−1
基本操作:
1. __init__():初始化空树
2. is_empty():判断树是否为空
3. add_root(data):创建根节点
4. add_child(parent, data):在指定父节点下添加子节点
5. remove(node):删除指定节点及其子树
6. traverse_preorder(mode):按指定模式遍历树
7. find(data):查找值为 data 的节点
1.3 树的应用场景
树具有广泛的应用场景,例如:
- 文件系统:目录与文件的层次管理
XML/JSON
解析:数据以树状结构存储- 编译原理:抽象语法树用于表达式和程序结构
- 数据库索引:如 B 树、B+ 树、红黑树等
- 路由表和决策树:网络分组转发与机器学习
2. 树的实现
树的存储方式主要有两种:链式存储(每个节点保存对其子节点的引用)和顺序存储(使用列表保存所有节点及其父节点索引)。
2.1 链式存储实现
树的链式存储中,每个节点包含数据和指向其子节点的指针。使用一个列表 (children
) 来存储子节点。每个节点可能有多个子节点,因此我们使用一个列表来保存这些子节点的引用:
class ChainNode:def __init__(self, data):self.data = dataself.children = [] # 存放子节点引用def __str__(self):return str(self.data)
2.1.1 树的初始化
初始化空树,即将根节点设置为 None
:
class Tree:def __init__(self):self.root = None
2.1.2 树判空
判断树是否为空,当根节点为 None
时,树为空:
def is_empty(self):return self.root is None
2.1.3 创建根节点
创建根节点,若已有根则抛出异常;返回新建的根节点引用:
def add_root(self, data):if self.root is not None:raise Exception("根节点已存在")self.root = ChainNode(data)return self.root
2.1.4 创建子节点
在指定父节点下添加子节点,父节点不存在时抛出异常;返回新建的子节点引用:
def add_child(self, parent, data):if parent is None:raise Exception("父节点不存在")node = ChainNode(data)parent.children.append(node)return node
2.1.5 删除指定节点
删除指定节点及其整个子树,若删除根则将树置为空:
def remove(self, node):if self.root is None or node is None:returnif node is self.root:self.root = Nonereturndef _remove_from_parent(cur, target):for child in cur.children:if child is target:cur.children.remove(child)return Trueif _remove_from_parent(child, target):return Truereturn False_remove_from_parent(self.root, node)
2.1.6 遍历树
按指定模式遍历树,支持前序 (preorder
)、后序 (postorder
) 和层序 (levelorder
):
前序遍历:也称深度优先遍历,首先访问根节点,然后递归依次遍历各子树
后序遍历:首先对节点的各子树依次递归遍历,然后访问根节点
层序遍历:也成广度优先遍历,按层次从上至下、从左至右依次访问节点
def traverse(self, mode='preorder'):if self.root is None:return []result = []if mode == 'preorder':def dfs_pre(node):result.append(node.data)for ch in node.children:dfs_pre(ch)dfs_pre(self.root)elif mode == 'postorder':def dfs_post(node):for ch in node.children:dfs_post(ch)result.append(node.data)dfs_post(self.root)elif mode == 'levelorder':from collections import dequeq = deque([self.root])while q:cur = q.popleft()result.append(cur.data)for ch in cur.children:q.append(ch)else:raise ValueError("不支持的遍历模式:" + mode)return result
2.1.7 查找
查找值为 data
的节点,返回第一个匹配的节点引用,否则返回 None
:
def find(self, data):def dfs(node):if node.data == data:return nodefor ch in node.children:found = dfs(ch)if found:return foundreturn Noneif self.root is None:return Nonereturn dfs(self.root)
2.2 顺序存储实现
在树的顺序存储中,我们通常使用数组(或列表)来存储树的节点信息,并通过节点的父子关系来定位每个节点的位置,每个节点的子节点在数组中的位置是连续的。
2.2.1 树的初始化
初始化空树,数据和值列表均为空:
class SeqTree:def __init__(self):self.data_list = []self.parent_list = []
2.2.2 判树空
判断树是否为空,当 data_list
为空时返回 True
:
def is_empty(self):return len(self.data_list) == 0
2.2.3 创建根节点
创建根节点,将 data_list
添加根值,parent_list
添加 -1
;返回根节点索引:
def add_root(self, data):if not self.is_empty():raise Exception("根已存在")self.data_list.append(data)self.parent_list.append(-1)return 0
2.2.4 创建子节点
在指定父节点索引下添加子节点,索引无效时抛出异常;返回新节点索引:
def add_child(self, parent_idx, data):if parent_idx < 0 or parent_idx >= len(self.data_list):raise IndexError("父节点索引无效")idx = len(self.data_list)self.data_list.append(data)self.parent_list.append(parent_idx)return idx
2.2.5 删除指定节点
删除指定节点及其子树,先收集所有待删除节点索引,再降序删除并修正父索引:
def remove(self, idx):if idx < 0 or idx >= len(self.data_list):returnto_del = set()def dfs_del(i):to_del.add(i)for j, p in enumerate(self.parent_list):if p == i:dfs_del(j)dfs_del(idx)for i in sorted(to_del, reverse=True):self.data_list.pop(i)self.parent_list.pop(i)self.parent_list = [p-1 if p > i else p for p in self.parent_list]
2.6 遍历树
按指定模式遍历树,支持 preorder
、postorder
和 levelorder
:
def traverse(self, mode='preorder'):if self.is_empty():return []result = []if mode == 'preorder':def dfs(i):result.append(self.data_list[i])for j, p in enumerate(self.parent_list):if p == i:dfs(j)dfs(0)elif mode == 'postorder':def dfs(i):for j, p in enumerate(self.parent_list):if p == i:dfs(j)result.append(self.data_list[i])dfs(0)elif mode == 'levelorder':from collections import dequeq = deque([0])while q:i = q.popleft()result.append(self.data_list[i])for j, p in enumerate(self.parent_list):if p == i:q.append(j)else:raise ValueError("不支持的遍历模式:" + mode)return result
2.7 查找
查找值为 data
的节点,返回第一个匹配的索引,否则返回 None
:
def find(self, data):for i, d in enumerate(self.data_list):if d == data:return ireturn None
2.3 实现对比
特性 | 链式存储 | 顺序存储 |
---|---|---|
存储结构 | 节点对象 + 动态列表 | Python 列表 |
插入 | O ( 1 ) O(1) O(1) | O ( 1 ) O(1) O(1),但可能触发扩容复制 |
删除 | O ( n ) O(n) O(n) | O ( n ) O(n) O(n) |
遍历 | 递归/迭代 | 索引计算 |
空间利用率 | 与节点个数成正比 | 需预留/扩容,存在浪费 |
3. 树的应用
3.1 链树
首先初始化一个链树,然后测试相关操作:
tree = ChainTree()
r = tree.add_root('A')
b = tree.add_child(r, 'B')
c = tree.add_child(r, 'C')
tree.add_child(b, 'D')
tree.add_child(b, 'E')
print(tree.traverse('preorder'))
print(tree.find('E').data)
tree.remove(b)
print(tree.traverse('levelorder'))
输出结果如下所示:
['A', 'B', 'D', 'E', 'C']
E
['A', 'C']
3.2 顺序树
初始化一个顺序树,然后测试相关操作:
st = SeqTree()
r_idx = st.add_root('A')
b_idx = st.add_child(r_idx, 'B')
c_idx = st.add_child(r_idx, 'C')
st.add_child(b_idx, 'D')
st.add_child(b_idx, 'E')
print(st.traverse('postorder'))
print(st.find('C'))
st.remove(b_idx)
print(st.traverse('levelorder'))
输出结果如下所示:
['D', 'E', 'B', 'C', 'A']
2
['A', 'C']
小结
本节系统地介绍了树的基本术语和结构,包括节点、边、根节点、父子关系、度、深度和高度等概念,建立对树结构的清晰认知。随后,通过详细的代码,展示了树的构建及其常用的遍历方法,包括前序、后序和层次遍历。