lec7 Trees(3)

又回到了树,依然是只记录比较重要的部分,其他就不记录了。😎

树的遍历

前序遍历(Preorder Traversal)过程

前序遍历是树的一种常见遍历方式,其遍历顺序为:

  1. 访问根节点
  2. 对根节点的每一个子树(从左到右)递归地进行前序遍历。

前序遍历的递归过程:

前序遍历的算法可以通过递归方式实现,以下是前序遍历的伪代码:

1
2
3
4
5
6
7
8
Procedure preorder (T: ordered rooted tree)
r := root of T // 获取树 T 的根节点
List r // 访问并输出根节点 r
For each child c of r from left to right // 从左到右遍历每一个子节点
Begin
T(c) := subtree with c as its root // 以 c 为根的子树
preorder(T(c)) // 递归地进行子树的前序遍历
End

解释:

  • 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) $ 进行前序遍历。

前序遍历的流程:

  1. 访问树的根节点。
  2. 然后递归访问每个子树(从左到右)。

这种遍历方法的特点是,根节点总是被先访问,然后是其所有的子节点。

image-20241121131022728
image-20241121131050966

中序遍历(Inorder Traversal)过程

中序遍历是一种树的遍历方式,其遍历顺序为:

  1. 先遍历 左子树
  2. 然后 访问根节点
  3. 最后遍历 右子树

对于具有多个子树的树,中序遍历会依次遍历每一个子树,在遍历每个子树后再访问根节点。

中序遍历的定义:

  1. 如果树 T 只包含根节点 r,那么该树的中序遍历就是节点 r 本身。
  2. 如果树 T 包含多个子树,设根节点 $ r $ 的子树为 $ T_1, T_2, , T_n $,从左到右的顺序排列。
    • 中序遍历的过程如下:
      1. 先遍历子树 $ T_1 $ 的中序遍历。
      2. 然后访问根节点 $ r $。
      3. 接着遍历子树 $ T_2 $ 的中序遍历,依此类推,直到遍历完所有子树。

中序遍历的递归过程(伪代码):

1
2
3
4
5
6
7
8
9
10
11
12
Procedure inorder (T: ordered rooted tree)
r := root of T // 获取树 T 的根节点
if r is a leaf then list r // 如果根节点是叶节点,直接访问并输出根节点
Else Begin
l := first child of r from left to right // 获取根节点的第一个子节点
T(l) := subtree with l as its root // 将第一个子节点 l 作为新树的根
inorder(T(l)) // 对子树 T(l) 进行中序遍历
list r // 访问并输出根节点 r
for each child c of r except for l from left to right // 遍历根节点的其他子节点
T(c) := subtree with c as its root // 将 c 作为新树的根
inorder(T(c)) // 对子树 T(c) 进行中序遍历
End

解释:

  1. r := root of T:获取树 $ T $ 的根节点 $ r $。
  2. if r is a leaf then list r:如果根节点 $ r $ 是叶节点(即没有子节点),直接输出根节点 $ r $。
  3. l := first child of r from left to right:获取根节点 $ r $ 的第一个子节点。
  4. T(l) := subtree with l as its root:以子节点 $ l $ 为根,形成一个子树 $ T(l) $。
  5. inorder(T(l)):递归地对子树 $ T(l) $ 进行中序遍历。
  6. list r:在遍历完左子树后,访问并输出根节点 $ r $。
  7. for each child c of r except for l from left to right:遍历根节点 $ r $ 的其余子节点(不包括第一个子节点 $ l $)。
  8. inorder(T(c)):递归地对每个子树 $ T(c) $ 进行中序遍历。

中序遍历的流程:

  1. 如果当前节点是叶节点,直接输出该节点。
  2. 否则:
    • 首先递归遍历根节点的第一个子树。
    • 然后输出根节点。
    • 最后递归遍历根节点的剩余子树。
image-20241121131231047

后序遍历(Postorder Traversal)过程

后序遍历是一种树的遍历方式,其遍历顺序为:

  1. 先遍历 左子树
  2. 然后遍历 右子树
  3. 最后访问 根节点

对于有多个子树的树,后序遍历会依次遍历每个子树,在遍历完所有子树后再访问根节点。

后序遍历的定义:

  1. 如果树 T 只包含根节点 r,那么该树的后序遍历就是节点 r 本身。
  2. 如果树 T 包含多个子树,设根节点 $ r $ 的子树为 $ T_1, T_2, , T_n $,从左到右的顺序排列。
    • 后序遍历的过程如下:
      1. 先遍历子树 $ T_1 $ 的后序遍历。
      2. 接着遍历子树 $ T_2 $ 的后序遍历,依此类推,直到遍历完所有子树。
      3. 最后访问根节点 $ r $。

后序遍历的递归过程(伪代码):

