连通图的割点、割边(桥)、块、缩点,有向图的强连通分量

连通图的割点、割边(桥)、块、缩点,有向图的强连通分量
连通图的割点、割边(桥)、块、缩点,有向图的强连通分量

连通图的割点、割边(桥)、块、缩点,有向图的强连通分量

一、基本概念

无向图

割点:删掉它之后(删掉所有跟它相连的边),图必然会分裂成两个或两个以上的子图。

块:没有割点的连通子图

割边:删掉一条边后,图必然会分裂成两个或两个以上的子图,又称桥。

缩点:把没有割边的连通子图缩为一个点,此时满足任意两点间都有两条路径相互可达。

求块跟求缩点非常相似,很容易搞混,但本质上完全不同。割点可以存在多个块中(假如存在k个块中),最终该点与其他点形成k个块,对无割边的连通子图进行缩点后(假设为k个),新图便变为一棵k个点由k-1条割边连接成的树,倘若其中有一条边不是割边,则它必可与其他割边形成一个环,而能继续进行缩点。

有割点的图不一定有割边,如:

3是割点,分别与(1,2)和(4,5)形成两个无割点的块

有割边的图也不定有割点,如:

w(1,2)为割边,

有向图

强连通分量:有向图中任意两点相互可达的连通子图,其实也相当于无向图中的缩点

二、算法

无向图

借助两个辅助数组dfn[],low[]进行DFS便可找到无向图的割点和割边,用一个栈st[]维护记录块和“缩点”后连通子图中所有的点。

dfn[i]表示DFS过程中到达点i的时间,low[i]表示能通过其他边回到其祖先的最早时间。low[i]=min(low[i],dfn[son[i]])

设 v,u之间有边w(v,u),从v->u:

如果low[u]>=dfn[v],说明v的儿子u不能通过其他边到达v的祖先,此时如果拿掉v,则必定把v的祖先和v的儿子u,及它的子孙分开,于是v便是一个割点,v和它的子孙形成一个块。

如果low[u]>dfn[v]时,则说明u不仅不能到达v的祖先,连v也不能通过另外一条边直接到达,从而它们之间的边w(v,u)便是割边,求割边的时候有一个重边的问题要视情况处理,如果v,u之间有两条无向边,需要仍视为割边的话,则在DFS的时候加一个变量记录它的父亲,下一步遇到父结点时不扩展回去,从而第二条无向重边不会被遍历而导致low[u]==dfn[v] ,而在另外一些问题中,比如电线连接两台设备A,B 如果它们之间有两根电线,则应该视为是双连通的,因为任何一条电线出问题都不会破坏A和B之间的连通性,这个时候,我们可以用一个used[]数组标记边的id,DFS时会把一条无向边拆成两条有向边进行遍历,但我们给它们俩同一个id号,在开始遍历v->u前检查它的id是否在上一次u->v 时被标记,这样如果两点之间有多条边时,每次遍历都只标记其中一条,还可以通过其他边回去,形成第二条新的路

求割点的时候,维护一个栈st 每遍历到一个顶点v则把它放进去,对它的子孙u如果dfn[u]为0,则表示还没有遍历到则先DFS(u),之后再判断low[u]和dfn[v],如果low[u]>=dfn[v],则把栈中从栈顶到v这一系列元素弹出,这些点与v 形成一个块,如果u的子孙x也是一个割点,这样做会不会错把它们和v,u放在一起形成一个块呢,这种情况是不会发生的,如果发现x是一个割点,则DFS 到x那一步后栈早就把属于x的子孙弹出来了,而只剩下v,u的子孙,它们之间不存在割点,否则在回溯到v之前也早就提前出栈了!画一个图照着代码模拟一下可以方便理解。

求割边也是一样的。

有向图

有向图强连通分量的算法有两个,一个是Kosaraju,另一个是Tarjan,前者需要两次DFS,代码量偏大但容易理解,后者只需要一次DFS和维护一个栈便可以,实现简单,详见这里>>

强连通分量 Kosaraju PK Tarjan

2010-04-22 16:23

Kosaraju算法

对每个不在树中的点开始DFS一次,并记录离开各点的时间,这里是离开的时间,而不是到达时的,比如有图1->2 2->3 则1,2,3分别对应的时间是3 2 1,因为3没有出边,所以最先离开,其次是2,最后是1,

DFS后,在同一棵树中的点,如果dfn[v]>dfn[u]则说明点从v有可能到达u,而这棵树中的dfn[]最大的点,肯定可以到达每个点,从而在原图的逆图中,每次都选没有访问过的最大的dfn值开始DFS,如果可达点x 则说明它们是强连通的

void DFS_T(int u)

{

int i,v;

if(used[u])return ;

used[u]=1;id[u]=scc;

for(i=q[u];i!=-1;i=Tedge[i].pre)

{

v=Tedge[i].d;

if(!used[v])DFS_T(v);

}

}

void DFS(int v){

int i,u;

if(used[v])return ;

used[v]=1;

for(i=p[v];i!=-1;i=edge[i].pre)

{

u=edge[i].d;

if(!used[u])DFS(u);

}

order[++num]=v;

}

int Kosaraju()

{

int i,j,k,v,u;

memset(used,0,sizeof(used));num=0;

for(i=1;i<=n;++i)if(!used[i])DFS(i);

memset(used,0,sizeof(used));

memset(id,0,sizeof(id));scc=0;

for(i=num;i>=1;--i)if(!used[order[i]])scc++,DFS_T(order[i]);

}

Tarjan算法

dfn[v]记录到达点v的时间,跟上面的离开不同,low[v]表示通过它的子结点可以到达的所有点中时间最小值,即low[i]=min(low[i],low[u]),u为v的了孙,初始化时low[v]=dfn[u]。如果low[v]比dfn[v]小,说明v可以通过它的子结点u,u1,u2...到达它的祖先v',则存在环,这个环上所有的点组成的子图便是一个强连通分量。换一个角度看,如果当low[v]==dfn[v]时,则它的子树中所有low[u]==dfn[v]的点都与v构成一个环,维护一个栈,DFS过程中,每遍历一个点则把它放入栈中,当发现low[v]==dfn[v]则依次把栈里的元素都弹出来,当栈顶元素为v时结束,这些点便构成一个以v为树根的强连通分量。

仍以上图为例,首先遍历点1,并dfn[1]=low[1]=++num, num表示按先后访问时间编号 ,同时1入栈

a.从3深入 dfn[3]=low[3]=2; 3入栈

b.从3到5 dfn[5]=low[5]=3; 5入栈

c.从5到6 dfn[6]=low[6]=4; 6入栈

d.发现6没有子结点可走,这时判断dfn[6]==low[6],于是开始弹栈,当遇到6时则break,即共弹出一个元素,于是6便是一个强连通分量

