李云清数据结构第八章图

关注公众号凡花花的小窝,可以获取更多的计算机相关的资源
第8 章 图
8.1 选择题
(1)如果某图的邻接矩阵是对角线元素均为零的上三角矩阵,则此图是( D )。
A.有向完全图 B.连通图 C.强连通图 D.有向无环图
(2)若邻接表中有奇数个表结点,则一定( D )。
A.图中有奇数个顶点 B.图中有偶数个顶点
C.图为无向图 D.图为有向图
(3)下列关于无向连通图特性的叙述中,正确的是( A )。
Ⅰ.所有顶点的度之和为偶数
Ⅱ.边数大于顶点个数减1
Ⅲ.至少有一个顶点的度为1
A.只有Ⅰ B.只有Ⅱ C.Ⅰ和Ⅱ D.Ⅰ和Ⅲ
(4)假设一个有n 个顶点和e 条弧的有向图用邻接表表示,则删除与某个顶点vi 相关的
所有弧的时间复杂度是( B )。
A.O(n) B.O(e) C.O(n+e) D.O(n*e)
(5)已知一个有向图8.30 所示,则从顶点a 出发进行深度优先偏历,不可能得到的DFS
序列为( A )。
A.a d b e f c B.a d c e f b C.a d c b f e D.a d e f c b
b
a c e f
d
图8.30 有向图
(6)无向图G=(V,E),其中:V={a,b,c,d,e,f},E={(a,b),(a,e),(a,c),(b,e),(c,f),
(f,d),(e,d)},对该图进行深度优先遍历,得到的顶点序列正确的是( D )。
A.a,b,e,c,d,f B.a,c,f,e,b,d C.a,e,b,c,f,d D.a,e,d,f,c,b
(7)下列哪一个选项不是图8.31 所示有向图的拓扑排序结果( C )。
A.AFBCDE B.FABCDE C.FACBDE D.FADBCE
图8.31 AOV 网
76
(8)判断一个有向图是否存在回路除了可以利用拓扑排序方法外,还可以利用( D )。
A.单源最短路Dijkstra 算法 B.所有顶点对最短路Floyd 算法
C.广度优先遍历算法 D.深度优先遍历算法
(9)在一个带权连通图G 中,权值最小的边一定包含在G 的( A )。
A.最小生成树中 B.深度优先生成树中
C.广度优先生成树中 D.深度优先生成森林中
(10)下图所示带权无向图的最小生成树的权为( C )。
A.14 B.15 C.17 D.18
图8.32 带权无向图
8.2 对于如图8.33 所示的无向图,试给出:
(1)图中每个顶点的度;
(2)该图的邻接矩阵;
(3)该图的邻接表与邻接多重表;
(4)该图的连通分量。
图8.33 无向图 图8.34 有向图
【答】:
(1)D(V0)=2;D(V1)=2;D(V2)=3;D(V3)=3;D(V4)=2;D(V5)=2;D(V6)
=1。
(2)该图的邻接矩阵如图8.33.1 所示。
77
图8.33.4 两个连通分量
图8.33.1 邻接矩阵 图8.33.2 邻接表
(3)该图的邻接表如图8.30.2 所示;该图的邻接多重表如图8.30.3 所示。
图8.33.3 邻接多重表
(4)该图的两个连通分量如图8.33.4 所示。
8.3 对于如图8.34 所示的有向图,试给出:
(1)顶点D 的入度与出度;
(2)该图的出边表与入边表;
(3)该图的强连通分量。
【答】:
(1)顶点D 的入度是2;顶点D 的出度为1。
(2)该图的出边表如图8.34.1 所示,该图的入边表如图8.34.2 所示。
78
图8.34.1 出边表 图8.34.2 入边表
(3)该图的两个强连通分量如图8.34.3 所示。
A
B
C
D
E
图8.34.3 两个强连通分量
8.4 试回答下列关于图的一些问题。
(1)有n 个顶点的有向强连通图最多有多少条边?最少有多少条边?
(2)表示一个有500 个顶点,500 条边的有向图的邻接矩阵有多少个非零元素?
(3)G 是一个非连通的无向图,共有28 条边,则该图至少有多少个顶点?
【答】:
(1)有n 个顶点的有向强连通图昨多有n(n-1)条边;最少有n-1 条边。
(2)500 个。
(3)9 个顶点。
8.5 图8.35 所示的是某个无向图的邻接表,试:
(1)画出此图;
(2)写出从顶点A 开始的DFS 遍历结果;
(3)写出从顶点A 开始的BFS 遍历结果。
【答】:
(1)图8.35 邻接表对应的无向图如图8.35.1 所示。
图8.35.1
(2)从顶点A 开始的DFS 遍历结果是:A,B,C,F,E,G,D
(3)从顶点A 开始的BFS 遍历结果是:A,B,C,D,F,E,G
8.6 证明,用Prim 算法能正确地生成一棵最小生成树。
【证明】:
图8.35 题8.5 的邻接表
79
Prim 算法是按照逐边加入的方法来构造最小生成树过程的。记构造过程中生成的子图为
G’,显然G’始终是一棵树。这是因为初始时V(G’)={u0},E(G’)=Ф,是一棵树。随后每一
步都是向G’中添加一个顶点同时添加一条边,始终保持G’中所有顶点连通。这样,当G’有n
个顶点时是仅有n-1 条边的连通图,所以G’是一棵树。Prim 算法在执行过程中,始终能保证
G’=(V’,E’)是无向连通网络G=(V,E)上某个最小代价生成树的连通图,如果该结论正
确,则随着V(G’)顶点不断增加,当V(G’)=V(G)时,G’=(V’,E’)就是G=(V,E)上的最小代价生
成树。
下面证明Prim 算法的每一步都能保证G’=(V’,E’)是无向连通网络G=(V,E)上某个代价生成
树的子连通图。
初始时,G’仅有一个顶点V(G’)={u0},E(G’)=Ф,设该树为T1,此时G’显然是G
的某个最小生成树的子连通图。现假设第i 步G’=(V’,E’)中含有i 个顶点,不妨设该树为Ti,
在G(V,E)中存在一棵最小生成树包含着Ti,在第i+1 步,存在u∈Ti,v∉Ti,且(u,v)是最
小两栖边,将顶点{v},边(u,v)添加到树Ti 中,所得树Ti+1 是包含V(Ti)+{v}顶点集的生
成树,且具有i+1 个顶点。根据MST 性质,此时在G=(V,E)中必存在一棵最小生成树包含着
Ti+1。由此可知,当i=n 时,G’(Tn)即为无向图G 含有n 个顶点的最小生成树。
当然在进行最小两栖边选择时,如果同时存在几个具有相同代价的最小两栖边,则可任选
一条,因些Prim 算法构造的最小生成树不是唯一的。但它们的最小(代价)总和是相等的。
8.7 证明,用Kruskal 算法能正确地生成一棵最小生成树。
【证明】:
算法首先构造具有n 个不同顶点的n 个连通分量,然后在E(G)中选择边(u,v),若u,v
顶点属于不同的两个连通分量,则把该边添加到树T 中,每添加一条边就减少一个连通分量。
当添加了共n-1 条边时,就把n 个连通分量变成一个连通分量,此时T 就是具有n 个顶点n-1
条边的树。由于n 是有限数,E(G)中边数也是有限的,所以算法具有有穷性。
不妨设T 的边为(u1,v1),(u2,v2),…,(un-1,vn-1),按权值从小到大排列,若存在一棵
树T’的代价总和小于T的代价总和,则必在T’中存在一条边(u’,v’)∈TE’,(u’,v’) ∉TE,
且(u’,v’)的代价小于(un-1,vn-1)的代价,这就说明(u’,v’)边没有选取的原因是因为u’,v’在同一个
连通分量,添加(u’,v’)将产生回路,说明T’不可能是树(添加一条边没有减少一个连通分
量,这样T’的边数将大于n-1)。这与T’是一棵树的假设
相矛盾。证毕。
8.8 对如图8.36 所示的连通图,分别用Prim 和Kruskal
算法构造其最小生成树。
【答】:
(1)采用Prim 算法求解最小生成树的过程如图8.36.1
所示。
图8.36 无向连通网
80
(2)采用Kruskal 算法求解最小生成树时首先要对边进行由小到大进行排序,本题对边进行
排序的结果是:(D,F)1、(C,F)2、(A,F)3、(A,C)4、(F,G)4、(D,E)4、(D,B)4、(C,D)
5、(E,G)5、(A,D)6、(D,G)6、(A,B)7 。根据Kruskal 算法,构造最小生成树的过程如图
8.33.2 所示。
4
4
2
A B
C D E
F G
3
4
1
(a)选取(A,F) (b)选取(D,F) (c)选取(C,F)
4
4
2
A B
C D E
F G
3
4
1
(d)选取(F,G) (e)选取(D,E) (f)选取(D,B)
图8.36.1 Prim 算法求解最小生成树过程
(a)选取(D,F) (b)选取(C,F) (c)选取(A,F)
4
2
A B
C D E
F G
3
4
1
4
4
2
A B
C D E
F G
3
4
1
(d)选取(F,G) (e)选取(D,E) (f)选取(D,B)
图8.36.2 Kruskal 算法求解最小生成树过程
81
B
A E C
D
G
F
48
28
15
40 33
20
9
13
18 43
23
33
12
A
B
G
C
D F
E
H
I
图8.37 有向网 图8.38 题8.10 的AOV 网
8.9 对于如图8.37 所示的有向网,用Dijkstra 方法求从顶点A 到图中其他顶点的最短路径,
并写出执行算法过程中距离向量d 与路径向量p 的状态变化情况。
【答】:对于如图8.37 所示的有向网,Dijkstra 方法求从顶点A 到图中其它顶点的最短径时,
距离向量与路径向量的状态变化如下表示:
距离向量d 路径向量p
循环 集合S v
0 1 2 3 4 5 6 0 1 2 3 4 5 6
初始化 {A } - 0 48 ∞ 15 28 ∞ 40 -1 0 -1 0 0 -1 0
1 { AD} 3 0 48 ∞ 15 28 48 38 -1 0 -1 0 0 3 3
2 {ADE } 4 0 48 61 15 28 48 38 -1 0 4 0 0 3 3
3 { ADG} 6 0 48 61 15 28 48 38 -1 0 4 0 0 3 3
4 {ADGB } 1 0 48 60 15 28 48 38 -1 0 1 0 0 3 3
5 {ADGBF} 5 0 48 57 15 28 48 38 -1 0 5 0 0 3 3
6 {ADGBFC } 2 0 48 57 15 28 48 38 -1 0 5 0 0 3 3
从表中可以看出源点A 到其它各顶点的最短距离及路径为:
A→B:48 路径:A→B
A→C:57 路径:A→D→F→C
A→D:15 路径:A→D
A→E:28 路径:A→E
A→F:48 路径:A→D→F
A→G:38 路径:A→D→G
8.10 试写出如图8.38 所示的AOV 网的4 个不同的拓扑序列。
【答】:图8.38 所示的AOV 网的4 个不同的拓扑序列为:
(1)ABDCEFGIH
(2)ABDECFGIH
(3)DABCEFGIH
(4)DAECBFGIH
8.11 计算如图8.39 所示的AOE 网中各顶点所表示的事件的发生时间ve(j),vl(j),各边所表
示的活动的开始时间e(i),l(i),并找出其关键路径。
82
【答】:为描述方便,将AOE 网中的所有边事件记为a0-a7,如图8.39 所示。按照关键路径算
法,求得各顶事件的最早与最晚开始时间如下表所示。
顶点 ve vl 活动 e l l-e 关键活动
v0
v1
v2
v3
v4
v5
0
6
4
13
22
25
0
6
5
13
22
25
a0
a1
a2
a3
a4
a5
a6
a7
0
0
6
4
4
13
13
22
0
1
6
5
5
13
15
22
0
1
0
1
1
0
2
0




