7.4 图的连通性问题
description
Transcript of 7.4 图的连通性问题
第 7 章 图
7.4 图的连通性问题1. 无向图的连通分量
图遍历时,对于连通图,无论是广度优先搜索还是深度优先搜索,仅需要调用一次搜索过程,即从任一个顶点出发,便可以遍历图中的各个顶点。对于非连通图,则需要多次调用搜索过程,而每次调用得到的顶点访问序列恰为各连通分量中的顶点集。
j=0;
for(v=0; v < G.vernum; ++v)
if(!visited[v]){
DFS(G, v);
j++;
}
第 7 章 图
A L M J B F C; D E;G K H I;
第 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 的一棵生成树。
第 7 章 图
由深度优先遍历得到的生成树称为深度优先生成树;由广度优先遍历得到的生成树称为广度优先生成树。这样的生成树是由遍历时访问过的 n 个顶点和遍历时经历的 n-1 条边组成。 对于非连通图 , 每个连通分量中的顶点集和遍历时走过的边一起构成一棵生成树 , 各个连通分量的生成树组成非连通图的生成森林。
问题:以孩子兄弟链表作为森林(或树)的存储结构,如何建立无向图的深度优先生成森林或广度优先生成森林?
第 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
广度优先生成森林
第 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);
}
}
第 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
}
第 7 章 图
3. 最小生成树
在一个连通网的所有生成树中, 各边的代价之和最小的那棵生成树称为该连通网的最小代价生成树( Minimum Cost Spanning Tree ),简称为最小生成树 (MST) 。
最小生成树有如下重要性质 (MST 性质 ) :
设 N=(V, {E}) 是一连通网, U 是顶点集 V 的一个非空子集。 若( u , v )是一条具有最小权值的边, 其中 u U∈ , v∈V-U , 则存在一棵包含边( u , v )的最小生成树。
第 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 )算法便是利用了这个性质。
第 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 中的顶点, 可称为“加点法”。
第 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
第 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})∈
第 7 章 图
第 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 };
}
}
第 7 章 图
Prim() 算法中有两重 for 循环 , 所以时间复杂
度为 O(n2) 。 与网中的边数无关,适用于求边
稠密的网的最小生成树。
第 7 章 图
克鲁斯卡尔算法(避圈法) Kruskal 于 1956 年提出
假设 G=(V,E) 是一个具有 n 个顶点的带权连通无向图 ,T=(U,TE) 是 G 的最小生成树 , 则构造最小生成树的步骤如下:
Step1:T=(V,{}) ,有 n 个顶点而无边的非连通图;
Step2: 在 E 中选择代价最小的边,若该边依附的顶点落在 T 中不同的连通分量上,则将此边加入 TE ;否则舍弃 , 而选择下一条代价最小的边;
Step3: 若 T 中有 n-1 条边,结束;否则转 step2.
从一个零图开始,克鲁斯卡尔算法逐步增加生成树的边, 与普里姆算法相比,可称为“加边法”。
第 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
克鲁斯卡尔算法求解最小生成树的过程
第 7 章 图
为了简便 , 在实现克鲁斯卡尔算法 Kruskal() 时 ,参数 E 存放图 G 中的所有边 , 假设它们是按权值从小到大的顺序排列的。 n 为图 G 的顶点个数 ,e 为图 G
的边数。 typedef struct {
int u; /* 边的起始顶点 */
int v; /* 边的终止顶点 */
int w; /* 边的权值 */
} Edge;
第 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++; /* 扫描下一条边 */
} }
第 7 章 图
完整的克鲁斯卡尔算法应包括对边按权值递增排序,上述算法假设边已排序的情况下,时间复杂度为 O(n2) 。
如果给定的带权连通无向图 G 有 e 条边 ,n 个顶点,采用堆排序 (在第 10章中介绍 )对边按权值递增排序,那么用克鲁斯卡尔算法构造最小生成树的时间复杂度降为 O(eloge) 。 由于它与 n 无关,只与 e 有关,所以说克鲁斯卡尔算法适合于求边稀疏的网的最小生成树。
第 7 章 图
7.5 有向无环图及其应用 1. 拓扑排序( Topological Sort )
设 G=(V,E) 是一个具有 n 个顶点的有向图 ,V 中顶点序列v1,v2,…,vn 称为一个拓扑 ( 有序 ) 序列 ,当且仅当该顶点序列满足下列条件:若 <vi,vj> 是图中的弧 ( 即从顶点 vi 到 vj 有一条路径 ), 则在序列中顶点 vi 必须排在顶点 vj 之前。 在一个有向图中找一个拓扑序列的过程称为拓扑排序。
第 7 章 图
C2
C 1 C 8
C9
C3
C 4
C5
C 6
C7
第 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- 网。
第 7 章 图 如何进行拓扑排序?
方法一:(从图中顶点的入度考虑)
① 从有向图中选择一个没有前驱 (即入度为 0)的顶点并且输出它。
② 从网中删去该顶点和所有以它为尾的弧;
③ 重复上述两步 , 直到图全部顶点输出;或当前图中不再存在没有前驱的顶点。
v1 , v6 , v4 , v3 , v2 , v5 或 v1 , v3 , v2 , v6 , v4 ,v5
第 7 章 图 方法二:(从图中顶点的出度考虑,得到逆拓扑序列)
① 从有向图中选择一个出度为 0的顶点并且输出它。
② 从网中删去该顶点和所有以它为头的弧;
③ 重复上述两步 , 直到图全部顶点输出;或当前图中不再存在出度为 0的顶点。
方法三:当有向图中无环时,利用深度优先遍历进行拓扑排序
从某点出发进行DFS遍历时,最先退出 DFS 函数的顶点即出
度为 0的顶点,是拓扑序列中最后一个顶点。按退出 DFS 函数的
先后记录下来的顶点序列即为逆拓扑序列。问题:判定一个图是否有圈(回路)的方法?
第 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;}
第 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
}
第 7 章 图
作业:
7.5 7.7