280 likes | 469 Views
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;.
E N D
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;
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的一棵生成树。
由深度优先遍历得到的生成树称为深度优先生成树;由广度优先遍历得到的生成树称为广度优先生成树。这样的生成树是由遍历时访问过的n个顶点和遍历时经历的n-1条边组成。由深度优先遍历得到的生成树称为深度优先生成树;由广度优先遍历得到的生成树称为广度优先生成树。这样的生成树是由遍历时访问过的n个顶点和遍历时经历的n-1条边组成。 对于非连通图,每个连通分量中的顶点集和遍历时走过的边一起构成一棵生成树,各个连通分量的生成树组成非连通图的生成森林。 问题:以孩子兄弟链表作为森林(或树)的存储结构,如何建立无向图的深度优先生成森林或广度优先生成森林?
深度优先生成森林 广度优先生成森林
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); } }
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 }
3. 最小生成树 在一个连通网的所有生成树中, 各边的代价之和最小的那棵生成树称为该连通网的最小代价生成树(Minimum Cost Spanning Tree),简称为最小生成树(MST)。 最小生成树有如下重要性质(MST性质): 设 N=(V, {E}) 是一连通网,U 是顶点集V的一个非空子集。 若(u , v)是一条具有最小权值的边, 其中u∈U, v∈V-U, 则存在一棵包含边(u , v)的最小生成树。
用反证法来证明这个MST性质: 假设不存在这样一棵包含边(u , v)的最小生成树。 任取一棵最小生成树T,将(u , v)加入T中。根据树的性质,此时T中必形成一个包含(u , v)的回路, 且回路中必有一条边(u′, v′)的权值大于或等于(u , v)的权值。 删除(u′, v′),则得到一棵代价小于等于T的生成树T′,且T′为一棵包含边(u , v)的最小生成树。 这与假设矛盾, 故该性质得证。 利用MST性质来生成一个连通网的最小生成树。普里姆(Prim)算法和克鲁斯卡尔(Kruskal)算法便是利用了这个性质。
普里姆算法 (Prim算法,又称边割法) 1957年由Prim提出 假设N=(V, {E})是连通网,TE为最小生成树中边的集合。 Step1: 初始U={u0}(u0∈V), TE=φ; Step2: 在所有u∈U, v∈V-U的边中选一条代价最小的边(u0, v0) 并入集合TE,同时将v0并入U; Step3: 重复Step2,直到U=V为止。 此时,TE中必含有n-1条边,则T=(V, {TE})为N的最小生成树。 从一个平凡图开始,普利姆算法逐步增加U中的顶点, 可称为“加点法”。
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})
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 }; } }
克鲁斯卡尔算法(避圈法)Kruskal于1956年提出 假设G=(V,E)是一个具有n个顶点的带权连通无向图,T=(U,TE)是G的最小生成树,则构造最小生成树的步骤如下: Step1:T=(V,{}),有n个顶点而无边的非连通图; Step2:在E中选择代价最小的边,若该边依附的顶点落在T中不同的连通分量上,则将此边加入TE;否则舍弃, 而选择下一条代价最小的边; Step3: 若T中有n-1条边,结束;否则转step2. 从一个零图开始,克鲁斯卡尔算法逐步增加生成树的边, 与普里姆算法相比,可称为“加边法”。
为了简便,在实现克鲁斯卡尔算法Kruskal()时,参数E存放图G中的所有边,假设它们是按权值从小到大的顺序排列的。n为图G的顶点个数,e为图G的边数。为了简便,在实现克鲁斯卡尔算法Kruskal()时,参数E存放图G中的所有边,假设它们是按权值从小到大的顺序排列的。n为图G的顶点个数,e为图G的边数。 typedef struct { int u; /*边的起始顶点*/ int v; /*边的终止顶点*/ int w; /*边的权值*/ } Edge;
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++; /*扫描下一条边*/ } }
完整的克鲁斯卡尔算法应包括对边按权值递增排序,上述算法假设边已排序的情况下,时间复杂度为O(n2)。完整的克鲁斯卡尔算法应包括对边按权值递增排序,上述算法假设边已排序的情况下,时间复杂度为O(n2)。 如果给定的带权连通无向图G有e条边,n个顶点,采用堆排序(在第10章中介绍)对边按权值递增排序,那么用克鲁斯卡尔算法构造最小生成树的时间复杂度降为O(eloge)。 由于它与n无关,只与e有关,所以说克鲁斯卡尔算法适合于求边稀疏的网的最小生成树。
7.5 有向无环图及其应用 1. 拓扑排序(Topological Sort) 设G=(V,E)是一个具有n个顶点的有向图,V中顶点序列v1,v2,…,vn称为一个拓扑(有序)序列,当且仅当该顶点序列满足下列条件:若<vi,vj>是图中的弧(即从顶点vi到vj有一条路径),则在序列中顶点vi必须排在顶点vj之前。 在一个有向图中找一个拓扑序列的过程称为拓扑排序。
拓扑序列: C1, C2, C3, C4, C5, C8, C9, C7, C6。 拓扑序列: C1, C2, C3, C8, C4, C5, C9, C7, C6 。 用顶点表示活动,用弧表示活动间的优先关系的有向图,称为顶点表示活动的网(Activity On Vertex Network),简称为AOV-网。
如何进行拓扑排序? • 方法一:(从图中顶点的入度考虑) • 从有向图中选择一个没有前驱(即入度为0)的顶点并且输出它。 • 从网中删去该顶点和所有以它为尾的弧; • 重复上述两步,直到图全部顶点输出;或当前图中不再存在没有前驱的顶点。 v1,v6,v4,v3,v2,v5 或 v1,v3,v2,v6,v4,v5
方法二:(从图中顶点的出度考虑,得到逆拓扑序列)方法二:(从图中顶点的出度考虑,得到逆拓扑序列) • 从有向图中选择一个出度为0的顶点并且输出它。 • 从网中删去该顶点和所有以它为头的弧; • 重复上述两步,直到图全部顶点输出;或当前图中不再存在出度为0的顶点。 方法三:当有向图中无环时,利用深度优先遍历进行拓扑排序 从某点出发进行DFS遍历时,最先退出DFS函数的顶点即出 度为0的顶点,是拓扑序列中最后一个顶点。按退出DFS函数的 先后记录下来的顶点序列即为逆拓扑序列。 问题:判定一个图是否有圈(回路)的方法?
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; }
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.5 7.7