e.回溯至5,同样判断和弹栈,发现5也是一个强连通分量

f.再回溯至3,发现有边3->4,dfn[4]=low[4]=5,4入栈

g.4有边到1,由于1已经在栈里面,所以用dfn[1]更新low[4] 即

low[4]=min(low[4],dfn[1])=1

h.回溯更新4的父亲3的low值 low[3]=min(low[3],low[4])=1

i.再回溯至1,发现有边1->2 继续深度遍历,2入栈,发现它的子结点4已经在栈中,直接更新low[2]=min(low[2],dfn[4]);

j.回溯至1,从而1所有出发的边都走了一遍,这时再比较low[1]与dfn[1],发现相等,于是开始弹栈,找到2,4,3,1这四个元素,构成一个连通分量。

void Tarjan(int v){

dfn[v]=low[v]=++num;

used[v]=1;

st[++numSt]=v;

for(int i=p[v];i!=-1;i=edge[i].pre){

int u(edge[i].d);

if(!dfn[u])//还没有标号的点

{

Tarjan(u);//先遍历它的子结点

GetMin(low[v],low[u]);//用子结点更新当前点的low值

}

else if(used[u]&&GetMin(low[v],dfn[u]));

}

if(dfn[v]==low[v]){

scc++;

while(1){

int u(st[numSt--]);

id[u]=scc;

used[u]=0;

if(v==u)break;

}

}

}

int main(){

for(int i=1;i<=n;++i)if(!dfn[i])Tarjan(i);

}

这里有一个疑问,为什么当发现一个点v的子结点u已经在栈中时用dfn[u]来更新low[v],而不是用low[u],感觉好象两个都可以用,因为只要保证low[v]尽可能变小就行了,

三、代码实现

割点和块

求割点的时候由于不知道最开始选的树根是不是只有一个儿子,这样在DFS过来中不会满足low[u]>=dfn[v]而判为割点,但有两个或两个以上儿子的根肯定也是一个割点,所以要特判!

void CutBlock(int v){

dfn[v]=low[v]=++cnt;

st[++top]=v;

for(int i=p[v];i!=-1;i=edge[i].pre){

int u(edge[i].d);

if(dfn[u]==0){

CutBlock(u);

GetMin(low[v],low[u]);

if(low[u]>=dfn[v]){ //V是一个割点

block[0]=0;

while (true) {

block[++block[0]]=st[top];

if (st[top--] == u) //只能弹到u为止,v还可以在其他块中

break;

}

block[++block[0]]=v;//割点属于多个块,一定要补进去

Count(block);

}

}

else GetMin(low[v],dfn[u]);

}

}

割边和缩点

void CutEdge(int v,int fa){

dfn[v]=low[v]=++cnt;

st[++top]=v;

for(int i=p[v];i!=-1;i=edge[i].pre){

int u(edge[i].d);

if(u==fa)continue;

if(!dfn[u]){

CutEdge(u,v);

GetMin(low[v],low[u]);

if(low[u]>dfn[v]){//边v->u为一条割边

cutE[++numE]=E(v,u);

// 将u及与它形成的连通分量的所有点存起来

++numB;

while(1){

id[st[top]]=numB;

if(st[top--]==u)break;

}

}

}

else GetMin(low[v],dfn[u]);

}

}

有向图强连通分量

