7.4 图的连通性问题

28
第7第 7.4 第第第第第第第 1. 第第第第第第第第 第第第第 第第第第第第 第第第第第第第第第第第第第第第第第第 第第第第第 ,,, 第第第第第第 第第第第 第第第第第第 第第第第第第第第第第第第第 第第第第第第第 第第第第 一,一,。, 第第第第第第第 第第第第第第第第第第第第第第第第第第第第第第第第第第第 ,。 j=0; for(v=0; v < G.vernum; ++v) if(!visited[v]){ DFS(G, v); j++; }

description

7.4 图的连通性问题. 1. 无向图的连通分量. 图遍历时,对于连通图,无论是广度优先搜索还是深度优先搜索,仅需要调用一次搜索过程,即从任一个顶点出发,便可以遍历图中的各个顶点。对于非连通图,则需要多次调用搜索过程,而每次调用得到的顶点访问序列恰为各连通分量中的顶点集。. j=0; for(v=0; v < G.vernum; ++v) if(!visited[v]){ DFS(G, v); j++; }. A L M J B F C;  D E; G K H I;. - PowerPoint PPT Presentation

Transcript of 7.4 图的连通性问题

Page 1: 7.4   图的连通性问题

第 7 章 图

7.4 图的连通性问题1. 无向图的连通分量

图遍历时,对于连通图,无论是广度优先搜索还是深度优先搜索,仅需要调用一次搜索过程,即从任一个顶点出发,便可以遍历图中的各个顶点。对于非连通图,则需要多次调用搜索过程,而每次调用得到的顶点访问序列恰为各连通分量中的顶点集。

j=0;

for(v=0; v < G.vernum; ++v)

if(!visited[v]){

DFS(G, v);

j++;

}

Page 2: 7.4   图的连通性问题

第 7 章 图

A L M J B F C; D E;G K H I;

Page 3: 7.4   图的连通性问题

第 7 章 图

2. 无向图的生成树

一个连通图的生成树是一个极小连通子图 , 它含有图中全部顶点 , 但只有构成一棵树的 (n-1) 条边。 e<n-1 → 非连通图 e>n-1 → 有回路 e=n-1 → 不一定都是图的生成树

设 G=(V,E) 为连通图 , 则从图中任一顶点出发遍历图时 , 必定将 E(G) 分成两个集合 T 和 B, 其中 T 是遍历图过程中走过的边的集合 ,B 是剩余的边的集合: T∩B=Φ,T B=E(G)∪ 。显然 ,G'=(V,T) 是 G 的极小连通子图 , 即 G' 是 G 的一棵生成树。

Page 4: 7.4   图的连通性问题

第 7 章 图

由深度优先遍历得到的生成树称为深度优先生成树;由广度优先遍历得到的生成树称为广度优先生成树。这样的生成树是由遍历时访问过的 n 个顶点和遍历时经历的 n-1 条边组成。 对于非连通图 , 每个连通分量中的顶点集和遍历时走过的边一起构成一棵生成树 , 各个连通分量的生成树组成非连通图的生成森林。

问题:以孩子兄弟链表作为森林(或树)的存储结构,如何建立无向图的深度优先生成森林或广度优先生成森林?

Page 5: 7.4   图的连通性问题

第 7 章 图

A

F C L

M

J B

D

E

G

K I

H

深度优先生成森林

A

F C L

M J

B

D

E

G

K I H

广度优先生成森林

Page 6: 7.4   图的连通性问题

第 7 章 图

void DFSForest(ALGraph G, CSTree &T)

{ T = NULL;

for(v=0; v<G.vexnum; ++v) visited[v] = 0;

for(v=0; v<G.vexnum; ++v)

if( !visited[v]){

p = (CSTree) malloc(sizeof(CSNode));

*p = { G.vertices[v].data, NULL, NULL};

if( !T) T = p;

else q->nextsibling = p;

q = p;

DFSTree(G, v, p);

}

}