1
2
3
4
5
6
7
8
Procedure postorder (T: ordered rooted tree)
r := root of T // 获取树 T 的根节点
For each child c of r from left to right // 遍历根节点 r 的每个子节点
Begin
T(c) := subtree with c as its root // 将子节点 c 作为新树的根
postorder(T(c)) // 递归地对子树 T(c) 进行后序遍历
End
list r // 在遍历完所有子树后,访问并输出根节点 r

解释:

  1. r := root of T:获取树 $ T $ 的根节点 $ r $。
  2. For each child c of r from left to right:从左到右遍历根节点 $ r $ 的每个子节点。
  3. T(c) := subtree with c as its root:将当前子节点 $ c $ 作为新树的根,形成一个子树 $ T(c) $。
  4. postorder(T(c)):递归地对子树 $ T(c) $ 进行后序遍历。
  5. list r:在遍历完所有子树之后,访问并输出根节点 $ r $。
image-20241121131506683

表达式树

image-20241121131600667
image-20241121131719305
image-20241121131738085

前缀表达式的求值

1. 前缀表达式的定义

前缀表达式(也称为波兰表示法,Polish Notation)是一种将操作符放在操作数前面的数学表达式表示方法。在这种表示法中,运算顺序由操作符的位置决定,而不依赖于括号或运算符的优先级。前缀表达式的一个重要特点是没有括号,运算的顺序完全由操作符的顺序确定。

2. 前缀表达式求值方法

我们通过从右到左遍历前缀表达式来求值:

  • 如果当前字符是操作数(如数字),则将其压入操作数栈。
  • 如果当前字符是操作符(如 +, -, *, /, ),则从操作数栈中弹出相应的操作数,进行运算后将结果压回栈中。
  • 最终栈中剩下的唯一数就是表达式的结果。

3. 给定前缀表达式:

前缀表达式为:

1
+ - * 2 3 5 / ↑ 2 3 4

我们将按顺序从右到左遍历该表达式并求值:

  • 从右到左开始,遇到操作符 +,表示接下来要进行加法运算。
  • + 之前是 -,表示先进行减法运算。
  • - 之前是 *,表示先进行乘法运算。
image-20241121132450744

后缀表达式(逆波兰表示法)

1. 后缀表达式的定义

后缀表达式(Reverse Polish Notation,简称 RPN)是另一种没有括号的数学表达式表示法,在这种表示法中,操作符放在操作数后面。例如,表达式 (2 * 3) - 5 对应的后缀表达式是 2 3 * 5 -。后缀表达式通过栈进行求值,运算顺序由操作符的顺序决定,且不需要括号来表示优先级。

2. 后缀表达式的求值方法

  • 如果遇到操作数(数字),则将其压入栈中。
  • 如果遇到操作符,则从栈中弹出相应数量的操作数,进行运算并将结果压回栈中。
  • 最终栈中只剩一个元素时,结果即为表达式的值。
image-20241121132502814

例子

image-20241121132610493
image-20241121135106784
image-20241121135135463

证明:简单图有且仅有在它存在生成树时是连通的

1. 问题定义

一个简单图 $ G $ 连通 的充要条件是:它存在一个 生成树。生成树是一个包含所有顶点的无环连通子图。


2. 充分性证明:如果 $ G $ 存在生成树 $ T $,则 $ G $ 连通

假设:简单图 $ G $ 存在一个生成树 $ T $。

  • $ T $ 是 $ G $ 的一个子图,且 $ T $ 包含 $ G $ 中的所有顶点。
  • 由于 $ T $ 是树,在 $ T $ 中,任意两个顶点之间存在唯一一条路径(树的性质)。
  • 因为 $ T $ 是 $ G $ 的子图,$ T $ 中的路径也是 $ G $ 中的路径。

结论:在 $ G $ 中任意两个顶点之间都存在路径,说明 $ G $ 是连通图。


3. 必要性证明:如果 $ G $ 是连通图,则 $ G $ 存在生成树

假设:简单图 $ G $ 是连通图。

  1. 如果 $ G $ 是一棵树,则 $ G $ 本身就是其生成树。

  2. 如果 $ G $ 不是树,则 $ G $ 必定包含至少一个简单回路(因为树是无环图)。

    • 从 $ G $ 中去掉回路中的一条边,得到一个子图。
    • 新的子图仍然连通(见以下论证)。
  3. 去掉边后仍然连通的证明

    • 假设在原图 $ G $ 中,两顶点 $ u $ 和 $ v $ 通过一条路径连接,这条路径中包含被移除的边 $ e $。
    • 因为 $ e $ 属于一个简单回路,去掉 $ e $ 后,仍存在另一条路径可以绕过该回路,将 $ u $ 和 $ v $ 连通。
  4. 迭代去掉边

    • 重复上述过程,逐步移除所有简单回路的边。
    • 因为图中只有有限条边,该过程会在有限步内结束。
    • 最终得到一个无环连通子图,即生成树。
  5. 生成树的性质

    • 该子图仍然包含所有顶点(连通性确保每个顶点仍被包含)。
    • 因为所有简单回路被移除,该子图无环。
    • 因此,该子图是一棵生成树。

