SlideShare a Scribd company logo
1 of 52
7. 3 图的遍历
Tra ve rs ing G ra p h
  从图中某个顶点出发游历图,访遍
图中其余顶点,并且使图中的每个顶点
仅被访问一次的过程。图的遍历算法是
求解图的连通性问题 , 拓扑排序和求关
键路径等算法的基础 .
7.3 图的遍历
深度优先搜索

  广度优先搜索
    遍历应用举例
一、深度优先搜索遍历图

 连通图的深度优先搜索遍历

   从图中某个顶点 V0 出发,访问
此顶点,然后依次从 V0 的各个未被访
问的邻接点出发深度优先搜索遍历图
,直至图中所有和 V0 有路径相通的顶
点都被访问到。
V               W1 、 W2 和 W3 均为
                        V 的邻接点, SG1 、
 w1    w2         w3
                        SG2 和 SG3 分别为含
SG1         SG2   SG3   顶点 W1 、 W2 和 W3
                        的子图。
访问顶点 V :
for (W1 、 W2 、 W3 )
      若该邻接点 W 未被访问,
      则从它出发进行深度优先搜索遍历。
从上页的图解可见 :

 1. 从深度优先搜索遍历连通图的
 过程类似于树的先根遍历;

2. 如何判别 V 的邻接点是否被访问?

 解决的办法是:为每个顶点设立
 一个 “ 访问标志 visited[w]” 。
例如 :                   0
                           a
       2           3           4               5
       c           d               e               f

           1   b                   6       g
               0   1       2           3       4       5   6
访问标志 : F F F F F T T
       T T T T T F F