Page 7: 7.4   图的连通性问题

第 7 章 图 void DFSTree(Graph G, int v, CSTree &T)

{ ArcNode *a; CSTree p; visited[v] = 1; first = TRUE;

for(a=G.vertices[v].firstarc; a; a=a->nextarc){

w = a->adjvex;

if( !visited[w]){

p = (CSTree) malloc(sizeof(CSNode));

*p = {G.vertices[w].data, NULL, NULL};

if( first) { T->lchild = p; first = FALSE; }

else q->nextsibling = p;

q = p;

DFSTree(G, w, q);

}//if

}//for

}

Page 8: 7.4   图的连通性问题

第 7 章 图

3. 最小生成树

在一个连通网的所有生成树中, 各边的代价之和最小的那棵生成树称为该连通网的最小代价生成树( Minimum Cost Spanning Tree ),简称为最小生成树 (MST) 。

最小生成树有如下重要性质 (MST 性质 ) :

设 N=(V, {E}) 是一连通网, U 是顶点集 V 的一个非空子集。 若( u , v )是一条具有最小权值的边, 其中 u U∈ , v∈V-U , 则存在一棵包含边( u , v )的最小生成树。

Page 9: 7.4   图的连通性问题

第 7 章 图

用反证法来证明这个 MST 性质:

假设不存在这样一棵包含边( u , v )的最小生成树。 任取一棵最小生成树 T ,将( u , v )加入 T 中。根据树的性质,此时 T 中必形成一个包含( u , v )的回路, 且回路中必有一条边( u′, v′ )的权值大于或等于( u , v )的权值。 删除( u′, v′ ),则得到一棵代价小于等于 T 的生成树 T′ ,且 T′ 为一棵包含边( u , v )的最小生成树。 这与假设矛盾, 故该性质得证。

利用 MST 性质来生成一个连通网的最小生成树。普里姆( Prim )算法和克鲁斯卡尔( Kruskal )算法便是利用了这个性质。

Page 10: 7.4   图的连通性问题

第 7 章 图

普里姆算法 (Prim 算法 , 又称边割法 ) 1957 年由 Prim 提出

假设 N=(V, {E}) 是连通网, TE 为最小生成树中边的集合。

Step1: 初始 U={u0}(u0 V), TE=φ∈ ;

Step2: 在所有 u U, v V-U∈ ∈ 的边中选一条代价最小的边 (u0, v

0) 并入集合 TE ,同时将 v0 并入 U ;

Step3: 重复 Step2 ,直到 U=V 为止。

此时, TE 中必含有 n-1 条边,则 T=(V, {TE}) 为 N 的最小生成树。

从一个平凡图开始,普利姆算法逐步增加 U 中的顶点, 可称为“加点法”。

Page 11: 7.4   图的连通性问题

第 7 章 图

普里姆算法求解最小生成树的过程

5

1

3 2 4

5 6

(a)

1 5 6

3

5

6

6

4 2

1

3 2 4

5 6

(b)

1

1

3 2 4

5 6

(c)

1

4

1

3 2 4

5 6

(d)

1

4 2

1

3 2 4

5 6

(e)

1 5

4 2

1

3 2 4

5 6

(f)

1

3 4 2

5

Page 12: 7.4   图的连通性问题

第 7 章 图

