lec7 Trees(3)
lec7 Trees(3)
又回到了树,依然是只记录比较重要的部分,其他就不记录了。😎
树的遍历
前序遍历(Preorder Traversal)过程
前序遍历是树的一种常见遍历方式,其遍历顺序为:
- 访问根节点。
- 对根节点的每一个子树(从左到右)递归地进行前序遍历。
前序遍历的递归过程:
前序遍历的算法可以通过递归方式实现,以下是前序遍历的伪代码:
1 | Procedure preorder (T: ordered rooted tree) |
解释:
- r := root of T:首先获取树 $ T $ 的根节点 $ r $。
- List r:访问并记录根节点 $ r $。
- For each child c of r from left to right:遍历根节点 $ r $ 的每一个子节点,遍历顺序是从左到右。
- T(c) := subtree with c as its root:将每一个子节点 $ c $ 作为新树的根,递归遍历子树 $ T(c) $。
- preorder(T(c)):对以 $ c $ 为根的子树 $ T(c) $ 进行前序遍历。
前序遍历的流程:
- 访问树的根节点。
- 然后递归访问每个子树(从左到右)。
这种遍历方法的特点是,根节点总是被先访问,然后是其所有的子节点。
中序遍历(Inorder Traversal)过程
中序遍历是一种树的遍历方式,其遍历顺序为:
- 先遍历 左子树。
- 然后 访问根节点。
- 最后遍历 右子树。
对于具有多个子树的树,中序遍历会依次遍历每一个子树,在遍历每个子树后再访问根节点。
中序遍历的定义:
- 如果树 T 只包含根节点 r,那么该树的中序遍历就是节点 r 本身。
- 如果树 T 包含多个子树,设根节点 $ r $ 的子树为 $
T_1, T_2, , T_n $,从左到右的顺序排列。
- 中序遍历的过程如下:
- 先遍历子树 $ T_1 $ 的中序遍历。
- 然后访问根节点 $ r $。
- 接着遍历子树 $ T_2 $ 的中序遍历,依此类推,直到遍历完所有子树。
- 中序遍历的过程如下:
中序遍历的递归过程(伪代码):
1 | Procedure inorder (T: ordered rooted tree) |
解释:
- r := root of T:获取树 $ T $ 的根节点 $ r $。
- if r is a leaf then list r:如果根节点 $ r $ 是叶节点(即没有子节点),直接输出根节点 $ r $。
- l := first child of r from left to right:获取根节点 $ r $ 的第一个子节点。
- T(l) := subtree with l as its root:以子节点 $ l $ 为根,形成一个子树 $ T(l) $。
- inorder(T(l)):递归地对子树 $ T(l) $ 进行中序遍历。
- list r:在遍历完左子树后,访问并输出根节点 $ r $。
- for each child c of r except for l from left to right:遍历根节点 $ r $ 的其余子节点(不包括第一个子节点 $ l $)。
- inorder(T(c)):递归地对每个子树 $ T(c) $ 进行中序遍历。
中序遍历的流程:
- 如果当前节点是叶节点,直接输出该节点。
- 否则:
- 首先递归遍历根节点的第一个子树。
- 然后输出根节点。
- 最后递归遍历根节点的剩余子树。
后序遍历(Postorder Traversal)过程
后序遍历是一种树的遍历方式,其遍历顺序为:
- 先遍历 左子树。
- 然后遍历 右子树。
- 最后访问 根节点。
对于有多个子树的树,后序遍历会依次遍历每个子树,在遍历完所有子树后再访问根节点。
后序遍历的定义:
- 如果树 T 只包含根节点 r,那么该树的后序遍历就是节点 r 本身。
- 如果树 T 包含多个子树,设根节点 $ r $ 的子树为 $
T_1, T_2, , T_n $,从左到右的顺序排列。
- 后序遍历的过程如下:
- 先遍历子树 $ T_1 $ 的后序遍历。
- 接着遍历子树 $ T_2 $ 的后序遍历,依此类推,直到遍历完所有子树。
- 最后访问根节点 $ r $。
- 后序遍历的过程如下:
后序遍历的递归过程(伪代码):
1 | Procedure postorder (T: ordered rooted tree) |
解释:
- r := root of T:获取树 $ T $ 的根节点 $ r $。
- For each child c of r from left to right:从左到右遍历根节点 $ r $ 的每个子节点。
- T(c) := subtree with c as its root:将当前子节点 $ c $ 作为新树的根,形成一个子树 $ T(c) $。
- postorder(T(c)):递归地对子树 $ T(c) $ 进行后序遍历。
- list r:在遍历完所有子树之后,访问并输出根节点 $ r $。
表达式树
前缀表达式的求值
1. 前缀表达式的定义
前缀表达式(也称为波兰表示法,Polish Notation)是一种将操作符放在操作数前面的数学表达式表示方法。在这种表示法中,运算顺序由操作符的位置决定,而不依赖于括号或运算符的优先级。前缀表达式的一个重要特点是没有括号,运算的顺序完全由操作符的顺序确定。
2. 前缀表达式求值方法
我们通过从右到左遍历前缀表达式来求值:
- 如果当前字符是操作数(如数字),则将其压入操作数栈。
- 如果当前字符是操作符(如
+
,-
,*
,/
,↑
),则从操作数栈中弹出相应的操作数,进行运算后将结果压回栈中。 - 最终栈中剩下的唯一数就是表达式的结果。
3. 给定前缀表达式:
前缀表达式为:
1 | + - * 2 3 5 / ↑ 2 3 4 |
我们将按顺序从右到左遍历该表达式并求值:
- 从右到左开始,遇到操作符
+
,表示接下来要进行加法运算。 +
之前是-
,表示先进行减法运算。-
之前是*
,表示先进行乘法运算。
后缀表达式(逆波兰表示法)
1. 后缀表达式的定义
后缀表达式(Reverse Polish Notation,简称
RPN)是另一种没有括号的数学表达式表示法,在这种表示法中,操作符放在操作数后面。例如,表达式
(2 * 3) - 5
对应的后缀表达式是
2 3 * 5 -
。后缀表达式通过栈进行求值,运算顺序由操作符的顺序决定,且不需要括号来表示优先级。
2. 后缀表达式的求值方法
- 如果遇到操作数(数字),则将其压入栈中。
- 如果遇到操作符,则从栈中弹出相应数量的操作数,进行运算并将结果压回栈中。
- 最终栈中只剩一个元素时,结果即为表达式的值。
例子
证明:简单图有且仅有在它存在生成树时是连通的
1. 问题定义
一个简单图 $ G $ 连通 的充要条件是:它存在一个 生成树。生成树是一个包含所有顶点的无环连通子图。
2. 充分性证明:如果 $ G $ 存在生成树 $ T $,则 $ G $ 连通
假设:简单图 $ G $ 存在一个生成树 $ T $。
- $ T $ 是 $ G $ 的一个子图,且 $ T $ 包含 $ G $ 中的所有顶点。
- 由于 $ T $ 是树,在 $ T $ 中,任意两个顶点之间存在唯一一条路径(树的性质)。
- 因为 $ T $ 是 $ G $ 的子图,$ T $ 中的路径也是 $ G $ 中的路径。
结论:在 $ G $ 中任意两个顶点之间都存在路径,说明 $ G $ 是连通图。
3. 必要性证明:如果 $ G $ 是连通图,则 $ G $ 存在生成树
假设:简单图 $ G $ 是连通图。
如果 $ G $ 是一棵树,则 $ G $ 本身就是其生成树。
如果 $ G $ 不是树,则 $ G $ 必定包含至少一个简单回路(因为树是无环图)。
- 从 $ G $ 中去掉回路中的一条边,得到一个子图。
- 新的子图仍然连通(见以下论证)。
去掉边后仍然连通的证明:
- 假设在原图 $ G $ 中,两顶点 $ u $ 和 $ v $ 通过一条路径连接,这条路径中包含被移除的边 $ e $。
- 因为 $ e $ 属于一个简单回路,去掉 $ e $ 后,仍存在另一条路径可以绕过该回路,将 $ u $ 和 $ v $ 连通。
迭代去掉边:
- 重复上述过程,逐步移除所有简单回路的边。
- 因为图中只有有限条边,该过程会在有限步内结束。
- 最终得到一个无环连通子图,即生成树。
生成树的性质:
- 该子图仍然包含所有顶点(连通性确保每个顶点仍被包含)。
- 因为所有简单回路被移除,该子图无环。
- 因此,该子图是一棵生成树。
深度优先搜索算法构造生成树的步骤与解析
1. 算法目的
通过深度优先搜索 (Depth-First Search, DFS) 的方法在图中找到一个 生成树,即一个连通图的无环子图,包含图中的所有顶点。
2. 算法步骤
步骤 1:随机选择一个顶点作为起点(根节点)
- 在图中任意选择一个顶点,将其作为搜索的起点。
步骤 2:从起点开始逐步延伸路径
- 沿着一条路径依次添加顶点和边。
- 每次选择一条边,该边与当前路径中的最后一个顶点相连,并连接到一个未访问过的顶点。
步骤 3:尽可能延长路径
- 不断重复 步骤 2,将新的顶点和边加入路径,直到不能再找到未访问的顶点。
步骤 4:检查路径是否覆盖所有顶点
- 如果当前路径经过了图中的所有顶点,则该路径构成了一棵生成树,算法结束。
步骤 5:如果未覆盖所有顶点
- 如果还有未访问的顶点,需要将它们加入路径。
- 回退到当前路径中的倒数第二个顶点,并尝试从该顶点开始,寻找另一条通向未访问顶点的新路径。
步骤 6:继续回退并尝试新的路径
- 如果从倒数第二个顶点也无法继续延伸路径,则继续向回回退到更早的顶点,尝试寻找新的未访问顶点。
步骤 7:重复上述过程
- 不断回退并尝试新路径,直到所有顶点都被访问为止。
- 最终生成的路径和边形成一棵生成树。
3. 算法特点
- 递归性质:深度优先搜索是一种递归算法,在当前顶点无法继续时,会回溯到之前的顶点,尝试其他可能路径。
- 生成树的构造:DFS 的路径和边自然形成生成树,因为 DFS 遍历过程中不会重复访问已访问过的顶点,从而避免了环的产生。
- 适用范围:DFS 适用于连通图,确保所有顶点都可以被访问到。
广度优先搜索算法构造生成树的步骤与解析
1. 算法目的
通过广度优先搜索 (Breadth-First Search, BFS) 的方法在图中找到一个 生成树,即一个连通图的无环子图,包含图中的所有顶点。
2. 算法步骤
步骤 1:随机选择一个顶点作为起点(根节点)
- 在图中任意选择一个顶点,将其作为生成树的根节点。
步骤 2:添加与根节点相连的边
- 找到与根节点直接相连的边,将其加入生成树。
- 新增的顶点成为 第 1 层 的顶点,并将它们按任意顺序排列。
步骤 3:遍历第 1 层的顶点并添加边
- 依次遍历 第 1 层 的每个顶点。
- 对于每个顶点,添加与其相连的边,只要添加的边不会形成回路。
- 添加的新的顶点成为 第 2 层 的顶点,并将它们按任意顺序排列。
步骤 4:继续添加边并生成下一层
- 对 第 2 层 的顶点重复步骤 3,找到与这些顶点相连的边,确保不会形成回路,并将新增顶点加入 第 3 层。
步骤 5:重复上述过程
- 按层次遍历,逐层添加边和顶点,直到所有顶点都被加入生成树。
步骤 6:终止条件
- 由于图中的边和顶点是有限的,算法会在所有顶点都被访问后终止。
步骤 7:生成树完成
- 通过上述过程构造的子图是一个包含所有顶点的无环连通子图,即 生成树。
3. 算法特点
- 分层遍历:BFS 是一种逐层向外扩展的搜索方法,每次处理一个层级的所有顶点,然后再处理下一层的顶点。
- 无环性:BFS 只会添加与未访问顶点相连的边,避免了回路的产生。
- 生成树的构造:BFS 的分层访问和边的选择过程自然形成生成树。
最小生成树
在连通加权图中,最小生成树(Minimum Spanning Tree,MST)是指包含图中所有顶点且边权和最小的生成树。 构造最小生成树的两种经典算法是:
Prim算法:适用于稠密图,逐步扩展已构建的生成树,通过选择与当前树连接且权值最小的边来添加新顶点。
Kruskal算法:适用于稀疏图,按照边的权值从小到大排序,依次选择不会形成环的边,直至构建出包含所有顶点的生成树。