访问次序 : a c b d g f e
void DFS(Graph G, int v) {
  // 从顶点 v 出发,深度优先搜索遍历连通
图 G
 visited[v] = TRUE; VisitFunc(v);
 for(w=FirstAdjVex(G, v);
        w!=0; w=NextAdjVex(G,v,w))
    if (!visited[w]) DFS(G, w);
         // 对 v 的尚未访问的邻接顶点 w
         // 递归调用 DFS
非连通图的深度优先搜索遍历

   首先将图中每个顶点的访问标志
设为 FALSE, 之后搜索图中每个顶点
,如果未被访问,则以该顶点为起始点
,进行深度优先搜索遍历,否则继续检
查下一顶点。
例如 :                   0                   1
                           a                   b       6
                                                           g
       2           3           4               5
       c           d               e               f

           7   h                   8       k
               0   1       2           3       4   5   6       7   8
访问标志 : F F F F F T T T F
       T T T T T F F F T

访问次序 : a c h d k f e b g
void DFSTraverse(Graph G,
                Status (*Visit)(int v)) {
 // 对图 G 作深度优先遍历。
 VisitFunc = Visit;
 for (v=0; v<G.vexnum; ++v)
   visited[v] = FALSE; // 访问标志数组初始
化
 for (v=0; v<G.vexnum; ++v)
   if (!visited[v]) DFS(G, v);
        // 对尚未访问的顶点调用 DFS
二、广度优先搜索遍历图
                        对连通图,从起始点 V 到
              w2        其余各顶点必定存在路径。
w1
                        其中, V->w1, V->w2, V->w8
          V         w3
                                     的路径长度为 1 ;
w7                             V->w7, V->w3, V->w5
              w8
                         w4       的路径长度为 2 ;
     w6
                   w5          V->w6, V->w4
                                 的路径长度为 3 。
从图中的某个顶点 V0 出发,并在访问
此顶点之后依次访问 V0 的所有未被访问过
的邻接点,之后分别从这些邻接点出发依
次访问它们的邻接点,并使“ 先被访问的顶
点的邻接点 ” 先于 “ 后被访问的顶点的邻接
点 ” 被访问,直至图中所有和 V0 有路径相通
    若此时图中尚有顶点未被访问,则另选
的顶点都被访问到。
图中一个未曾被访问的顶点作起始点,重
复上述过程,直至图中所有顶点都被访问
到为止。
0    1    2   3   4   5   6   7   8
          Visited       F F F F F T T T F
                        T T T T T F F F T

              w2             Q
w1                               w1 w2 w8 w7 w3 w5 w6 w4
                                 V
          V         w3
w7                               访问次序 :
              w8
                            w4
     w6
                   w5
void BFSTraverse(Graph G,
                 Status (*Visit)(int v)){
 for (v=0; v<G.vexnum; ++v)
   visited[v] = FALSE; // 初始化访问标
志
 InitQueue(Q);         // 置空的辅助队列 Q
 for ( v=0; v<G.vexnum; ++v )
   if … …
      ( !visited[v]) {      // v 尚未访问

    }
visited[u] = TRUE; Visit(u); // 访问 u
EnQueue(Q, v);            // v 入队列
while (!QueueEmpty(Q)) {
  DeQueue(Q, u);
                    // 队头元素出队并置为 u
  for(w=FirstAdjVex(G, u); w!=0;
                    w=NextAdjVex(G,u,w))
     if ( ! visited[w]) {
        visited[w]=TRUE; Visit(w);
        EnQueue(Q, w); // 访问的顶点 w 入队列
     } // if
} // while
图的连通性问题
连通分量 (Connected
                  A                 B
component)
   当无向图为非连通图
                          C D   E
时 , 从图中某一顶点出发
                      F    G    H
, 利用深度优先搜索算法
或广度优先搜索算法不            I         K
                            J
可能遍历到图中的所有                          M
                  L
顶点 , 只能访问到该顶点
所在的最大连通子图 ( 连
通分量 ) 的所有顶点。
A                 B       A                   B
        C D   E                   C

    F    G    H               F

    I         K                       J
          J                                   M
L                 M       L

 若从无向图的每一
个连通分量中的一个                     G   H
顶点出发进行遍历 ,                                D       E
 可求得无向图的所             I           K
有连通分量。

     求连通分量的算法需要对图的每一
    个顶点进行检测:若已被访问过,
    则该顶点一定是落在图中已求得的
    连通分量上;若还未被访问,则从
    该顶点出发遍历图,可求得图的另
    一个连通分量。
A                  B       A               B
         C D   E                   C
     F    G    H               F
     I         K
           J                           J
L                  M       L                   M

    非连通无向图

    DFS 访问序列 :             G   H
    ALMJBFC                                D       E
    DE                 I       K
    GKHI
A               B       G   H
        C
                    I       K
    F
                        D   E
            J
L               M
                            A         G       D
                                          K
对于非连通的无向   L F                  C I           E
图,所有连通分量                                  H
的生成树组成了非   M
连通图的生成森林
         J   B
。
7.4 ( 连通网的 ) 最小生成
树
问题:
  假设要在 n 个城市之间建立
通讯联络网,则连通 n 个城市只
需要修建 n-1 条线路,如何在最
节省经费的前提下建立这个通讯
网?
该问题等价于:
   构造网的一棵最小生成树,即:
  在 e 条带权的边中选取 n-1 条边
(不构成回路), 使 “ 权值之和 ” 为最
小。
算法一:(普里姆算法)

算法二:(克鲁斯卡尔算法)
普里姆算法的基本思想
     假设 N={V,{E}) 是连通网, TE 是 N
上最小生成树边的集合。算法从 U =
{u0}(u0∈ V) , TE={ } 开始,重复执行下
述操作:在所有 u∈ V , v∈ V-U 的边
(u,v)∈ E 中找一条代价最小 的边( u0 ,
v0 )并入集合 TE, 同时 v0 并入 U ,直至
U=V 为止。此时 TE 中必有 n-1 条边,
则 T=(V,{TE}) 为 N 的最小生成树。
一般情况下所添加的顶点应满足下
列条件 : 在生成树的构造过程中,图中
n 个顶点分属两个集合:已落在生成树
上的顶点集 U 和尚未落在生成树上的
顶点集 V-U ,则应在所有连通 U 中顶点
和 V-U 中顶点的边中选取权值最小的边
。
    U            V-U
例如 :
       a         19                    b       5
                14           12
                                           7       c
  18
                     e        8
           16                                  3
  g                                    d
           27                     21
                         f             所得生成树权值和
                              = 14+8+3+5+16+21 = 67
设置一个辅助数组,对每个顶
点,记录从顶点集 U 到 V - U 具有
代价最小的边:
                adjvex lowcost
struct {
    VertexType adjvex; // U 集中的顶点
    VRType lowcost; // 边的权值
} closedge[MAX_VERTEX_NUM];
U: a
    0            19       1
        a                     b       5       2   V-U: b c d e f                 g
            14          12                            a    b c      d e      f   g
                                  7       c       a
18                                                    0    19 ∞     ∞ 14 ∞ 18
            16
                  4   e       8           3       b   19   0    5   7   12 ∞     ∞
                                   d
6   g        27           21          3
                                                  c   ∞    5    0   3    ∞   ∞   ∞
                                                  d   ∞    7    3   0    8   21 ∞
                  5
                    f                             e   14 12 ∞       8    0   ∞ 16
                                                  f   ∞    ∞    ∞ 21 ∞       0   27
                                                  g   18 ∞      ∞   ∞ 16 27          0

                          0       1           2       3        4        5        6
    closedge
        Adjvex                    cd
                                   e
                                   a          d       e         a       d   a
                                                                            e
    Lowcost                       19
                                  12
                                   5
                                   7          3       8        14          18
                                                                        21 16
void MiniSpanTree_P(MGraph G, VertexType u) {
 // 用普里姆算法从顶点 u 出发构造网 G 的最小生成
树
  k = LocateVex ( G, u ); // 定位 u 的位置 k
  for ( j=0; j<G.vexnum; ++j ) // 辅助数组初始化
  if (j!=k)
     closedge[j] = { u, G.arcs[k][j].adj };
  closedge[k].lowcost = 0; // 初始, U = {u}
  for (i=0; i<G.vexnum; ++i) {
      继续向生成树上添加顶点 ;

  }
} //MiniSpanTree
k = minimum(closedge); // 求出加入生成树的下一个
顶点 ( k )
printf(closedge[k].adjvex, G.vexs[k]);
            // 输出生成树上一条边
closedge[k].lowcost = 0; // 第 k 顶点并入 U 集
for (j=0; j<G.vexnum; ++j)
      // 修改其它顶点的最小边
  if (G.arcs[k][j].adj < closedge[j].lowcost)
    closedge[j] = { G.vexs[k], G.arcs[k][j].adj };
  // 新顶点并入 U 后重新选择最小边
克鲁斯卡尔算法的基本思想:
考虑问题的出发点 : 为使生成树上边
的权值之和达到最小,则应使生成树
中每一条边的权值尽可能地小。
克鲁斯卡尔算法的基本思想:
具体做法 : 先构造一个只含 n 个顶
点的子图 SG ,然后从权值最小的
边开始,若它的添加不使 SG 中产
生回路 ,则在 SG 上加上这条边,
如此重复,直至加上 n-1 条边为止
。
例如 :

            a         19                    b       5
                     14           12
                                                7       c
       18
                          e
                16                 8                3
       g                                    d
                27                     21
                              f
算法描述 :
构造非连通图 ST=( V,{ } );
k = i = 0; // k 计选中的边数
while (k<n-1) {
 ++i;
  检查边集 E 中第 i 条权值最小的边 (u,v)
  若 (u,v) 加入 ST 后不使 ST 中产生回路,
  则 输出边 (u,v); 且 k++;
比较两种算法

算法名    普里姆算法 克鲁斯卡尔算

                法
时间复杂度   O(n2)       O(eloge)

适应范围    稠密图         稀疏图
7.7 拓扑排序
 问
题:
       假设以有向图表示一个工程的
施工图或程序的数据流图,则图中不
允许出现回路。一个无环的有向图称
为有向无环图 directed acycline
graph,DAG. DAG 是描述一项工程或系
统的进行过程的有效工具 .
何谓 “ 拓扑排序 ” ?

   检查有向图中是否存在回路的方
法之一,是对有向图进行拓扑排序
(Topological Sort) 。从离散数学的角
度进行解释就是由某个集合上的一个
偏序得到该集合上的一个全序 .
具体操作:

  按照有向图给出的次序关系
,将图中顶点排成一个线性序列
,对于有向图中没有限定次序关
系的顶点,则可以人为加上任意
的次序关系。
由此所得顶点的线性序列称之为拓
扑有序序列 . 例如:对于下列有向图
                B,C 不可比 , 为建立
       B        线序 , 人为增加 B,C
 A          D   的可比关系 , 即在图
       C        上添加弧 <B,C> 或
                <C,B>. 构造出拓扑有
                序序列 .
可求得拓扑有序序列:
     ABCD   或     ACBD
反之,对于下列有向图
      B
  A       D
      C


不能求得它的拓扑有序序列。

因为图中存在一个回路 {B, C, D}
如何进行拓扑排序?
一、从有向图中选取一个没有前
驱
    的顶点,并输出之;
二、从有向图中删去此顶点以及所
   有以它为尾的弧;
   重复上述两步,直至图空,或
者图不空但找不到无前驱的顶点为止
。
c
       a       d
           g          e
       b        f
           h

   a   b h c d g f    e
在算法中需要用定量的描述替代定性的概念
没有前驱的顶点 点      入度为零的顶点
删除顶点及以它为尾的弧 弧        弧头顶点的入度减
c                     a         2       6   ∧
                       0
a            d         1   b         6       7   ∧
     g           e     2   c         3   ∧
                       3   d         4       6   ∧
b            f
     h                 4   e   ∧
                       5   f         4   ∧
    s                  6   g         5   ∧
                       7   h         5   ∧
         h
         b                     0   1 2 3 4   5 6 7
         a
         c
         d           indegree 0 0 0 1 2 2 3 1
                                  1 0 0 1 2 0
                                        0 1
                                          0
输出次序 : b h a c d g f e
算法的设计与实现
l 采用邻接表作有向图的存储结构
l 增加一个存放入度的数组 (indegree), 记
  录拓扑排序过程中 , 顶点入度的变化 .
l 入度 = 0 的顶点为没有前驱的顶点
l 删除顶点及其尾弧的操作可换以弧头
  顶点的入度 -1 来实现 .
l 设置堆栈存储入度为 0 的顶点 , 按照后
  进先出的策略输出拓扑序列
算法描述 : 若 G 无回路 , 则输出 G 的顶点的
一个拓扑序列并返回 OK. 否则返回 ERROR.
 Status Topologicalsort(ALGraph G) {
 FindinDegree(G,indegree); // 对各顶点求入度
 InitStack(s); // 初始化 0 入度顶点栈
 Count=0; // 已输出的顶点数 , 初值为 0

// 对所有顶点 i, 若入度之和为 0, 则将 i 入零入度顶点
栈 S.
 For(i=0;i<G.vexnum;++i)
   if(!indegree[i]) push(s,i);
算法描述
 // 当 0 入度顶点不为空
While(!StackEmpty(s)) {
    pop(s,i); // 出栈一个 0 入度顶点的序号 , 赋给 i
    printf(i,G.vertices[i].data); // 输出 i 号顶点
    ++count; // 累加输出个数
   // 对 i 号顶点的每个邻接顶点
   for(p=G.vertices[i].firstarc;p;p=p->nextarc) {
       k=p->adjvex; // 取其序号 k
       if(!(- -indegree[k])) push(s,k); //k 的入度 - 1 若减到 0, 则
入栈
  if(count<g.vexnum) return ERROR;
    }}                                   }
拓扑排序的应用
l拓扑序列可表示一个流程图 , 如
 应用在施工流程 , 产品的生产流
 程或者数据处理流程 . 流程图内
 工序通常为图中的顶点 , 而顶点
 之间的弧则表达了工序执行的先
 后关系 .
排课系统
l问题描述 :
  为软件学院的学生制定教
 学计划 . 见书 P181 图 7.26
l排课原则 : 依据课程要求 , 对
 任意课程 , 必须先行修完前序
 课程 .
排课系统
解决思路与方案 :
l 建立基于课程先后关系的有向图 . 一
  般称这种用顶点表示活动 , 以弧表示
  活动间的优先关系的有向图为 AOV
  网 (Activity On Vertex Nextwork)
排课系统
解决思路与方案 :
l AOV 网中不应出现有向环 , 否则不合
  课程先后的逻辑 . 因此问题首先转换
  为判定给定的 AOV 网是否存在环 .
l 拓扑排序 : 对有向图构造其顶点的拓
  扑有序序列 , 若网中所有顶点都在它
  的拓扑有序序列中 , 则该 AOV 网中必
  然存在环 .
排课系统
      c4                       c5
              c2
c1                        c3        c7

                   c12
 c9                                 c8
        c10              c6

      c11
c1,c2,c3,c4,c5,c7,c9,c10,c11,c6,c12,c8
排课系统
      c4                       c5
              c2
c1                        c3        c7

                   c12
 c9                                 c8
        c10              c6

      c11
c9,c10,c11,c6,c1,c12,c4,c2,c3,c5,c7,c8
排课系统
l 对此图也可构造得其他的拓扑有序序
  列 . 若某个学生每学期只学一门课程
  的话 , 则他必须按拓扑有序的顺序来
  安排学习计划 .

More Related Content

Viewers also liked (8)

V 293
V 293V 293
V 293
 
звіт 2012
звіт 2012звіт 2012
звіт 2012
 
Presentacion Ver2
Presentacion Ver2Presentacion Ver2
Presentacion Ver2
 
2011 03-21 - semana leitura
2011 03-21 - semana leitura2011 03-21 - semana leitura
2011 03-21 - semana leitura
 
Trabajo Luis
Trabajo LuisTrabajo Luis
Trabajo Luis
 
Estandarizacion Del Proceso
Estandarizacion Del ProcesoEstandarizacion Del Proceso
Estandarizacion Del Proceso
 
Decoração Natal Parte II- EB2 Anadia
Decoração Natal Parte II- EB2 AnadiaDecoração Natal Parte II- EB2 Anadia
Decoração Natal Parte II- EB2 Anadia
 
web 2.0
web 2.0web 2.0
web 2.0
 

More from Wang Yizhe (13)

第四章 串
第四章 串第四章 串
第四章 串
 
第六章 树和二叉树2
第六章 树和二叉树2第六章 树和二叉树2
第六章 树和二叉树2
 
第六章1
第六章1第六章1
第六章1
 
第九章+查找[4]
第九章+查找[4]第九章+查找[4]
第九章+查找[4]
 
第九章 查找[3]
第九章 查找[3]第九章 查找[3]
第九章 查找[3]
 
第九章 查找[2]
第九章 查找[2]第九章 查找[2]
第九章 查找[2]
 
第九章 查找[1]
第九章 查找[1]第九章 查找[1]
第九章 查找[1]
 
第三章 栈和队列
第三章 栈和队列第三章 栈和队列
第三章 栈和队列
 
第七章 图[4]1
第七章 图[4]1第七章 图[4]1
第七章 图[4]1
 
第七章 图[3]
第七章 图[3]第七章 图[3]
第七章 图[3]
 
数据结构 总结与复习
数据结构 总结与复习数据结构 总结与复习
数据结构 总结与复习
 
第四章 串操作应用举例
第四章 串操作应用举例第四章 串操作应用举例
第四章 串操作应用举例
 
第二章 线性表
第二章 线性表第二章 线性表
第二章 线性表
 

第七章 图[2]1

  • 1. 7. 3 图的遍历 Tra ve rs ing G ra p h 从图中某个顶点出发游历图,访遍 图中其余顶点,并且使图中的每个顶点 仅被访问一次的过程。图的遍历算法是 求解图的连通性问题 , 拓扑排序和求关 键路径等算法的基础 .
  • 2. 7.3 图的遍历 深度优先搜索 广度优先搜索 遍历应用举例
  • 3. 一、深度优先搜索遍历图 连通图的深度优先搜索遍历 从图中某个顶点 V0 出发,访问 此顶点,然后依次从 V0 的各个未被访 问的邻接点出发深度优先搜索遍历图 ,直至图中所有和 V0 有路径相通的顶 点都被访问到。
  • 4. V W1 、 W2 和 W3 均为 V 的邻接点, SG1 、 w1 w2 w3 SG2 和 SG3 分别为含 SG1 SG2 SG3 顶点 W1 、 W2 和 W3 的子图。 访问顶点 V : for (W1 、 W2 、 W3 ) 若该邻接点 W 未被访问, 则从它出发进行深度优先搜索遍历。
  • 5. 从上页的图解可见 : 1. 从深度优先搜索遍历连通图的 过程类似于树的先根遍历; 2. 如何判别 V 的邻接点是否被访问? 解决的办法是:为每个顶点设立 一个 “ 访问标志 visited[w]” 。
  • 6. 例如 : 0 a 2 3 4 5 c d e f 1 b 6 g 0 1 2 3 4 5 6 访问标志 : F F F F F T T T T T T T F F 访问次序 : a c b d g f e
  • 7. void DFS(Graph G, int v) { // 从顶点 v 出发,深度优先搜索遍历连通 图 G visited[v] = TRUE; VisitFunc(v); for(w=FirstAdjVex(G, v); w!=0; w=NextAdjVex(G,v,w)) if (!visited[w]) DFS(G, w); // 对 v 的尚未访问的邻接顶点 w // 递归调用 DFS
  • 8. 非连通图的深度优先搜索遍历 首先将图中每个顶点的访问标志 设为 FALSE, 之后搜索图中每个顶点 ,如果未被访问,则以该顶点为起始点 ,进行深度优先搜索遍历,否则继续检 查下一顶点。
  • 9. 例如 : 0 1 a b 6 g 2 3 4 5 c d e f 7 h 8 k 0 1 2 3 4 5 6 7 8 访问标志 : F F F F F T T T F T T T T T F F F T 访问次序 : a c h d k f e b g
  • 10. void DFSTraverse(Graph G, Status (*Visit)(int v)) { // 对图 G 作深度优先遍历。 VisitFunc = Visit; for (v=0; v<G.vexnum; ++v) visited[v] = FALSE; // 访问标志数组初始 化 for (v=0; v<G.vexnum; ++v) if (!visited[v]) DFS(G, v); // 对尚未访问的顶点调用 DFS
  • 11. 二、广度优先搜索遍历图 对连通图,从起始点 V 到 w2 其余各顶点必定存在路径。 w1 其中, V->w1, V->w2, V->w8 V w3 的路径长度为 1 ; w7 V->w7, V->w3, V->w5 w8 w4 的路径长度为 2 ; w6 w5 V->w6, V->w4 的路径长度为 3 。
  • 12. 从图中的某个顶点 V0 出发,并在访问 此顶点之后依次访问 V0 的所有未被访问过 的邻接点,之后分别从这些邻接点出发依 次访问它们的邻接点,并使“ 先被访问的顶 点的邻接点 ” 先于 “ 后被访问的顶点的邻接 点 ” 被访问,直至图中所有和 V0 有路径相通 若此时图中尚有顶点未被访问,则另选 的顶点都被访问到。 图中一个未曾被访问的顶点作起始点,重 复上述过程,直至图中所有顶点都被访问 到为止。
  • 13. 0 1 2 3 4 5 6 7 8 Visited F F F F F T T T F T T T T T F F F T w2 Q w1 w1 w2 w8 w7 w3 w5 w6 w4 V V w3 w7 访问次序 : w8 w4 w6 w5
  • 14. void BFSTraverse(Graph G, Status (*Visit)(int v)){ for (v=0; v<G.vexnum; ++v) visited[v] = FALSE; // 初始化访问标 志 InitQueue(Q); // 置空的辅助队列 Q for ( v=0; v<G.vexnum; ++v ) if … … ( !visited[v]) { // v 尚未访问 }
  • 15. visited[u] = TRUE; Visit(u); // 访问 u EnQueue(Q, v); // v 入队列 while (!QueueEmpty(Q)) { DeQueue(Q, u); // 队头元素出队并置为 u for(w=FirstAdjVex(G, u); w!=0; w=NextAdjVex(G,u,w)) if ( ! visited[w]) { visited[w]=TRUE; Visit(w); EnQueue(Q, w); // 访问的顶点 w 入队列 } // if } // while
  • 16. 图的连通性问题 连通分量 (Connected A B component) 当无向图为非连通图 C D E 时 , 从图中某一顶点出发 F G H , 利用深度优先搜索算法 或广度优先搜索算法不 I K J 可能遍历到图中的所有 M L 顶点 , 只能访问到该顶点 所在的最大连通子图 ( 连 通分量 ) 的所有顶点。
  • 17. A B A B C D E C F G H F I K J J M L M L 若从无向图的每一 个连通分量中的一个 G H 顶点出发进行遍历 , D E 可求得无向图的所 I K 有连通分量。
  • 18. 求连通分量的算法需要对图的每一 个顶点进行检测:若已被访问过, 则该顶点一定是落在图中已求得的 连通分量上;若还未被访问,则从 该顶点出发遍历图,可求得图的另 一个连通分量。
  • 19. A B A B C D E C F G H F I K J J L M L M 非连通无向图 DFS 访问序列 : G H ALMJBFC D E DE I K GKHI
  • 20. A B G H C I K F D E J L M A G D K 对于非连通的无向 L F C I E 图,所有连通分量 H 的生成树组成了非 M 连通图的生成森林 J B 。
  • 21. 7.4 ( 连通网的 ) 最小生成 树 问题: 假设要在 n 个城市之间建立 通讯联络网,则连通 n 个城市只 需要修建 n-1 条线路,如何在最 节省经费的前提下建立这个通讯 网?
  • 22. 该问题等价于: 构造网的一棵最小生成树,即: 在 e 条带权的边中选取 n-1 条边 (不构成回路), 使 “ 权值之和 ” 为最 小。 算法一:(普里姆算法) 算法二:(克鲁斯卡尔算法)
  • 23. 普里姆算法的基本思想 假设 N={V,{E}) 是连通网, TE 是 N 上最小生成树边的集合。算法从 U = {u0}(u0∈ V) , TE={ } 开始,重复执行下 述操作:在所有 u∈ V , v∈ V-U 的边 (u,v)∈ E 中找一条代价最小 的边( u0 , v0 )并入集合 TE, 同时 v0 并入 U ,直至 U=V 为止。此时 TE 中必有 n-1 条边, 则 T=(V,{TE}) 为 N 的最小生成树。
  • 24. 一般情况下所添加的顶点应满足下 列条件 : 在生成树的构造过程中,图中 n 个顶点分属两个集合:已落在生成树 上的顶点集 U 和尚未落在生成树上的 顶点集 V-U ,则应在所有连通 U 中顶点 和 V-U 中顶点的边中选取权值最小的边 。 U V-U
  • 25. 例如 : a 19 b 5 14 12 7 c 18 e 8 16 3 g d 27 21 f 所得生成树权值和 = 14+8+3+5+16+21 = 67
  • 26. 设置一个辅助数组,对每个顶 点,记录从顶点集 U 到 V - U 具有 代价最小的边: adjvex lowcost struct { VertexType adjvex; // U 集中的顶点 VRType lowcost; // 边的权值 } closedge[MAX_VERTEX_NUM];
  • 27. U: a 0 19 1 a b 5 2 V-U: b c d e f g 14 12 a b c d e f g 7 c a 18 0 19 ∞ ∞ 14 ∞ 18 16 4 e 8 3 b 19 0 5 7 12 ∞ ∞ d 6 g 27 21 3 c ∞ 5 0 3 ∞ ∞ ∞ d ∞ 7 3 0 8 21 ∞ 5 f e 14 12 ∞ 8 0 ∞ 16 f ∞ ∞ ∞ 21 ∞ 0 27 g 18 ∞ ∞ ∞ 16 27 0 0 1 2 3 4 5 6 closedge Adjvex cd e a d e a d a e Lowcost 19 12 5 7 3 8 14 18 21 16
  • 28. void MiniSpanTree_P(MGraph G, VertexType u) { // 用普里姆算法从顶点 u 出发构造网 G 的最小生成 树 k = LocateVex ( G, u ); // 定位 u 的位置 k for ( j=0; j<G.vexnum; ++j ) // 辅助数组初始化 if (j!=k) closedge[j] = { u, G.arcs[k][j].adj }; closedge[k].lowcost = 0; // 初始, U = {u} for (i=0; i<G.vexnum; ++i) { 继续向生成树上添加顶点 ; } } //MiniSpanTree
  • 29. k = minimum(closedge); // 求出加入生成树的下一个 顶点 ( k ) printf(closedge[k].adjvex, G.vexs[k]); // 输出生成树上一条边 closedge[k].lowcost = 0; // 第 k 顶点并入 U 集 for (j=0; j<G.vexnum; ++j) // 修改其它顶点的最小边 if (G.arcs[k][j].adj < closedge[j].lowcost) closedge[j] = { G.vexs[k], G.arcs[k][j].adj }; // 新顶点并入 U 后重新选择最小边
  • 31. 克鲁斯卡尔算法的基本思想: 具体做法 : 先构造一个只含 n 个顶 点的子图 SG ,然后从权值最小的 边开始,若它的添加不使 SG 中产 生回路 ,则在 SG 上加上这条边, 如此重复,直至加上 n-1 条边为止 。
  • 32. 例如 : a 19 b 5 14 12 7 c 18 e 16 8 3 g d 27 21 f
  • 33. 算法描述 : 构造非连通图 ST=( V,{ } ); k = i = 0; // k 计选中的边数 while (k<n-1) { ++i; 检查边集 E 中第 i 条权值最小的边 (u,v) 若 (u,v) 加入 ST 后不使 ST 中产生回路, 则 输出边 (u,v); 且 k++;
  • 34. 比较两种算法 算法名 普里姆算法 克鲁斯卡尔算 法 时间复杂度 O(n2) O(eloge) 适应范围 稠密图 稀疏图
  • 35. 7.7 拓扑排序 问 题: 假设以有向图表示一个工程的 施工图或程序的数据流图,则图中不 允许出现回路。一个无环的有向图称 为有向无环图 directed acycline graph,DAG. DAG 是描述一项工程或系 统的进行过程的有效工具 .
  • 36. 何谓 “ 拓扑排序 ” ? 检查有向图中是否存在回路的方 法之一,是对有向图进行拓扑排序 (Topological Sort) 。从离散数学的角 度进行解释就是由某个集合上的一个 偏序得到该集合上的一个全序 .
  • 38. 由此所得顶点的线性序列称之为拓 扑有序序列 . 例如:对于下列有向图 B,C 不可比 , 为建立 B 线序 , 人为增加 B,C A D 的可比关系 , 即在图 C 上添加弧 <B,C> 或 <C,B>. 构造出拓扑有 序序列 . 可求得拓扑有序序列: ABCD 或 ACBD
  • 39. 反之,对于下列有向图 B A D C 不能求得它的拓扑有序序列。 因为图中存在一个回路 {B, C, D}
  • 40. 如何进行拓扑排序? 一、从有向图中选取一个没有前 驱 的顶点,并输出之; 二、从有向图中删去此顶点以及所 有以它为尾的弧; 重复上述两步,直至图空,或 者图不空但找不到无前驱的顶点为止 。
  • 41. c a d g e b f h a b h c d g f e 在算法中需要用定量的描述替代定性的概念 没有前驱的顶点 点 入度为零的顶点 删除顶点及以它为尾的弧 弧 弧头顶点的入度减
  • 42. c a 2 6 ∧ 0 a d 1 b 6 7 ∧ g e 2 c 3 ∧ 3 d 4 6 ∧ b f h 4 e ∧ 5 f 4 ∧ s 6 g 5 ∧ 7 h 5 ∧ h b 0 1 2 3 4 5 6 7 a c d indegree 0 0 0 1 2 2 3 1 1 0 0 1 2 0 0 1 0 输出次序 : b h a c d g f e
  • 43. 算法的设计与实现 l 采用邻接表作有向图的存储结构 l 增加一个存放入度的数组 (indegree), 记 录拓扑排序过程中 , 顶点入度的变化 . l 入度 = 0 的顶点为没有前驱的顶点 l 删除顶点及其尾弧的操作可换以弧头 顶点的入度 -1 来实现 . l 设置堆栈存储入度为 0 的顶点 , 按照后 进先出的策略输出拓扑序列
  • 44. 算法描述 : 若 G 无回路 , 则输出 G 的顶点的 一个拓扑序列并返回 OK. 否则返回 ERROR. Status Topologicalsort(ALGraph G) { FindinDegree(G,indegree); // 对各顶点求入度 InitStack(s); // 初始化 0 入度顶点栈 Count=0; // 已输出的顶点数 , 初值为 0 // 对所有顶点 i, 若入度之和为 0, 则将 i 入零入度顶点 栈 S. For(i=0;i<G.vexnum;++i) if(!indegree[i]) push(s,i);
  • 45. 算法描述 // 当 0 入度顶点不为空 While(!StackEmpty(s)) { pop(s,i); // 出栈一个 0 入度顶点的序号 , 赋给 i printf(i,G.vertices[i].data); // 输出 i 号顶点 ++count; // 累加输出个数 // 对 i 号顶点的每个邻接顶点 for(p=G.vertices[i].firstarc;p;p=p->nextarc) { k=p->adjvex; // 取其序号 k if(!(- -indegree[k])) push(s,k); //k 的入度 - 1 若减到 0, 则 入栈 if(count<g.vexnum) return ERROR; }} }
  • 46. 拓扑排序的应用 l拓扑序列可表示一个流程图 , 如 应用在施工流程 , 产品的生产流 程或者数据处理流程 . 流程图内 工序通常为图中的顶点 , 而顶点 之间的弧则表达了工序执行的先 后关系 .
  • 47. 排课系统 l问题描述 : 为软件学院的学生制定教 学计划 . 见书 P181 图 7.26 l排课原则 : 依据课程要求 , 对 任意课程 , 必须先行修完前序 课程 .
  • 48. 排课系统 解决思路与方案 : l 建立基于课程先后关系的有向图 . 一 般称这种用顶点表示活动 , 以弧表示 活动间的优先关系的有向图为 AOV 网 (Activity On Vertex Nextwork)
  • 49. 排课系统 解决思路与方案 : l AOV 网中不应出现有向环 , 否则不合 课程先后的逻辑 . 因此问题首先转换 为判定给定的 AOV 网是否存在环 . l 拓扑排序 : 对有向图构造其顶点的拓 扑有序序列 , 若网中所有顶点都在它 的拓扑有序序列中 , 则该 AOV 网中必 然存在环 .
  • 50. 排课系统 c4 c5 c2 c1 c3 c7 c12 c9 c8 c10 c6 c11 c1,c2,c3,c4,c5,c7,c9,c10,c11,c6,c12,c8
  • 51. 排课系统 c4 c5 c2 c1 c3 c7 c12 c9 c8 c10 c6 c11 c9,c10,c11,c6,c1,c12,c4,c2,c3,c5,c7,c8
  • 52. 排课系统 l 对此图也可构造得其他的拓扑有序序 列 . 若某个学生每学期只学一门课程 的话 , 则他必须按拓扑有序的顺序来 安排学习计划 .