void Tarjan(int v){

dfn[v]=low[v]=++num;

used[v]=1;

st[++numSt]=v;

for(int i=p[v];i!=-1;i=edge[i].pre){

int u(edge[i].d);

if(!dfn[u])//还没有标号的点

{

Tarjan(u);//先遍历它的子结点

GetMin(low[v],low[u]);//用子结点更新当前点的low值 }

else if(used[u]&&GetMin(low[v],dfn[u]));

}

if(dfn[v]==low[v]){

scc++;

while(1){

int u(st[numSt--]);

id[u]=scc;

used[u]=0;

if(v==u)break;

}

}

}

这里需要注意一个地方,上面标记为紫色的那行代码,相比上面两个代码,这里加了一个used[]判断点是否在栈中,为什么前面的不要,而这里需要呢,举个例子

根据dfn可以看出搜索的顺序是1->2->5->6形成一个强连通分量(2,5,6),于是开始退栈,回溯到1从3出发到达4,此时如果直接用dfn[2]更新low[4]的话,会得到low[4]=2,变小后而与dfn[4]不再相等,不能退栈,这与最后的4形成一个单独强连通分量是不符合的,所以,不在栈中的点,不能用来更新当前点的low[]值,为什么无向图不用标记呢,那时因为,边是无向的,有边从4->2同时也必有边2->4由于2之前被标记过,而遍历到当前结点4又不是通过w(2,4)这条边过来的,则必还存在另一条路径可以使2和4是相通的,(即图中的4-3-1-2),从而2,4是双连通的。

其实以上三个算法都源于Tarjan,只是根据dfn[]和low[]判断条件不同而得到不同的结果,无限Orz Trajan !!!

于是,顺便总结一下LCA的离线算法

离线是指把所有的问题都存起来,类似邻接表的形式,能根据点v找到它关的点u,处理完后一次性回答所有的答案。

DFS到v时,用used[]标记为已访问,下面分两部分完成

1、在Q个查询中对所有与v相关连的ui,uj,uk,如果检查发现used[]为真,则说明它们的最近公共祖先为ui当前能往上最大程度找到的祖先,这个可借助并查集实现,记录结果用以后面输出。

2、对v所有子结点u(不同于上面的u),进行DFS() ,DFS结束后,设置u的父亲为v,即fa[u]=v;

时间复杂度为O(m+Q ),Q为查询的总数,dist[]记录根到当前点的距离,如果最后要求任意两点v和u之间的距离,则为dist[v]+dist[u]-2*dist[lca(v,u)]

void LcaTarjan(int v){

used[v]=1;

fa[v]=v;

for(int i=q[v];i!=-1;i=e[i].pre){ //对跟v相关每个问题,尝试进行回答

int u(e[i].d),id(e[i].id);

if(used[u])ans[id]=Find(u);

}

for(int i=p[v];i!=-1;i=edge[i].pre){

int u(edge[i].d),w(edge[i].w);

if(!used[u]){

dist[u]=dist[v]+w;

LcaTarjan(u);

fa[u]=v;

}

}

}

四、例题

pku1523>>

先求割点,第二问其实就是求块,一个割点存在k个块中,删掉后,便形成k

个子图

pku2942>>

求块后,对每块有:如果存在奇圈,则可以分开开会,否则全T掉,判断奇圈可以用DFS二分染色的方法,当前点染为白色,它所有相邻点染为黑色,如果最后发现某条边两个端点同色,则存在奇圈。

pku3694>>

求割边后,并标记,这时新图形成一棵树,但并不需要缩点,否则反而不好处理,每加一条边w(v,u)进去,必会形成一个圈,剩下的问题但是如何找圈,事先求出v,u的最小公共祖先,加入边w(v,u)后,则这个圈的一部分便是从v到lca(v,u)

之间的树边,另一部分是u到lca(v,u)之间的树边,由于一个图中割边的总条数不会超过n,所以可用割边关联的两个顶点中的一个来记录它的位置,这样在沿v或u向lca(v,u)往上找时,快速判断它与它父亲之间相连的边是否为割边,是的话ans-- 并标记为非,因为w(v,u)的加入形成了环,环中原来所有的割边都会变成非割边。用fa[v]表示v的父亲,set[v]表示v的祖先,虽然初始都表示v的父亲,但在LCA时要区分使用,一个只记录它的直接父亲,另一个并查集时压缩路径会改变。

pku 3352>>pku3177>>

求割边,缩点后,形成一棵树,统计度为1的点个数t,需要连的边数则为

(t+1)/2 ,pku3177只是多了重边处理,方法见上。

hdu3394>> 求块,如果一个块的顶点数等于边数,则这个块只有一个环,如果边数大于点数,则必有多个环,容易知道在一个K环的块中,每条边也必属于K

个环,这样可以计算出在一个环和多个环里的边总数,剩下的便是不在环中的边。hdu 3394 Railway 无向图求块

2010-06-02 22:07

hdu 3394 >>

求在0个环、1个环、多个环里的边的条数

问题转化为无向图求块,即缩块。块是不存在割点的连通子图,如果一个块的顶点数等于边数,则这个块只有一个环,如果边数大于点数,则必有多个环,容易知道在一个K环的块中,每条边也必属于K个环,这样可以计算出在一个环和多个环里的边总数,剩下的便是不在环中的边。

#define arr 10010

#define brr 500010

struct Edge{

int d,pre;

Edge(){}

Edge(int d1,int pre1):d(d1),pre(pre1){}

}edge[brr];

int p[arr];

int pn;

int dfn[arr];

int low[arr];

bool used[arr];

int cnt;

int block[arr];

int n,m;

int none,one,two;

int st[arr];

int top;

int fa[arr];

void Insert(int v,int u){

edge[++pn]=Edge(u,p[v]);p[v]=pn;

edge[++pn]=Edge(v,p[u]);p[u]=pn;

}

void Count(int* block){

FF(i,block[0])used[block[i]]=1;

int sum(0);

FF(i,block[0]){

int v(block[i]);

for(int j=p[v];j!=-1;j=edge[j].pre){

int u(edge[j].d);

if(used[u])sum++;

}

}

sum/=2;

if(sum==block[0])//点和边总数一样多,刚好一个环 one+=sum;

else if(sum>block[0])//边比点多,存在多个环

two+=sum;

else none+=sum;

FF(i,block[0])used[block[i]]=0;

}

void DFS(int v){

dfn[v]=low[v]=++cnt;

st[top++]=v;

for(int i=p[v];i!=-1;i=edge[i].pre){

int u(edge[i].d);

if(dfn[u]==0){

fa[u]=v;

DFS(u);

GetMin(low[v],low[u]);

if(low[u]>=dfn[v]){

block[0]=0;

while (true) {

block[++block[0]]=st[top-1]; if (st[--top] == u)

break;

}

block[++block[0]]=v;

Count(block);

}

}

else if(u!=fa[v]) GetMin(low[v],dfn[u]);

}

}

void Work(){

clr(dfn,0);clr(low,0);

clr(used,0);

cnt=none=one=two=top=0;

FF(i,n){

if(dfn[i]==0)DFS(i);

}

printf("%d %d\n",none,two);

}

int main(){

while(scanf("%d%d",&n,&m)!=EOF){ if(n+m==0)break;

clr(p,-1);pn=0;

FF(i,m){

int v,u;

scanf("%d%d",&v,&u);

v+=1;u+=1;

Insert(v,u);

}

Work();

}

return 0;

}

08-图论-离散数学讲义-海南大学(共十一讲)

08-图论-离散数学讲义-海南大学(共十一讲)

8.图论Topics in Graph Theory §8.1 图Graphs G= V={v 1,v 2 ,······,v n} 顶点vertex集。 E={ e | e=( v i , v j ), v i ,v j ∈V, v i≠v j}无向边edge集。 γ(e)={ v i, v j}, e的端点end points集。 简写为G=(V,E)。 TD(v i)顶点v i的度数degree:连接到v i的边的条数。连接一个顶点的圈loop算两度。 孤立点isolated vertex:度数为0的点。 两个顶点相邻adjacent:有一边相连。 定理1. (握手定理) TD= TD(v i)=2m. 推论. 任意图的奇数度顶点必有偶数多个。 完全图complete graph: 任意两点都相邻简单图。 定理2. n个顶点的完全图有n(n-1)/2条边。正则图regular graph:每个顶点都有相同的度数。E={|v i ,v j∈V}有向边集有向图 有向边

v i 起点弧尾, v j 终点弧头 TD(v i ):顶点的度degree: 以v i 为端点的边的数目。 OD(vi): 出度, 以v i 为起点的边的数目。 ID(v i ): 入度,以v i 为终点的边的数目。 TD(v i )= OD(vi)+ ID(v i ) OD=ID, TD=2|E|,E| =1/2*TD TD OD ID 为整个图的总度,出度,入度数。 路径path : v i ······v j , 以v i 为起点v j 为终点的顶点序列,相邻顶点相邻。 路径的长length : 路径上边的数目, 简单路径simple path :点都不重复的路径, 回路circuit : 首尾相接的路径, 简单回路simple circuit : 除起点和终点以外都不重复的路径, v i v j 连通connected : 有路径 v i ······v j 相连。 连通图: 任意两点都连通的图。 例 左图a,c,d,g 是简单路径 右图a,d,b,c,e 是简单路径。 f,e,a,d,b,a,f 是简单回路。 f,e,d,c,e,f 不是简单回路。 b f g d c e a f d c a e b

图的连通性总结

图的连通性总结 boboo 目录 1.图的遍历及应用 1.1.DFS遍历 1.2.DFS树的边分类 1.3.DFS树的性质 1.4.拓补排序 1.5.欧拉回路 2.无向图相关 2.1求割顶 2.2求图的桥 2.3求图的块 3.有向图相关 3.1求强连通分量(SCC划分) 3.2求传递闭包 4.最小环问题

一、图的遍历及应用 1.1 DFS遍历 DFS是求割顶、桥、强连通分量等问题的基础。 DFS对图进行染色, 白色:未访问; 灰色:访问中(正在访问它的后代); 黑色:访问完毕 一般在具体实现时不必对图的顶点进行染色,只需进行访问开始时间和访问结束时间的记录即可,这样就可以得出需要的信息了。 -发现时间D[v]:变灰的时间 -结束时间f[v]:变黑的时间 -1<=d[v]

浅谈强连通分量与拓扑排序的应用

浅谈强连通分量与拓扑排序的应用 浙江唐文斌摘要 强连通分量与拓扑排序是图论中最基础的算法之一。本文选取了两个简单但富有代表性的例子,说明这两个算法在一类图论问题中的应用。 [例一]Going from u to v or from v to u?1 给定一个有向图,问是否对于图中的任意两点u、v,总是存在u到v可达或者v到u (下文中将以a→b表示a到b可达)可达。图中点数不超过1000,边数不超过6000。 算法分析 题目描述很简单,我们最直观的想法就是求一个传递闭包,然后对于任意两点a、b判断是否a→b或者b→a。然而在本题中点数多达1000,传统的求传递闭包方法Floyd是行不通的。题目中的规模很小,似乎我们可以枚举起点s,并且从s开始对图进行一次宽度优先搜索,这样我们可以在O(N*(N+M))时间内求得传递闭包。似乎这个办法可行,但事实上,在本题中虽然规模小,但是数据组数高达200组,所以这个方法也是必然超时的。 我们抛开传递闭包,重新来看问题。题目中问是否对于任意两点都在至少一个方向上可达。那么如果两个点u、v,u→v且v→u,它们当然是符合要求的。所以我们第一个想法就是找到一个点集,该点集内所有点两两可达。由于其内部两两可达,所以我们可以将其缩成一个点,仅保留连向外界的边,并不会影响问题的本质。这个点集,就是强连通分量。所以我们的第一步操作就是:求图中所有的极大强连通分量,将每一个强连通分量缩成一个点,保留不同分量间的连边信息,得到一个新图。 我们对原图进行强连通分量缩点得到新图有什么好处呢?在这个过程中,我们将一些冗余信息进行了处理,得到的新图具有一个很重要的性质:无环(拓扑有序)。因为如果有环存在,那么这些环上的点都是互相可达的,所以它们应该同属于一个极大强连通分量,将被缩成一个点。所以我们现在的问题就是对于新图——一个拓扑有序的图,判断图中是否任意两点是否在至少一个方向上可达。 如果一个拓扑有序的图满足要求,那么它将拥有一些什么性质呢?我们先来看一些小规模的情况: (1)如果图只有一个点,则必然满足条件 (2)如果图中包含两个点,那么必须从一个点到另一个点有边相连。不妨设为a→b (显然b到a不可达)。 (3)如果图中包含3个点,不妨设第三个为c。那么必须满足c→a或者b→c。 通过上面3个情况的观察,我们大致就有了一个猜想: 1Poj Monthly Special – Jiajia&Wind’s story , problem G (POJ2762)

数据结构图习题分解

第7章图 一、单项选择题 1.在一个无向图G中,所有顶点的度数之和等于所有边数之和的______倍。 A.l/2 B.1 C.2 D.4 2.在一个有向图中,所有顶点的入度之和等于所有顶点的出度之和的______倍。 A.l/2 B.1 C.2 D.4 3.一个具有n个顶点的无向图最多包含______条边。 A.n B.n+1 C.n-1 D.n(n-1)/2 4.一个具有n个顶点的无向完全图包含______条边。 A.n(n-l) B.n(n+l) C.n(n-l)/2 D.n(n-l)/2 5.一个具有n个顶点的有向完全图包含______条边。 A.n(n-1) B.n(n+l) C.n(n-l)/2 D.n(n+l)/2 6.对于具有n个顶点的图,若采用邻接矩阵表示,则该矩阵的大小为______。 A.n B.n×n C.n-1 D.(n-l) ×(n-l) 7.无向图的邻接矩阵是一个______。 A.对称矩阵B.零矩阵

C.上三角矩阵D.对角矩阵 8.对于一个具有n个顶点和e条边的无(有)向图,若采用邻接表表示,则表头向量的大小为______。 A.n B.e C.2n D.2e 9.对于一个具有n个顶点和e条边的无(有)向图,若采用邻接表表示,则所有顶点邻接表中的结点总数为______。 A.n B.e C.2n D.2e 10.在有向图的邻接表中,每个顶点邻接表链接着该顶点所有______邻接点。 A.入边B.出边 C.入边和出边D.不是入边也不是出边 11.在有向图的逆邻接表中,每个顶点邻接表链接着该顶点所有______邻接点。 A.入边B.出边 C.入边和出边D.不是人边也不是出边 12.如果从无向图的任一顶点出发进行一次深度优先搜索即可访问所有顶点,则该图一定是______。 A.完全图B.连通图 C.有回路D.一棵树 13.采用邻接表存储的图的深度优先遍历算法类似于二叉树的______算法。 A.先序遍历B.中序遍历 C.后序遍历 D.按层遍历

连通图的割点、割边(桥)、块、缩点,有向图的强连通分量

连通图的割点、割边(桥)、块、缩点,有向图的强连通分量 一、基本概念 无向图 割点:删掉它之后(删掉所有跟它相连的边),图必然会分裂成两个或两个以上的子图。 块:没有割点的连通子图 割边:删掉一条边后,图必然会分裂成两个或两个以上的子图,又称桥。 缩点:把没有割边的连通子图缩为一个点,此时满足任意两点间都有两条路径相互可达。 求块跟求缩点非常相似,很容易搞混,但本质上完全不同。割点可以存在多个块中(假如存在k个块中),最终该点与其他点形成k个块,对无割边的连通子图进行缩点后(假设为k个),新图便变为一棵k个点由k-1条割边连接成的树,倘若其中有一条边不是割边,则它必可与其他割边形成一个环,而能继续进行缩点。 有割点的图不一定有割边,如: 3是割点,分别与(1,2)和(4,5)形成两个无割点的块 有割边的图也不定有割点,如:

w(1,2)为割边, 有向图 强连通分量:有向图中任意两点相互可达的连通子图,其实也相当于无向图中的缩点 二、算法 无向图 借助两个辅助数组dfn[],low[]进行DFS便可找到无向图的割点和割边,用一个栈st[]维护记录块和“缩点”后连通子图中所有的点。 dfn[i]表示DFS过程中到达点i的时间,low[i]表示能通过其他边回到其祖先的最早时间。low[i]=min(low[i],dfn[son[i]]) 设 v,u之间有边w(v,u),从v->u: 如果low[u]>=dfn[v],说明v的儿子u不能通过其他边到达v的祖先,此时如果拿掉v,则必定把v的祖先和v的儿子u,及它的子孙分开,于是v便是一个割点,v和它的子孙形成一个块。 如果low[u]>dfn[v]时,则说明u不仅不能到达v的祖先,连v也不能通过另外一条边直接到达,从而它们之间的边w(v,u)便是割边,求割边的时候有一个重边的问题要视情况处理,如果v,u之间有两条无向边,需要仍视为割边的话,则在DFS的时候加一个变量记录它的父亲,下一步遇到父结点时不扩展回去,从而第二条无向重边不会被遍历而导致low[u]==dfn[v] ,而在另外一些问题中,比如电线连接两台设备A,B 如果它们之间有两根电线,则应该视为是双连通的,因为任何一条电线出问题都不会破坏A和B之间的连通性,这个时候,我们可以用一个used[]数组标记边的id,DFS时会把一条无向边拆成两条有向边进行遍历,但我们给它们俩同一个id号,在开始遍历v->u前检查它的id是否在上一次u->v 时被标记,这样如果两点之间有多条边时,每次遍历都只标记其中一条,还可以通过其他边回去,形成第二条新的路 求割点的时候,维护一个栈st 每遍历到一个顶点v则把它放进去,对它的子孙u如果dfn[u]为0,则表示还没有遍历到则先DFS(u),之后再判断low[u]和dfn[v],如果low[u]>=dfn[v],则把栈中从栈顶到v这一系列元素弹出,这些点与v 形成一个块,如果u的子孙x也是一个割点,这样做会不会错把它们和v,u放在一起形成一个块呢,这种情况是不会发生的,如果发现x是一个割点,则DFS 到x那一步后栈早就把属于x的子孙弹出来了,而只剩下v,u的子孙,它们之间不存在割点,否则在回溯到v之前也早就提前出栈了!画一个图照着代码模拟一下可以方便理解。 求割边也是一样的。

7.4.1无向图的连通分量和生成树

7.4.1无向图的连通分量和生成树。

void DFSForest(Graph G,CSTree &T) //建立无向图G的深度优先生成森林的 //(最左)孩子(右)兄弟链表T。 { T=NULL; for(v=0;vnextSibling=p; //是其他生成树的根(前一棵的根的“兄弟”)。 q=p; //q指示当前生成树的根。 DFSTree(G,v,p); //建立以p为根的生成树。 }// if(!visited[v]) }// for(v=0;vlchild=p;first=FALSE; }// if(first) else //w是v的其它未被访问的邻接顶点 { //是上一邻接顶点的右兄弟节点。 q->nextsibling=p; }// else q=p; DFSTree(G,w,q); //从第w个顶点出发深度优先遍历图G,建立子生成树q。 }// if(!visited[w]) }// for(w=FirstAdjVex(G,v); }// DFSTree

求一个无向图G的连通分量的个数

《数据结构》实验报告 实验内容:(一)判断一个图有无回路 (二)求一个无向图G的连通分量的个数 一、目的和要求(需求分析): 1、了解图的定义和图的存储结构。 2、熟悉掌握图的邻接矩阵和邻接表。 3、理解图的遍历算法---深度优先搜索和广度优先搜索。 4、学会编程处理图的连通性问题。 二、程序设计的基本思想,原理和算法描述: (包括程序的结构,数据结构,输入/输出设计,符号名说明等) 判断一个图有无回路: 在程序设计中,先必须确定所要创建的图是有向还是无向,是图还是网,其次再根据各自的特点,用连接表来实现创建。 在有向图中,先找出入度为0的顶点,删除与这个顶点相关联的边(出边),将与这些边相关的其它顶点的入度减1,循环直到没有入度为0的顶点。如果此时还有未被删除的顶点,则必然存在环路,否则不存在回路。 无向图则可以转化为: 如果存在回路,则必然存在一个子图,是一个回路。因此回路中所有定点的度>=2。 第一步:删除所有度<=1的顶点及相关边,并将另外与这些边相关的其它顶点的度减1。 第二步:将度数变为1的顶点排入队列,并从该队列中(使用栈)取出一个顶点,并重复步骤一。 如果最后还有未删除的顶点,则存在回路,否则没有。 求一个无向图G的连通分量的个数: 用连接表创建图,对于非连通图,则需从多个顶点出发进行搜索,而每一次从一个新的起始点出发进行搜索过程中得到的顶点访问序列恰为其各个连通分量中的顶点集。所以在设计中,为了统计出无向图中的连通分量个数,则因在其深度优先所搜无向图时对函数DFSTraverse(ALGraph G)调用DFS次数进行统计,其结果便为无向图中连通分量个数。 三、调试和运行程序过程中产生的问题及采取的措施: 在调试和运行求一个无向图G的连通分量的个数程序时,由于执行语句块 void DFSTraverse(ALGraph G)先于void DFS(ALGraph G,int v), 而void DFSTraverse(ALGraph G)内调用了DFS( ),因此计算机无法正确运行,将两者顺序进行了交换,程序便实现了其功能,且运行正常。 四、源程序及注释:

有向图的强连通分量算法

有向图的强连通分量 分类:C/C++程序设计2009-04-15 16:50 2341人阅读评论(1) 收藏举报最关键通用部分:强连通分量一定是图的深搜树的一个子树。 一、Kosaraju算法 1.算法思路 基本思路: 这个算法可以说是最容易理解,最通用的算法,其比较关键的部分是同时应用了原图G和反图G T。(步骤1)先用对原图G进行深搜形成森林(树),(步骤2)然后任选一棵树对其进行深搜(注意这次深搜节点A能往子节点B走的要求是E AB存在于反图G T),能遍历到的顶点就是一个强连通分量。余下部分和 原来的森林一起组成一个新的森林,继续步骤2直到没有顶点为止。7 改进思路: 当然,基本思路实现起来是比较麻烦的(因为步骤2每次对一棵树进行深搜时,可能深搜到其他树上去,这是不允许的,强连通分量只能存在单棵树中(由开篇第一句话可知)),我们当然不这么做,我们可以巧妙的选择第二深搜选择的树的顺序,使其不可能深搜到其他树上去。想象一下,如果步骤2是从森林里选择树,那么哪个树是不连通(对于G T来说)到其他树上的

呢?就是最后遍历出来的树,它的根节点在步骤1的遍历中离开时间最晚,而且可知它也是该树中离开时间最晚的那个节点。这给我们提供了很好的选择,在第一次深搜遍历时,记录时间i离开的顶点j,即numb[i]=j。那么,我们每次只需找到没有找过的顶点中具有最晚离开时间的顶点直接深搜(对于G T来说)就可以了。每次深搜都得到一个强连通分量。 隐藏性质: 分析到这里,我们已经知道怎么求强连通分量了。但是,大家有没有注意到我们在第二次深搜选择树的顺序有一个特点呢?如果在看上述思路的时候,你的脑子在思考,相信你已经知道了!!!它就是:如果我们把求出来的每个强连通分量收缩成一个点,并且用求出每个强连通分量的顺序来标记收缩后的节点,那么这个顺序其实就是强连通分量收缩成点后形成的有向无环图的拓扑序列。为什么呢?首先,应该明确搜索后的图一定是有向无环图呢?废话,如果还有环,那么环上的顶点对应的所有原来图上的顶点构成一个强连通分量,而不是构成环上那么多点对应的独自的强连通分量了。然后就是为什么是拓扑序列,我们在改进分析的时候,不是先选的树不会连通到其他树上(对于反图GT来说),也就是后选的树没有连通到先选的树,也即先出现的强连通分量收缩的点只能指向后出现的强连通分量收缩的点。那么拓扑序列不是理所当然的吗?这就是Kosaraju算法的一个隐藏性质。

数据结构 第六章 图 练习题及答案详细解析(精华版)

图 1. 填空题 ⑴ 设无向图G中顶点数为n,则图G至少有()条边,至多有()条边;若G为有向图,则至少有()条边,至多有()条边。 【解答】0,n(n-1)/2,0,n(n-1) 【分析】图的顶点集合是有穷非空的,而边集可以是空集;边数达到最多的图称为完全图,在完全图中,任意两个顶点之间都存在边。 ⑵ 任何连通图的连通分量只有一个,即是()。 【解答】其自身 ⑶ 图的存储结构主要有两种,分别是()和()。 【解答】邻接矩阵,邻接表 【分析】这是最常用的两种存储结构,此外,还有十字链表、邻接多重表、边集数组等。 ⑷ 已知无向图G的顶点数为n,边数为e,其邻接表表示的空间复杂度为()。 【解答】O(n+e) 【分析】在无向图的邻接表中,顶点表有n个结点,边表有2e个结点,共有n+2e个结点,其空间复杂度为O(n+2e)=O(n+e)。 ⑸ 已知一个有向图的邻接矩阵表示,计算第j个顶点的入度的方法是()。 【解答】求第j列的所有元素之和 ⑹ 有向图G用邻接矩阵A[n][n]存储,其第i行的所有元素之和等于顶点i的()。 【解答】出度

⑺ 图的深度优先遍历类似于树的()遍历,它所用到的数据结构是();图的广度优先遍历类似于树的()遍历,它所用到的数据结构是()。 【解答】前序,栈,层序,队列 ⑻ 对于含有n个顶点e条边的连通图,利用Prim算法求最小生成树的时间复杂度为(),利用Kruskal 算法求最小生成树的时间复杂度为()。 【解答】O(n2),O(elog2e) 【分析】Prim算法采用邻接矩阵做存储结构,适合于求稠密图的最小生成树;Kruskal算法采用边集数组做存储结构,适合于求稀疏图的最小生成树。 ⑼ 如果一个有向图不存在(),则该图的全部顶点可以排列成一个拓扑序列。 【解答】回路 ⑽ 在一个有向图中,若存在弧、、,则在其拓扑序列中,顶点vi, vj, vk的相对次序为()。 【解答】vi, vj, vk 【分析】对由顶点vi, vj, vk组成的图进行拓扑排序。 2. 选择题 ⑴ 在一个无向图中,所有顶点的度数之和等于所有边数的()倍。 A 1/2 B 1 C 2 D 4 【解答】C 【分析】设无向图中含有n个顶点e条边,则。

有向图的强连通分量

实验报告 课程名称数据结构 实验项目名称有向图的强连通分量 班级与班级代码14计算机实验班 实验室名称(或课室)实验楼803 专业计算机科学与技术 任课教师 学号: 姓名: 实验日期:2015年12 月03 日 广东财经大学教务处制

姓名实验报告成绩 评语: 指导教师(签名) 年月日说明:指导教师评分后,实验报告交院(系)办公室保存。

一、实验目的与要求 采用邻接表存储的有向图。 二、实验内容 (1)创建N个节点的空图 DiGraph CreateGraph(int NumVertex)//创建一个N个节点的空图 { DiGraph G; G = malloc( sizeof( struct Graph ) ); if( G == NULL ) FatalError( "Out of space!!!" ); G->Table = malloc( sizeof( struct TableEntry ) * NumVertex ); if( G->Table == NULL ) FatalError( "Out of space!!!" ); G->NumVertex = NumVertex; G->NumEdge = 0; int i; for (i=0;iTable[i].Header=MakeEmpty(NULL); G->Table[i].V=i; } return G; } (2)在图G上执行DFS,通过对DFS生成森林的后序遍历对G的顶点编号。 //后序DFS遍历图G,并将节点按后序遍历的顺序编号 int *PostDFS(DiGraph G) { int NumVertex=G->NumVertex; int visited[NumVertex]; int i;

求强连通分量的Kosaraju算法和Tarjan算法的比较 by ljq

求强连通分量的Kosaraju算法和Tarjan算法的比较 一、定义 在有向图中,如果两个顶点vi,vj间有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。如果有向图的每两个顶点都强连通,则称该有向图是一个强连通图。非强连通的有向图的极大强连通子图,称为强连通分量(strongly connected components)。 而对于一个无向图,讨论强连通没有意义,因为在无向图中连通就相当于强连通。 由一个强连通分量内的所有点所组成的集合称为缩点。在有向图中的所有缩点和所有缩点之间的边所组成的集合称为该有向图的缩图。 例子: 原图: 缩图: 上面的缩图中的 缩点1包含:1、2,;缩点2包含:3; 缩点3包含:4;缩点4包含:5、6、7。

二、求强连通分量的作用 把有向图中具有相同性质的点找出来,形成一个集合(缩点),建立缩图,能够方便地进行其它操作,而且时间效率会大大地提高,原先对多个点的操作可以简化为对它们所属的缩点的操作。 求强连通分量常常用于求拓扑排序之前,因为原图往往有环,无法进行拓扑排序,而求强连通分量后所建立的缩图则是有向无环图,方便进行拓扑排序。 三、Kosaraju算法 时间复杂度:O(M+N)注:M代表边数,N代表顶点数。 所需的数据结构:原图、反向图(若在原图中存在vi到vj的有向边,在反向图中就变成为vj到vi的有向边)、标记数组(标记是否遍历过)、一个栈(或记录顶点离开时间的数组)。 算法描叙: 步骤1:对原图进行深度优先遍历,记录每个顶点的离开时间。 步骤2:选择具有最晚离开时间的顶点,对反向图进行深度优先遍历,并标记能够遍历到的顶点,这些顶点构成一个强连通分量。 步骤3:如果还有顶点没有遍历过,则继续进行步骤2,否则算法结束。 hdu1269(Kosaraju算法)代码: #include #include const int M=10005; struct node { int vex; node *next; }; node *edge1[M],*edge2[M]; bool mark1[M],mark2[M]; int T[M],Tcnt,Bcnt; void DFS1(int x)

数据结构 无向图的存储和遍历

《数据结构》实验报告 ◎实验题目:无向图的存储和遍历 ◎实验目的:1、掌握使用Visual C++6.0上机调试程序的基本方法; 2、掌握图的邻接表存储结构和深度优先遍历的非递归算法。 3、提高自己分析问题和解决问题的能力,在实践中理解教材上的理论。 ◎实验内容:建立有10个顶点的无向图的邻接表存储结构,然后对其进行深度优先遍历,该无向图可以是无向连通图或无向非连通图。 一、需求分析 1、输入的形式和输入值的范围:根据提示,首先输入图的所有边建立邻接表存储结构,然后输入遍历的起始顶点对图或非连通图的某一连通分量进行遍历。 2、输出的形式:输出对该图是连通图或非连通图的判断结果,若是非连通图则输出各连通分量的顶点,之后输出队连通图或非连通图的某一连通分量的遍历结果。 3、程序所能达到的功能:输入图的所有边后,建立图的邻接表存储结构,判断该图是连通图或非连通图,最后对图进行遍历。 4、测试数据: 输入10个顶点(空格分隔):A B C D E F G H I J 输入边的信息(格式为x y):AB AC AF CE BD DC HG GI IJ HJ EH 该图为连通图,请输入遍历的起始顶点:A 遍历结果为:A F C D B E H J I G 是否继续?(是,输入1;否,输入0):1 输入10个顶点(空格分隔):A B C D E F G H I J 输入边的信息(格式为xy):AB AC CE CA AF HG HJ IJ IG 该图为非连通图,各连通分量中的顶点为: < A F C E B > < D > < G I J H > 输入第1个连通分量起始顶点:F 第1个连通分量的遍历结果为:F A C E B 输入第2个连通分量起始顶点:I 第2个连通分量的遍历结果为:I G H J 输入第3个连通分量起始顶点:D 第3个连通分量的遍历结果为:D 是否继续?(是,输入1;否,输入0):0 谢谢使用! Press any key to continue 二概要设计 1、邻接表是图的一种顺序存储与链式存储结构结合的存储方法。邻接表表示法类似于树的孩子链表表示法。就是对图G中的每个顶点Vi,将所有邻接于Vi的顶点Vj链成一个单链表,这个单链表就称为顶点Vi的邻接表,再将所有邻接表的表头放到数组中,就构成了图的邻接表,邻接表表示中的两种结点结构如下所示。

求强连通分量tarjan算法讲解

求强连通分量的tarjan算法 强连通分量:是有向图中的概念,在一个图的子图中,任意两个点相互可达,也就是存在互通的路径,那么这个子图就是强连通分量。(如果一个有向图的任意两个点相互可达,那么这个图就称为强连通图)。 如果u是某个强连通分量的根,那么: (1)u不存在路径可以返回到它的祖先。 (2)u的子树也不存在路径可以返回到u的祖先。 ?例如: ?强连通分量。在一个非强连通图中极大的强连通子图就是该图的强连通分量。比如图中子图{1,2,3,5}是一个强连通分量,子图{4}是一个强连通分量。 tarjan算法的基础是深度优先搜索,用两个数组low和dfn,和一个栈。low数组是一个标记数组,记录该点所在的强连通子图所在搜索子树的根节点的dfn值,dfn数组记录搜索到该点的时间,也就是第几个搜索这个点的。根据以下几条规则,经过搜索遍历该图和对栈的操作,我们就可以得到该有向图的强连通分量。

算法规则: ?数组的初始化:当首次搜索到点p时,Dfn与Low数组的值都为到该点的时间。 ?堆栈:每搜索到一个点,将它压入栈顶。 ?当点p有与点p’相连时,如果此时(时间为dfn[p]时)p’不在栈中,p 的low值为两点的low值中较小的一个。 ?当点p有与点p’相连时,如果此时(时间为dfn[p]时)p’在栈中,p的low值为p的low值和p’的dfn值中较小的一个。 ?每当搜索到一个点经过以上操作后(也就是子树已经全部遍历)的low 值等于dfn值,则将它以及在它之上的元素弹出栈。这些出栈的元素组成一个强连通分量。 ?继续搜索(或许会更换搜索的起点,因为整个有向图可能分为两个不连通的部分),直到所有点被遍历。 算法伪代码: tarjan(u) { DFN[u]=Low[u]=++Index // 为节点u设定次序编号和Low初值 Stack.push(u) // 将节点u压入栈中 for each (u, v) in E // 枚举每一条边 if (!dfn[v]) // 如果节点v未被访问过 { tarjan(v) // 继续向下找 Low[u] = min(Low[u], Low[v]) } else if (v in S) // 如果节点v还在栈内 Low[u] = min(Low[u], DFN[v]) if (DFN[u] == Low[u]) // 如果节点u是强连通分量的根 do{ v = S.pop // 将v退栈,为该强连通分量中一个顶点}while(u == v); } 演示算法流程;

图论讲义第7章-平面图

第七章 平面图 §7.1 平面图的概念 定义7.1.1 如果图G 能画在曲面S 上,使得任意两边互不交叉,则称G 可嵌入曲面S 。若图G 可嵌入平面,则称G 是可平面图或平面图,画出的无交叉边的图形称为图G 的平面嵌入。 例如,下面是三个平面图及其平面嵌入。 根据定义,下列定理是显然的。 定理7.1.1 若图G 是平面图,则G 的任何子图都是平面图。 定理7.1.2 若图G 是非平面图,则G 的任何母图都是非平面图。 定理7.1.3 若图G 是平面图, 则在G 中添加重边或环边后所得之图仍是平面图。 注:由以上定理知 (1) K n ( n ≤4 ) 和 K 1,n (n ≥ 1)及其所有子图都是平面图。 (2) 环边和重边不影响图的平面性。故以下讨论平面性时总假定图G 是简单图。 定义7.1.2 设图G 是平面图 (已平面嵌入),G 的边将平面划分出的若干区域都称为图G 的面。其中面积无限的面称为无限面或外部面,面积有限的面称为有限面或内部面。包围一个面的所有边称为该面的边界。一个面边界上的边数称为该面的次数 (割边按两次计),面R 的次数记为deg (R )。 定理7.1.4 平面图G 中所有面的次数之和等于G 的边数的两倍,即 其中R 1, R 2, … , R r 是G 的所有面。 证明: 对G 的任何一条边e ,若e 是两个面 R i 和 R j 的公共边界,则在计算R i 和 R j 的次数时,e 各提供了1;若e 只是某一个面的边界,则在计算该面的次数时,e 提供了2。可见每条边在计算总次数时,都提供2。因而结论成立。 1 deg()2().r i i R G ε==∑

强连通分量与模拟链表

强联通分量与模拟链表 作者:逸水之寒 1.强连通分量 强连通分量的定义是:在有向图中,u可以到达v,但是v不一定能到达u,如果u,v 到达,则他们就属于一个强连通分量。 求强连通分量最长用的方法就是Kosaraju算法,比较容易理解而且效率很高,本文对强连通分量的求法均采用Kosaraju算法。 其主要思想:首先对原图G进行深搜形成森林(树),然后选择一棵树进行第二次深搜,注意第一次是要判断节点A能不能通向节点B,而第二次要判断的是节点B能不能通向A,能遍历到的就是一个强连通分量。(附录给出伪代码) Kosaraju算法如果采用了合适的数据结构,它的时间复杂度是O(n)的。相关题目有很多,例如USACO 5.3.3,2009NOIP Senior No.3。下面将以USACO 5.3.3 schlnet 举例说明。 Preblem 1. Network of Schools (USACO 5.3.3 schlnet\IOI96 No.3) A number of schools are connected to a computer network. Agreements have been developed among those schools: each school maintains a list of schools to which it distributes software (the "receiving schools"). Note that if B is in the distribution list of school A, then A does not necessarily appear in the list of school B. You are to write a program that computes the minimal number of schools that must receive a copy of the new software in order for the software to reach all schools in the network according to the agreement (Subtask A). As a further task, we want to ensure that by sending the copy of new software to an arbitrary school, this software will reach all schools in the network. To achieve this goal we may have to extend the lists of receivers by new members. Compute the minimal number of extensions that have to be made so that whatever school we send the new software to, it will reach all other schools (Subtask B). One extension means introducing one new member into the list of receivers of one school.

求无向连通图的生成树

求无向连通图的生成树 一、实验目的 ⑴掌握图的逻辑结构 ⑵掌握图的邻接矩阵存储结构 ⑶验证图的邻接矩阵存储及其遍历操作的实现 二、实验内容 (1)建立无向图的邻接矩阵存储 (2)对建立的无向图,进行深度优先遍历 (3)对建立的无向图进行广度优先遍历 三、设计与编码 (1)本实验用到的理论知识 (2)算法设计 (3)编码 // 图抽象类型及其实现.cpp : Defines the entry point for the console application. // #include"stdafx.h" #include"Graph.h" #include"iostream.h" int Graph::Find(int key,int &k) { int flag=0; for(int i=0;i

if(vertexnum<1)return(-1);//参数vertexnum非法 int i,front,rear,k; Enode *q; //先生成不带边表的顶点表--即顶点为孤立顶点集 A=new Vnode[vertexnum]; if(!A)return(0);//堆耗尽 for(i=0;ikey=front; q->Weight=E[i].weight; q->next=A[rear].first; A[rear].first=q; A[rear].data.OutDegree++; A[front].data.InDegree++; if(Type>2) { q=new Enode; if(!q)return(0); q->key=rear; q->next=A[front].first;

实现有向图强连通分量的算法 数据结构课程设计报告

课程设计报告 课程设计名称:数据结构课程设计 课程设计题目:实现求有向图强连通分量的算法 院(系): 专业: 班级: 学号: 姓名: 指导教师:

沈阳航空航天大学课程设计报告 目录 1 系统分析 (1) 1.1题目介绍 (1) 1.2功能要求 (1) 2 概要设计 (2) 2.1流程图 (2) 2.2结构体说明 (2) 3 详细设计 (3) 3.1遍历函数设计 (3) 3.1.1 Kosaraju算法基本思路: (3) 3.1.2伪代码 (4) 3.2调试分析和测试结果 (6) 3.2.1调试分析 (6) 3.2.2测试结果 (6) 参考文献 (8) 附录(关键部分程序清单) (9)

沈阳航空航天大学课程设计报告 1 系统分析 1.1 题目介绍 在键盘上输入有向图,对任意给定的图(顶点数和边数自定),建立它的邻接表并输出。然后判断该图是否强连通。如果是强连通图,求出该图的所有强连通分量并输出字符。 1.2 功能要求 首先输入图的类型,有向图(因为遍历与权值无关,所以没有涉及带权图)。然后输入图的顶点数、边数和各条边,之后生成该图的邻接表并输出。 再输入要遍历该图的起点,然后从所输入的点深度搜索该图的十字链表,并按遍历顺序输出顶点内容。之后决定是否继续遍历该图或输入另一个需要遍历的图亦或是结束程序。 要求采取简单方便的输入方式。并且系统要求提供观察有向图图形结构和各强连通分量结构的功能。

2 概要设计 2.1 流程图 根据程序要求,设计流程图如下: 图2.1——流程图 2.2 结构体说明 //有向图十字链表存储表示 typedef struct arcbox int tailvex,headvex;//该弧的尾和头顶点的位置

图练习题

第七章:图练习题 一、选择题 1、一个有n个顶点的无向图最多有()条边。 A、n B、n(n-1) C、n(n-1)/2 D、2n 2、具有6个顶点的无向图至少有()条边才能保证是一个连通图。 A、5 B、6 C、7 D、8 3、具有n个顶点且每一对不同的顶点之间都有一条边的图被称为()。 A、线性图 B、无向完全图 C、无向图 D、简单图 4、具有4个顶点的无向完全图有()条边。 A、6 B、12 C、16 D、20 5、G是一个非连通无向图,共有28条边,则该图至少有()个顶点 A、6 B、7 C、8 D、9 6、存储稀疏图的数据结构常用的是()。 A、邻接矩阵 B、三元组 C、邻接表 D、十字链表 7、对一个具有n个顶点的图,采用邻接矩阵表示则该矩阵的大小为()。 222n D、(n-1)、C、(n+1)A、n B8、设连通图G的顶点数为n,则G的生成树的边数为()。 A、n-1 B、n C、2n D、2n-1 9、n个顶点的无向图的邻接表中结点总数最多有()个。 A、2n B、n C、n/2 D、n(n-1) 10、对于一个具有n个顶点和e条边的无向图,若采用邻接表表示,则表向量的大小为(),所有顶点邻接表的结点总数为()。 A、n B、n+1 C、n-1 D、2n E、e/2 F、e G、2e H、n+e 11、在有向图的邻接表存储结构中,顶点v在表结点中出现的次数是()。 A、顶点v的度 B、顶点v的出度 C、顶点v 的入度 D、依附于顶点v的边数 12、已知一个图,若从顶点a出发进行深度和广度优先搜索遍历,则可能得到的顶点序列分别为()和() (1)A、abecdf B、acfebd C、acebfd D、acfdeb (2)A、abcedf B、abcefd C、abedfc D、acfdeb 13、采用邻接表存储的图的深度和广度优先搜索遍历算法类似于二叉树的()和()。 A、中序遍历 B、先序遍历 C、后序遍历 D、层次遍历 14、已知一有向图的邻接表存储结构如下图所示,分别根据图的深度和广度优先搜索遍历算法,从顶点v1出发,得到的顶点序列分别为()和()。 v1,v4,v3,v5,v2、v1,v2,v3,v5,v4 D、v1,v3,v2,v4,v5 C、v1,v2,v3,v4,v5 B、 A.

相关文档
最新文档