可见,该AOE 网的关键路径是a0,a2,a5,a7。 (注:图中箭头指示求解的顺序)
8.12 无向图采用邻接表作为存储结构,试写出以下算法
(1)求一个顶点的度;
(2)往图中插入一个顶点;
(3)往图中插入一条边;
(4)删去图中某顶点;
(5)删去图中某条边。
【答】:本题的参考解答基于以下的存储结构:
#define m 20 /预定义图的最大顶点数/
typedef char datatype; /顶点信息数据类型/
typedef struct node{ /边表结点/
int adjvex; /邻接点/
struct node *next;
}edgenode;
typedef struct vnode{ /头结点类型/
datatype vertex; /顶点信息/
edgenode *firstedge; /邻接链表头指针/
}vertexnode;
v0
v1
v2
v3
v5
6 v4
4
7
8
20
9
10
3
a0
a1
a2
a3
a4
a5
a6
a7
图8.39 题8.10 的AOE 网
83
typedef struct{ /邻接表类型/
vertexnode adjlist [m]; /存放头结点的顺序表/
int n,e; /图的顶点数与边数/
}adjgraph;
(1)求一个顶点的度;
/求无向图g 的第i 个顶点的度/
int d(adjgraph g ,int i)
{int k=0;
edgenode *p;
p=g.adjlist[i].firstedge;
while §
{k++;
p=p->next;
}
return k;
}
(2)往图中插入一个顶点;
void insertvex(adjgraph *g,datatype x)
{ int i=0,flag=0;
/查找待插入的结点是否存在/
while (!flag&&in)
{if (g->adjlist[i].vertexx) flag=1;
i++;
}
if (flag) { printf(“结点已存在!”);
getch();
exit(0);
}
if (g->n>m) { printf(“邻接表已满!”);
exit(0);
}
g->adjlist[g->n].vertex=x;
g->adjlist[g->n].firstedge=NULL;
g->n++;
}
(3)往图中插入一条边;
/在无向图g 中插入无向边(i,j)/
void insertedge(adjgraph *g,int i,int j)
{ edgenode *p,*s;
84
int flag=0;
if (in&&jn)
{ p=g->adjlist[i].firstedge;
while (!flag&&p)
{if (p->adjvex
j) flag=1;
p=p->next;
}
if (flag) {printf(“边已存在!”);
getch();
exit(0);
}
/插入无向边(i,j)/
s=(edgenode )malloc(sizeof(edgenode));
s->adjvex=j; /邻接点序号为j/
s->next=g->adjlist[i].firstedge;
g->adjlist[i].firstedge=s; /将新结点s 插入顶点vi 的边表头部
/
s=(edgenode )malloc(sizeof(edgenode));
s->adjvex=i; /邻接点序号为i/
s->next=g->adjlist[j].firstedge;
g->adjlist[j].firstedge=s; /将新结点s 插入顶点vj 的边表头部
/
}
else
printf(“插入边不合法!”);
}
(4)删去图中某顶点;
/下面的函数删除无向图中顶点编号为i 的顶点。由于该顶点被删除,与这个顶点相关联
的边也应该被删除,具体做法是,通过邻接表查找与顶点i 邻接的其它所有顶点,在这些顶点
的邻接边链表中删除编号为i 的边结点,再将邻接表中最后一个顶点移动到第i 个顶点在邻接
表中的位置,相应地修改最后一个顶点在邻接表中的顶点编号为i
/
void deletevex(adjgraph *g,int i)
{ int k;
edgenode *pre,*p,*s;
if (i>=0 && in) /顶点下标合法/
{ /删除与原顶点i 有关的所有边/
s=g->adjlist[i].firstedge;
while (s)
{k=s->adjvex;
pre=NULL;
85
p=g->adjlist[k].firstedge;
while §
{if (p->adjvexi) /结点编号是i,应该删除/
if (!pre) /边链表的第一个边结点/
{g->adjlist[k].firstedge=p->next;
free§;
p=g->adjlist[k].firstedge;
}
else /不是第一个边结点/
{pre->next=p->next;
free§;
p=pre->next;
}
else /结点编号不是i/
{pre=p;
p=p->next;
}
}
g->adjlist[i].firstedge=s->next;
free(s); /释放顶点i 边链表上的结点/
s=g->adjlist[i].firstedge;
}
g->adjlist[i]=g->adjlist[g->n-1]; /将最后一个结点换到第i 个结点的位置/
p=g->adjlist[i].firstedge;
/由于第n-1 个结点的编号被改为i,所以调整所有与这个结点关联的边信息/
while §
{ k=p->adjvex;
s=g->adjlist[k].firstedge;
while (s)
{if (s->adjvex
g->n-1) /将最后一个结点的编号改为i/
s->adjvex=i;
s=s->next;
}
p=p->next;
}
g->n–; /结点个数减1/
}
else
printf(“不存在指定的结点!\n”);
86
}
(5)删去图中某条边。
void deleteedge(adjgraph *g,int i,int j)
{ edgenode *pre,*p;
int k;
if (i>=0 && in && j>=0 && jn) /判断边是否有效/
{ pre=NULL; /找无向边(i,j)并删除/
p=g->adjlist[i].firstedge;
while (p && p->adjvex!=j)
{pre=p;
p=p->next;
}
if § /找到了被删除边结点/
{ if (!pre) /是第一个边结点/
g->adjlist[i].firstedge=p->next;
else
pre->next=p->next;
free§;
pre=NULL; /找无向边(j,i)并删除/
p=g->adjlist[j].firstedge;
while (p&& p->adjvex!=i)
{pre=p;
p=p->next;
}
if (!pre)
g->adjlist[j].firstedge=p->next;
else
pre->next=p->next;
free§;
g->e–; /边的个数减1/
}
else { printf(“找不到指定的边!”);
getch();
exit(0);
}
}
else
{ printf(“边不合法!\n”);
exit(0);
87
}
}
8.13 设有向图采用邻接表的存储结构(出边表),试写出求图中一个顶点的入度的算法。
【答】:
/求有向图g 中顶点i 的入度,g 的存储结构为出边表,结构定义同题8.11/
int id(adjgraph *g,int i)
{int j,count=0;
edgenode p;
for (j=0;jn;j++)
{p=g->adjlist[j].firstedge;
while §
{if (p->adjvex==i) count++;
p=p->next;
}
}
return count;
}
8.14 设计一个算法,不利用拓扑排序判断有向图中是否存在环。
【答】:对于有向图进行深度优先遍历,如果从有向图上某个顶点v 出发的遍历,dfs(v)结束之
前出现一条从顶点u 到顶点v 的回边,由于u 在生成树上是v 的子孙,则有向图中必定存在包
含顶点v 到顶点u 的环。因此判断有一个有向图中是否有环可以借助图的深度优先遍历算法。
int visited[m];
void dfs(adjgraph g,int i)
{ edgenode p;
visited[i]=1;
p=g.adjlist[i].firstedge;
while §
{ if (!visited[p->adjvex])
dfs(g,p->adjvex);
else /
深度优先遍历到已访问的结点,出现环
/
{ printf(" found a circle!");
getch();
exit(0);
}
p=p->next;
}
}
void findcircle(adjgraph g)
{ int i;
88
for (i=0;i visited[i]=0; /初始化标志数组/
for (i=0;i if (!visited[i]) /vi 未访问过/
dfs(g,i);
}
8.15 编写一个非递归深度优先遍历图的函数。
【答】:图的深度优先遍历类似于二叉树的前序遍历,非递归实现时可以用一个栈保存已访问
过的结点,这些结点的邻接点可能还没有全部访问完成,遍历过程中可能需要回溯。
参考程序如下:
#define MAXVEX 100 /定义栈的最大容量/
int visited[m];
void dfs(adjgraph g,int i)
{ /以vi 为出发点对邻接表表示的图g 进行深度优先遍历/
edgenode *p;
edgenode * stack[MAXVEX]; /栈用来保存回溯点/
int top=-1;
printf("visit vertex: %c “,g.adjlist[i].vertex); /访问顶点i/
visited[i]=1;
p=g.adjlist[i].firstedge;
while (top>-1 || p) /当栈不空或p 不空时/
{ if § /优先处理p 不空的情况/
if (visited[p->adjvex])
p=p->next;
else
{printf(”%c ",g.adjlist[p->adjvex].vertex);
visited[p->adjvex]=1;
stack[++top]=p;
p=g.adjlist[p->adjvex].firstedge;
}
else /从栈中进行回溯/
if (top>-1)
{p=stack[top–];
p=p->next;
}
}
}
void dfstraverse(adjgraph g)
{ int i;
89
for (i=0;i visited[i]=0; /初始化标志数组/
for (i=0;i if (!visited[i]) /vi 未访问过/
dfs(g,i);
}
8.16 编写两个函数分别计算AOE 网中所有活动的最早开始时间与最晚允许开始时间。
【答】:为了记录AOE 网中所有活动的最早开始时间与最晚允许开始时间,定义边结点结构
edge 如下,其中vi,vj 存放边的起点与终点,e 存放该边表示的活动的最早开始时间,l 存放该
边表示的活动的最晚允许开始时间。
typedef struct
{ int vi,vj;
e,l;
} edge; /定义边结构类型,存放每个活动的最早开始时间与最晚允许开始时间/
若活动ak 的尾事件是vi,头事件是vj,则e(k)就是vi 的最早发生时间,l(k)是vj 所允许的
最晚开始时间减去活动ak 的持续的时间。求解AOE 网络事件的最早发生时间与最晚允许发生
时间的算法参见教材算法8.10。
/求AOE 网中各活动的最早开始时间/
void e(aoegraph gout,int ve[],edge active[])
{int i,k=0;
edgenode
p;
for (i=0;in;i++) /对出边表中的每一条边进行求解/
{ p=gout->adjlist[i].firstedge;
while §
{ active[k].vi=i; /边的起点/
active[k].vj=p->adjvex; /边的终点/
active[k].e=ve[i];
k++;
p=p->next;
}
}
}
/求AOE 网中各活动的最晚允许开始时间/
void l(aoegraph *gout,int vl[],edge active[])
{int i,k=0;
edgenode *p;
for (i=0;in;i++)
{ p=gout->adjlist[i].firstedge;
90
while §
{active[k].l=vl[p->adjvex]-p->len;
p=p->next;
k++;
}
}
}
91

你可能感兴趣的:(数据结构)