struct { VertexType adjvex; // 顶点名称 int lowcost; } closedge[MAX_VERTEX_NUM];

为了实现这个算法需要设置一个辅助数组 closedge[] ,以记录从U 到 V-U 具有最小代价的边。对每个顶点 vi V-U∈ ,在辅助数组中存在一个分量 closedge[vi] ,它包括两个域 adjvex 和 lowcost ,其中 lowcost 存储该边上的权, 显然有

closedge[i-1].lowcost = Min({cost(u, vi) | u U})∈

Page 13: 7.4   图的连通性问题

第 7 章 图

Page 14: 7.4   图的连通性问题

第 7 章 图

void MiniSpanTree_PRIM( MGraph G, VertexType u)

{ k = LocateVex(G, u);

for(j=0; j<G.vexnum; ++j)

if(j!=k) closedge[j] = { u, G.arcs[k][j].adj };

closedge[k].lowcost = 0;

for(i=1; i<G.vexnum; ++i){

k = minimun(closedge);

printf(colsedge[k].adjvex, G.vexs[k]);

closedge[k].lowcost = 0;

for(j=0; j<G.vexnum; ++j)

if(G.arcs[k][j].adj < closedge[j].lowcost)

colsedge[j] = { G.vexs[k], G.arcs[k][j].adj };

}

}

Page 15: 7.4   图的连通性问题

第 7 章 图

Prim() 算法中有两重 for 循环 , 所以时间复杂

度为 O(n2) 。 与网中的边数无关,适用于求边

稠密的网的最小生成树。

Page 16: 7.4   图的连通性问题

第 7 章 图

克鲁斯卡尔算法(避圈法) Kruskal 于 1956 年提出

假设 G=(V,E) 是一个具有 n 个顶点的带权连通无向图 ,T=(U,TE) 是 G 的最小生成树 , 则构造最小生成树的步骤如下:

Step1:T=(V,{}) ,有 n 个顶点而无边的非连通图;

Step2: 在 E 中选择代价最小的边,若该边依附的顶点落在 T 中不同的连通分量上,则将此边加入 TE ;否则舍弃 , 而选择下一条代价最小的边;

Step3: 若 T 中有 n-1 条边,结束;否则转 step2.

从一个零图开始,克鲁斯卡尔算法逐步增加生成树的边, 与普里姆算法相比,可称为“加边法”。

Page 17: 7.4   图的连通性问题

第 7 章 图

4

1

0

2 1 3

4 5

(a)

1

0

2 1 3

4 5

(b)

1

0

2 1 3

4 5

(c)

1

0

2 1 3

4 5

(d)

1

4 2

(e)

0

2 1 3

4 5

1

3 4 2

5

2 2 3

3

0

0 3

5 4

1

0

0 3

3 1

1

0

0 3

3

1

1

0

0 0

0 1

1

1

1 1

1

克鲁斯卡尔算法求解最小生成树的过程

Page 18: 7.4   图的连通性问题

第 7 章 图

为了简便 , 在实现克鲁斯卡尔算法 Kruskal() 时 ,参数 E 存放图 G 中的所有边 , 假设它们是按权值从小到大的顺序排列的。 n 为图 G 的顶点个数 ,e 为图 G

的边数。 typedef struct {

int u; /* 边的起始顶点 */

int v; /* 边的终止顶点 */

int w; /* 边的权值 */

} Edge;

Page 19: 7.4   图的连通性问题

第 7 章 图 void Kruskal(Edge E[], int n){ int i,j,k; int vset[MAXV]; for (i=0;i<n;i++) vset[i]=i; k=1; j=0; while (k<n) { /* 生成的边数小于 n 时循环 */ if (vset[E[j].u] != vset[E[j].v]) { printf("(%d,%d):%d\n",E[j].u,E[j].v,E[j].w);

k++; for (i=0;i<n;i++) /* 两个集合统一编号 */

if (vset[i]==vset[E[j].v]) vset[i]=vset[E[j].u];

}j++; /* 扫描下一条边 */

} }

Page 20: 7.4   图的连通性问题

第 7 章 图

完整的克鲁斯卡尔算法应包括对边按权值递增排序,上述算法假设边已排序的情况下,时间复杂度为 O(n2) 。

如果给定的带权连通无向图 G 有 e 条边 ,n 个顶点,采用堆排序 (在第 10章中介绍 )对边按权值递增排序,那么用克鲁斯卡尔算法构造最小生成树的时间复杂度降为 O(eloge) 。 由于它与 n 无关,只与 e 有关,所以说克鲁斯卡尔算法适合于求边稀疏的网的最小生成树。

Page 21: 7.4   图的连通性问题

第 7 章 图

7.5 有向无环图及其应用 1. 拓扑排序( Topological Sort )

设 G=(V,E) 是一个具有 n 个顶点的有向图 ,V 中顶点序列v1,v2,…,vn 称为一个拓扑 ( 有序 ) 序列 ,当且仅当该顶点序列满足下列条件:若 <vi,vj> 是图中的弧 ( 即从顶点 vi 到 vj 有一条路径 ), 则在序列中顶点 vi 必须排在顶点 vj 之前。 在一个有向图中找一个拓扑序列的过程称为拓扑排序。

Page 22: 7.4   图的连通性问题

第 7 章 图

C2

C 1 C 8

C9

C3

C 4

C5

C 6

C7

Page 23: 7.4   图的连通性问题

第 7 章 图

拓扑序列: C1, C2, C3, C4, C5, C8, C9, C7, C6 。

拓扑序列: C1, C2, C3, C8, C4, C5, C9, C7, C6 。

C2

C 1 C 8

C9

C3

C 4

C5

C 6

C7

用顶点表示活动,用弧表示活动间的优先关系的有向图,称为顶点表示活动的网 (Activity On Vertex Network) ,简称为 AOV- 网。

Page 24: 7.4   图的连通性问题

第 7 章 图 如何进行拓扑排序?

方法一:(从图中顶点的入度考虑)

① 从有向图中选择一个没有前驱 (即入度为 0)的顶点并且输出它。

② 从网中删去该顶点和所有以它为尾的弧;

③ 重复上述两步 , 直到图全部顶点输出;或当前图中不再存在没有前驱的顶点。

v1 , v6 , v4 , v3 , v2 , v5 或 v1 , v3 , v2 , v6 , v4 ,v5

Page 25: 7.4   图的连通性问题

第 7 章 图 方法二:(从图中顶点的出度考虑,得到逆拓扑序列)

① 从有向图中选择一个出度为 0的顶点并且输出它。

② 从网中删去该顶点和所有以它为头的弧;

③ 重复上述两步 , 直到图全部顶点输出;或当前图中不再存在出度为 0的顶点。

方法三:当有向图中无环时,利用深度优先遍历进行拓扑排序

从某点出发进行DFS遍历时,最先退出 DFS 函数的顶点即出

度为 0的顶点,是拓扑序列中最后一个顶点。按退出 DFS 函数的

先后记录下来的顶点序列即为逆拓扑序列。问题:判定一个图是否有圈(回路)的方法?

Page 26: 7.4   图的连通性问题

第 7 章 图 Status TopologicalSort( ALGraph G){ int St[MAXV], top=-1; /*栈St的指针为 top*/ FindInDegree(G, indegree); for (i=0; i<G.vexnum; i++) if (! indegree[i]) { top++; St[top]=i; } count = 0; while (top>-1) { /*栈不为空时循环 */ i=St[top]; top--; printf("%d ",i); ++count; for( p=G.vertices[i].firstarc; p; p=p->nextarc){ k = p->adjvex; adj[j].count--; if ( !(--indegree[k])) { top++; St[top]=k; }

} } if(count<G.vexnum) return ERROR; else return OK;}

Page 27: 7.4   图的连通性问题

第 7 章 图

void FindInDegree( ALGraph G, int *indegree)

{ int i; ArcNode *p;

for(i=0; i<G.vexnum; i++) indegree[i] = 0;

for(i=0; i<G.vexnum; i++) {

p=G.vertexes[i].firstarc;

while(p! =NULL) {

indegree[p->adjvex]++;

p = p->nextarc;

} //while

} // for

}

Page 28: 7.4   图的连通性问题

第 7 章 图

作业:

7.5 7.7