深度优先搜索算法构造生成树的步骤与解析

1. 算法目的

通过深度优先搜索 (Depth-First Search, DFS) 的方法在图中找到一个 生成树,即一个连通图的无环子图,包含图中的所有顶点。

2. 算法步骤

步骤 1:随机选择一个顶点作为起点(根节点)

  • 在图中任意选择一个顶点,将其作为搜索的起点。

步骤 2:从起点开始逐步延伸路径

  • 沿着一条路径依次添加顶点和边。
  • 每次选择一条边,该边与当前路径中的最后一个顶点相连,并连接到一个未访问过的顶点。

步骤 3:尽可能延长路径

  • 不断重复 步骤 2,将新的顶点和边加入路径,直到不能再找到未访问的顶点。

步骤 4:检查路径是否覆盖所有顶点

  • 如果当前路径经过了图中的所有顶点,则该路径构成了一棵生成树,算法结束。

步骤 5:如果未覆盖所有顶点

  • 如果还有未访问的顶点,需要将它们加入路径。
  • 回退到当前路径中的倒数第二个顶点,并尝试从该顶点开始,寻找另一条通向未访问顶点的新路径。

步骤 6:继续回退并尝试新的路径

  • 如果从倒数第二个顶点也无法继续延伸路径,则继续向回回退到更早的顶点,尝试寻找新的未访问顶点。

步骤 7:重复上述过程

  • 不断回退并尝试新路径,直到所有顶点都被访问为止。
  • 最终生成的路径和边形成一棵生成树。

3. 算法特点

  1. 递归性质:深度优先搜索是一种递归算法,在当前顶点无法继续时,会回溯到之前的顶点,尝试其他可能路径。
  2. 生成树的构造:DFS 的路径和边自然形成生成树,因为 DFS 遍历过程中不会重复访问已访问过的顶点,从而避免了环的产生。
  3. 适用范围:DFS 适用于连通图,确保所有顶点都可以被访问到。
image-20241121140311699
image-20241121140340654

广度优先搜索算法构造生成树的步骤与解析


1. 算法目的

通过广度优先搜索 (Breadth-First Search, BFS) 的方法在图中找到一个 生成树,即一个连通图的无环子图,包含图中的所有顶点。


2. 算法步骤

步骤 1:随机选择一个顶点作为起点(根节点)

  • 在图中任意选择一个顶点,将其作为生成树的根节点。

步骤 2:添加与根节点相连的边

  • 找到与根节点直接相连的边,将其加入生成树。
  • 新增的顶点成为 第 1 层 的顶点,并将它们按任意顺序排列。

步骤 3:遍历第 1 层的顶点并添加边

  • 依次遍历 第 1 层 的每个顶点。
  • 对于每个顶点,添加与其相连的边,只要添加的边不会形成回路。
  • 添加的新的顶点成为 第 2 层 的顶点,并将它们按任意顺序排列。

步骤 4:继续添加边并生成下一层

  • 第 2 层 的顶点重复步骤 3,找到与这些顶点相连的边,确保不会形成回路,并将新增顶点加入 第 3 层

步骤 5:重复上述过程

  • 按层次遍历,逐层添加边和顶点,直到所有顶点都被加入生成树。

步骤 6:终止条件

  • 由于图中的边和顶点是有限的,算法会在所有顶点都被访问后终止。

步骤 7:生成树完成

  • 通过上述过程构造的子图是一个包含所有顶点的无环连通子图,即 生成树

3. 算法特点

  1. 分层遍历:BFS 是一种逐层向外扩展的搜索方法,每次处理一个层级的所有顶点,然后再处理下一层的顶点。
  2. 无环性:BFS 只会添加与未访问顶点相连的边,避免了回路的产生。
  3. 生成树的构造:BFS 的分层访问和边的选择过程自然形成生成树。

image-20241121140046002
image-20241121140053395
image-20241121140230533
image-20241121140354930
image-20241121140407344

最小生成树

在连通加权图中,最小生成树(Minimum Spanning Tree,MST)是指包含图中所有顶点且边权和最小的生成树。 构造最小生成树的两种经典算法是:

  1. Prim算法:适用于稠密图,逐步扩展已构建的生成树,通过选择与当前树连接且权值最小的边来添加新顶点。

  2. Kruskal算法:适用于稀疏图,按照边的权值从小到大排序,依次选择不会形成环的边,直至构建出包含所有顶点的生成树。

image-20241121141248063
image-20241121141320813