第八章 图
一、基本知识点
(1)图的定义和相关术语。
(2)图的邻接矩阵和邻接表两种主要存储结构及其特点。
(3)图的基本运算算法设计。
(4)图的深度优先和广度优先遍历算法。
(5)图的两种遍历算法在图搜索算法设计中的应用。
(6)生成树和最小生成树的定义,求最小生成树的Prim和Kruskal算法。
(7)求单源最短路径的Dijkstra算法,求多源最短路径的Flody算法。
(8)拓扑排序过程。
(9)求AOE网关键路径的过程。
(10)灵活地运用图这种数据结构解决一些综合应用问题。
二、要点归纳、基本运算
(1)图由两个集合组成,G=(V,E),V是顶点的有限集合,E是边的有限集合。
(2)在有向图G=(V,E)中,集合E中的元素为有序对。
(3)在无向图G=(V,E)中,集合E中中的元素为无序对,无向图可以看成有向图的特床情况。
(4)如果图中从顶点u到顶点v之间存在一条路径,则称u和v是连通的。
(5)如果无向图G中任意两个顶点都是连通的,称G为连通图。无向图G的极大连通子图称为G的连通分量。
(6)有向图G中任意两个顶点都是连通的,称G为强连通图。有向图G的极大强连通子图称为G的强连通分量。
(7)图的主要存储结构有邻接矩阵和邻接表。
(8)无向图的邻接矩阵一定是对称矩阵 ,但对称矩阵对应的图不一定都是无向图。
(9)一个图的邻接矩阵是不对称的,则该图一定是有向图。
(10)若用邻接表表示图,图中的每个顶点v都对应一个单链表,单链表v中每个结点存放的顶点u满足<u,v>∈E(G)。
(11) 对于连通图,从它的任一顶点出发进行一次深度优先遍历或深度优先通历可访问到图的每个顶点。
(12)对于非连通图,它有几个连通分量就需要调用几次深度优先遍历或深度优先遍历才能访问图的全部顶点。
(13)图的深度优先遍历与二叉树的先序遍历类似。
(14)图的广度优先遍历与二叉树的层次遍历类似。
(15)给定一个不带权的连通图,采用深度优先遍历可以找到从顶点u到v的所有路径,而采用广度优先遍历可以找到最短路径。
(16)如果树T是图G的一个子图,且V(T)=V(G),即G的所有顶点也都是T的顶点,则称T为G的生成树。
(17)一个带权无向图的最小生成树并非指边数最少的生成树(因为所有生成树的边数相同),而是指所有边权值之和最小的生成树。
(18)一个带权无向图的最小生成树不一定是唯一的,但最小生成树的所有边权值之和一定是唯一的。
(19)一个图的最短路径一定是简单路径。
(20)求单源最短路径的Dijkstra算法既适合于带权有向图也适合于带权无向图。
(21)在Dijkstra算法中且一旦考查了一个顶点(把它添加到S 集合中),它以后的最短路径不会再调整。
(22)Dijkstra算法不适合含负权值的图求单源最短路径。
(23) Floyd算法可以对含负权值的图求最短路径,但图中不能有权值和为负数的环路。
(24)一个有向图中如果存在回路,则不能产生完整的拓扑序列,所以一个强连通图是不能进行拓扑排序的。
(25)若一个有向图不能产生完整的拓扑序列,则其中必存在回路。
(26)如果一个有向图的拓扑序列是唯一的,则图中必定仅有一个顶点的入度为0,一个顶点的出度为0。
(27)一个AOE网中至少有一条关键路径,且是从源点到汇点的路径中最长的一条。
(28)一个AOE网的关键路径不一定是唯一的,但其关键路径长度一定是唯一的。
上机实践
1、图的邻接表和邻接矩阵存储
建立下图的邻接表或邻接矩阵,并输出之;
MatGraph.h
#pragma once
#include<iostream>
using namespace std;
const int MAXV = 10; //最大定点数
const int INF = 500; //定义邻接矩阵的无穷 (500是此图中最大的)
typedef char InfoType;
//本次我采用图的存储结构是邻接矩阵
typedef struct
{
int no; //顶点编号
InfoType info; //顶点数值信息
}VertexType; //顶点类型
typedef struct
{
int edges[MAXV][MAXV]; //邻接矩阵
int n, e; //顶点数、边数
VertexType vexs[MAXV]; //存放顶点信息
}MatGraph;
void CreateMat(MatGraph& g, int A[MAXV][MAXV], int n, int e); //创建图的邻接矩阵
void DispMat(MatGraph g); //输出邻接矩阵g
/*-----------------以下是邻接表方式------------------*/
typedef struct ANode
{
int adjvex; //该边的邻接点编号
struct ANode* nextarc; //指向下一条边的指针
int weight; //该边的相关信息,如权值(用整型表示)
} ArcNode; //边结点类型
typedef struct Vnode
{
InfoType info; //顶点其他信息
//int count; //存放顶点入度,仅仅用于拓扑排序
ArcNode* firstarc; //指向第一条边
} VNode; //邻接表头结点类型
typedef struct
{
VNode adjlist[MAXV]; //邻接表头结点数组
int n, e; //图中顶点数n和边数e
} AdjGraph; //完整的图邻接表类型
void CreateAdj(AdjGraph*& G, int A[MAXV][MAXV], int n, int e);//创建图的邻接表
void DispAdj(AdjGraph* G);//输出图的邻接表
void DestroyAdj(AdjGraph*& G);//销毁图的邻接表
MatGraph.cpp
#include"MatGraph.h"
void CreateMat(MatGraph& g, int A[MAXV][MAXV], int n, int e) //创建图的邻接矩阵
{
g.e = e;
g.n = n;
for (int i = 0; i < g.n; i++)
{
for (int j = 0; j < g.n; j++)
{
g.edges[i][j] = A[i][j];
}
}
}
void DispMat(MatGraph g) //输出邻接矩阵g
{
for (int i = 0; i < g.n; i++)
{
for (int j = 0; j < g.n; j++)
{
if (g.edges[i][j] != INF)
{
cout << g.edges[i][j] << " ";
}
else cout << "∞" << " ";
}
cout << endl;
}
}
void CreateAdj(AdjGraph*& G, int A[MAXV][MAXV], int n, int e) //创建图的邻接表
{
int i, j;
ArcNode* p;
G = (AdjGraph*)malloc(sizeof(AdjGraph));
for (i = 0; i < n; i++) //给邻接表中所有头结点的指针域置初值
G->adjlist[i].firstarc = NULL;
for (i = 0; i < n; i++) //检查邻接矩阵中每个元素
for (j = n - 1; j >= 0; j--)
if (A[i][j] != 0 && A[i][j] != INF) //存在一条边
{
p = (ArcNode*)malloc(sizeof(ArcNode)); //创建一个结点p
p->adjvex = j;
p->weight = A[i][j];
p->nextarc = G->adjlist[i].firstarc; //采用头插法插入结点p
G->adjlist[i].firstarc = p;
}
G->n = n; G->e = n;
}
void DispAdj(AdjGraph* G) //输出邻接表G
{
int i;
ArcNode* p;
for (i = 0; i < G->n; i++)
{
p = G->adjlist[i].firstarc;
cout << i << " : ";
while (p != NULL)
{
cout << p->adjvex << "[" << p->weight << "]-->";
p = p->nextarc;
}
cout << "∧\n";
}
}
void DestroyAdj(AdjGraph*& G) //销毁图的邻接表
{
int i;
ArcNode* pre, * p;
for (i = 0; i < G->n; i++) //扫描所有的单链表
{
pre = G->adjlist[i].firstarc; //p指向第i个单链表的首结点
if (pre != NULL)
{
p = pre->nextarc;
while (p != NULL) //释放第i个单链表的所有边结点
{
free(pre);
pre = p; p = p->nextarc;
}
free(pre);
}
}
free(G); //释放头结点数组
}
MatGraph _main.cpp
#include"MatGraph.h"
int main()
{
MatGraph G;
AdjGraph* g=NULL;
int A[MAXV][MAXV] = {
{0,28,INF,INF,INF,10,INF},
{28,0,16,INF,INF,INF,14},
{INF,16,0,12,INF,INF,INF},
{INF,INF,12,0,22,INF,18},
{INF,INF,INF,22,0,25,24},
{10,INF,INF,INF,25,0,INF},
{INF,14,INF,18,24,INF,0}
};
int n = 7, e = 9;
CreateMat(G, A, n, e);
cout << "创建的邻接矩阵:\n";
DispMat(G);
cout << "创建的邻接表:\n";
CreateAdj(g, A, n, e);
DispAdj(g);
DestroyAdj(g);
}
2、图的各种遍历算法实现
以0结点为起点实现上述图的深度优先和广度优先遍历算法;
MatGraph.h
#pragma once
#include<iostream>
#include<queue> //广度优先遍历需要使用到队列,C++的STL(标准模板库)
using namespace std;
const int MAXV = 10; //最大定点数
const int INF = 500; //定义邻接矩阵的无穷 (500是次图中最大的)
typedef char InfoType;
//本次我采用图的存储结构是邻接矩阵
typedef struct
{
int no; //顶点编号
InfoType info; //顶点数值信息
}VertexType; //顶点类型
typedef struct
{
int edges[MAXV][MAXV]; //邻接矩阵
int n, e; //顶点数、边数
VertexType vexs[MAXV]; //存放顶点信息
}MatGraph;
void CreateMat(MatGraph& g, int A[MAXV][MAXV], int n, int e); //创建图的邻接矩阵
void DispMat(MatGraph g); //输出邻接矩阵g
void DFS(MatGraph g, int v);
void BFS(MatGraph g, int v);
MatGraph.cpp
#include"MatGraph.h"
int visit_1[MAXV] = { 0 }; //来判断遍历时顶点是否访问过
int visit_2[MAXV] = { 0 }; //来判断遍历时顶点是否访问过
void CreateMat(MatGraph& g, int A[MAXV][MAXV], int n, int e) //创建图的邻接矩阵
{
g.e = e;
g.n = n;
for (int i = 0; i < g.n; i++)
{
for (int j = 0; j < g.n; j++)
{
g.edges[i][j] = A[i][j];
}
}
}
void DispMat(MatGraph g) //输出邻接矩阵g
{
for (int i = 0; i < g.n; i++)
{
for (int j = 0; j < g.n; j++)
{
if (g.edges[i][j] != INF)
{
cout << g.edges[i][j] << " ";
}
else cout << "∞" << " ";
}
cout << endl;
}
}
void DFS(MatGraph g, int v)
{
cout << "->" << v;
visit_1[v] = 1; //访问过的点置true
for (int i = 0; i < g.n; i++) //搜索具有以该点为顶点的边的另一个顶点
{
if (g.edges[v][i] != INF && g.edges[v][i] != 0 && visit_1[i] == 0) //找得到该顶点的边的另一个顶点
{
DFS(g, i);
}
}
return;
}
void BFS(MatGraph g, int v)
{
queue<int>qu; //创建队列
visit_2[v] = 1; //第一个顶点置1,表示已经访问
qu.push(v); //入队
while (!qu.empty()) //只要队不为空
{
cout << "->" << qu.front(); //输出队头元素
v = qu.front(); //从队头开始继续访问
for (int i = 0; i < g.n; i++)
{
if (g.edges[v][i]!=INF && g.edges[v][i] !=0 && visit_2[i]==0)
{
qu.push(i); //遇到没访问的并且有边的,进队
visit_2[i] = 1;
}
}
qu.pop(); //已经输出的最后出队
}
}
MatGraph_main.cpp
#include"MatGraph.h"
int main()
{
MatGraph G;
int A[MAXV][MAXV] = {
{0,28,INF,INF,INF,10,INF},
{28,0,16,INF,INF,INF,14},
{INF,16,0,12,INF,INF,INF},
{INF,INF,12,0,22,INF,18},
{INF,INF,INF,22,0,25,24},
{10,INF,INF,INF,25,0,INF},
{INF,14,INF,18,24,INF,0}
};
int n = 7, e = 9;
CreateMat(G, A, n, e);
DispMat(G);
cout << "深度遍历递归算法:" << endl;
DFS(G, 0);
cout << endl;
cout << "广度遍历算法:" << endl;
BFS(G, 0);
}
3、最小生成树的算法实现
利用普里姆(Prim)算法或克鲁斯卡尔(Kruskal)算法求上图的最小生成树,算法实现代码必须有注释。
SpanningTree.h
#pragma once
#include<iostream>
using namespace std;
const int MaxSize = 100;
const int MAXV = 10; //最大定点数
const int INF = 500; //定义邻接矩阵的无穷 (500是此图中最大的)
typedef char InfoType;
//本次我采用图的存储结构是邻接矩阵
typedef struct
{
int no; //顶点编号
InfoType info; //顶点数值信息
}VertexType; //顶点类型
typedef struct
{
int edges[MAXV][MAXV]; //邻接矩阵
int n, e; //顶点数、边数
VertexType vexs[MAXV]; //存放顶点信息
}MatGraph;
void CreateMat(MatGraph& g, int A[MAXV][MAXV], int n, int e); //创建图的邻接矩阵
void DispMat(MatGraph g); //输出邻接矩阵g
void Prim(MatGraph g, int v); //普里姆算法
//----------------------克鲁斯卡尔算法---------------------//
typedef struct
{
int u; //边的起始顶点
int v; //边的终止点
int w; //边的权值
}Edge;
void InsertSort(Edge E[], int n);
void Kruskal(MatGraph g);
SpanningTree.cpp
#include"SpanningTree.h"
void CreateMat(MatGraph& g, int A[MAXV][MAXV], int n, int e) //创建图的邻接矩阵
{
g.e = e;
g.n = n;
for (int i = 0; i < g.n; i++)
{
for (int j = 0; j < g.n; j++)
{
g.edges[i][j] = A[i][j];
}
}
}
void DispMat(MatGraph g) //输出邻接矩阵g
{
for (int i = 0; i < g.n; i++)
{
for (int j = 0; j < g.n; j++)
{
if (g.edges[i][j] != INF)
{
cout << g.edges[i][j] << " ";
}
else cout << "∞" << " ";
}
cout << endl;
}
}
void Prim(MatGraph g, int v) //普里姆算法
{
int lowcost[MAXV], min, n = g.n;
int closest[MAXV], k=0;
for (int i = 0; i < n; i++) //给lowcost[]和closest[]设置初值
{
lowcost[i] = g.edges[v][i];
closest[i] = v;
}
for (int i = 1; i < n; i++) //找出n-1个顶点
{
min = INF;
for (int j = 0; j < n; j++) //在(V-U)中找出和U最近的顶点k
{
if (lowcost[j] != 0 && lowcost[j] < min)
{
min = lowcost[j];
k = j;
}
}
cout << "边 ( " << closest[k] << ", " << k << " ) 权为:" << min<< endl;
lowcost[k] = 0; //标注一下k已经加入U集合
for (int j = 0; j < n; j++) //修改数组lowcost[]和closest[],找到顶点k和上一顶点的下一边最小的
{
if (g.edges[k][j] != 0 && g.edges[k][j] < lowcost[j])
{
lowcost[j] = g.edges[k][j];
closest[j] = k;
}
}
}
}
//----------------------克鲁斯卡尔算法---------------------//
void InsertSort(Edge E[], int n) //对边的权值进行排序
{
int j;
Edge temp;
for (int i = 0; i < n; i++)
{
temp = E[i];
for (j = i - 1; j >= 0 && temp.w < E[j].w; j--) //从右向左插入E[i]
{
E[j + 1] = E[j]; //大于E[i].w的后移
}
E[j + 1] = temp; //在j+1处插入E[i]
}
}
void Kruskal(MatGraph g)
{
int j, u1, v1, sn1, sn2, k;
Edge E[MaxSize]; //存放所有边
k = 0; //E数组的下标
for (int i = 0; i < g.n; i++)
{
for (int j = 0; j <= i; j++)
{
if (g.edges[i][j] != 0 and g.edges[i][j] != INF) //如果有边,存入E[]
{
E[k].u = i;
E[k].v = j;
E[k].w = g.edges[i][j];
k++;
}
}
}
InsertSort(E, g.e); //对数组E按照权值递增排序
int vset[MAXV];
for (int i = 0; i < g.n; i++) //初始化辅助数组
{
vset[i] = i;
}
k = 1; //表示当前的生成树的第几条边,初值为1
j = 0; //E中边的下标
while (k < g.n) //生成的边数<n是循环
{
//取一条边的头尾顶点
u1 = E[j].u;
v1 = E[j].v;
//分别得到两个顶点所属的集合编号sn1、sn2
sn1 = vset[u1];
sn2 = vset[v1];
if (sn1 != sn2) //如果属于不同的集合,这条边是最小生成树的边
{
cout << "边 ( " << u1 << " , " << v1 << " ) 的权值: " << E[j].w<<endl;
k++; //边数+1
for (int i = 0; i < g.n; i++) //两个集合统一编号
{
if (vset[i] == sn2)
{
vset[i] = sn1;
}
}
}
j++; //扫描下一条边
}
}
SpanningTree_main.h
#include"SpanningTree.h"
int main()
{
MatGraph G;
int A[MAXV][MAXV] = {
{0,28,INF,INF,INF,10,INF},
{28,0,16,INF,INF,INF,14},
{INF,16,0,12,INF,INF,INF},
{INF,INF,12,0,22,INF,18},
{INF,INF,INF,22,0,25,24},
{10,INF,INF,INF,25,0,INF},
{INF,14,INF,18,24,INF,0}
};
int n = 7, e = 9;
CreateMat(G, A, n, e);
cout << "图的邻接矩阵为:"<<endl;
DispMat(G);
cout << "Prim算法的结果:" << endl;
Prim(G, 0);
cout << "Kruskal算法的结果:" << endl;
Kruskal(G);
}
4、最短路径的算法实现
利用狄克斯特拉(Dijkstra)算法求上图中0结点到其它结点的最短路径,算法实现代码必须有注释。
Dijkstra.h
#pragma once
#include<iostream>
using namespace std;
const int MAXV = 10; //最大定点数
const int INF = 500; //定义邻接矩阵的无穷 (500是此图中最大的)
typedef char InfoType;
//本次我采用图的存储结构是邻接矩阵
typedef struct
{
int no; //顶点编号
InfoType info; //顶点数值信息
}VertexType; //顶点类型
typedef struct
{
int edges[MAXV][MAXV]; //邻接矩阵
int n, e; //顶点数、边数
VertexType vexs[MAXV]; //存放顶点信息
}MatGraph;
void CreateMat(MatGraph& g, int A[MAXV][MAXV], int n, int e); //创建图的邻接矩阵
void DispMat(MatGraph g); //输出邻接矩阵g
void Dijkstra(MatGraph g, int v); //迪杰斯特拉具体算法
void Dispath(MatGraph, int dist[], int path[], int S[], int v); //输出从源点 v 到其他顶点的路径
Dijkstra.cpp
#include"Dijkstra.h"
void CreateMat(MatGraph& g, int A[MAXV][MAXV], int n, int e) //创建图的邻接矩阵
{
g.e = e;
g.n = n;
for (int i = 0; i < g.n; i++)
{
for (int j = 0; j < g.n; j++)
{
g.edges[i][j] = A[i][j];
}
}
}
void DispMat(MatGraph g) //输出邻接矩阵g
{
for (int i = 0; i < g.n; i++)
{
for (int j = 0; j < g.n; j++)
{
if (g.edges[i][j] != INF)
{
cout << g.edges[i][j] << " ";
}
else cout << "∞" << " ";
}
cout << endl;
}
}
void Dijkstra(MatGraph g, int v) //具体的迪杰斯特拉算法
{
int dist[MAXV], path[MAXV];
int S[MAXV]; //S[i]=1表示i在S中,S[i]=0表示i不在S中
int Mindis, u;
for (int i = 0; i < g.n; i++)
{
dist[i] = g.edges[v][i]; //距离初始化,v到其他顶点的直达距离
S[i] = 0; //集合置空
if (g.edges[v][i] < INF) //路径的初始化
{
path[i] = v; //顶点v到i有边,让i的前一个顶点为v
}
else
{
path[i] = -1; //顶点v到i没有边,让i的前一个顶点为-1
}
}
S[v] = 1; //源点v放入S中
path[v] = 0;
for (int i = 0; i < g.n - 1; i++) //循环直到源点到所有的顶点的路径找出
{
Mindis = INF;
for (int j = 0; j < g.n; j++) //选取(V-S)中具有最小路径长度的顶点 u
{
if (S[j] == 0 && dist[j] < Mindis)
{
u = j;
Mindis = dist[j];
}
}
S[u] = 1; //顶点u加入到S中
for (int j = 0; j < g.n; j++) //修改(V-S)的顶点的最短路径
{
if (S[j] == 0)
{
if (g.edges[u][j] < INF && dist[u] + g.edges[u][j] < dist[j])
{
dist[j] = dist[u] + g.edges[u][j];
path[j] = u;
}
}
}
}
Dispath(g, dist, path, S, v); //调用输出函数
}
void Dispath(MatGraph g, int dist[], int path[], int S[], int v) //输出从源点 v 到其他顶点的路径
{
int k;
int apath[MAXV], d; //存放一条最短路径及其顶点个数
for (int i = 0; i < g.n; i++) //循环输出
{
if (S[i] == 1 && i!=v)
{
cout << " 从顶点" << v << "到顶点" << i << "的最短路径长度为:" << dist[i] << " 具体路径为: " ;
d = 0;
apath[d] = i; //添加路径上 的终点
k = path[i];
if (k == -1) //如果没有路径
{
cout << "无路径 !" << endl;
}
else
{
while (k!=v)
{
d++;
apath[d] = k;
k = path[k];
}
d++;
apath[d] = v; //添加路径上的起点
cout << apath[d]; //先输出起点
for (int j = d-1; j >=0 ; j--) //在输出其他顶点
{
cout << "-->" << apath[j];
}
cout << endl;
}
}
}
}
Dijkstra_main.cpp
#include"Dijkstra.h"
int main()
{
MatGraph G;
int A[MAXV][MAXV] = {
{0,28,INF,INF,INF,10,INF},
{28,0,16,INF,INF,INF,14},
{INF,16,0,12,INF,INF,INF},
{INF,INF,12,0,22,INF,18},
{INF,INF,INF,22,0,25,24},
{10,INF,INF,INF,25,0,INF},
{INF,14,INF,18,24,INF,0}
};
int n = 7, e = 9;
CreateMat(G, A, n, e);
cout << "创建图的邻接矩阵:\n";
DispMat(G);
cout << "Dijkstra算法的结果:\n";
Dijkstra(G, 0);
}
三、练习题
1.选择题
(1)在一个图中,所有顶点的度数之和等于图的边数的( )倍。
A.1/2
B.1
C.2
D.4
答案:C
(2)在一个有向图中,所有顶点的入度之和等于所有顶点的出度之和的 ( )倍。
A.1/2
B.1
C.2
D.4
答案:B
解释:有向图所有顶点入度之和等于所有顶点出度之和。
(3)具有 n 个顶点的有向图最多有( )条边。
A.n
B.n(n-1)
C.n(n+1)
D.n2
答案:B
解释:有向图的边有方向之分,即为从 n 个顶点中选取 2 个顶点有序排列,结果为 n(n-1)。
(4)n 个顶点的连通图用邻接距阵表示时,该距阵至少有( )个非零元素。
A.n
B.2(n-1)
C.n/2
D.n2
答案:B
(5)G 是一个非连通无向图,共有 28 条边,则该图至少有()个顶点。
A.7
B.8
C.9
D.10
答案:C
解释:8 个顶点的无向图最多有 8*7/2=28 条边,再添加一个点即构成非连通无向图,故至少有 9 个顶点。
(6)若从无向图的任意一个顶点出发进行一次深度优先搜索可以访问图中所有的顶点,则该图一定是()图。
A.非连通
B.连通
C.强连通
D.有向
答案:B
解释:即从该无向图任意一个顶点出发有到各个顶点的路径,所以该无向图是连通图。
(7)下面( )算法适合构造一个稠密图 G 的最小生成树。
A. Prim 算法
B.Kruskal 算法
C.Floyd 算法
D.Dijkstra
算法
答案:A
解释:Prim 算法适合构造一个稠密图 G 的最小生成树,Kruskal 算法适合构造一个稀疏图 G 的最小生成树。
(8)用邻接表表示图进行广度优先遍历时,通常借助( )来实现算法。
A.栈
B. 队列
C. 树
D.图
答案:B
解释:广度优先遍历通常借助队列来实现算法,深度优先遍历通常借助栈来实现算法。
(9)用邻接表表示图进行深度优先遍历时,通常借助()来实现算法。
A.栈
B. 队列
C. 树
D.图
答案:A
(10)深度优先遍历类似于二叉树的( )。
A.先序遍历
B.中序遍历
C.后序遍历
D.层次遍历
答案:A
(11)广度优先遍历类似于二叉树的()。
A.先序遍历
B.中序遍历
C.后序遍历
D.层次遍历
答案:D
(12)图的 BFS 生成树的树高比 DFS 生成树的树高()。
A.小
B.相等
C.小或相等
D.大或相等
答案:C
解释:对于一些特殊的图,比如只有一个顶点的图,其 BFS 生成树的树高和 DFS 生成树的树高相等。一般的图,根据图的 BFS 生成树和 DFS 树的算法思想,BFS 生成树的树高比 DFS 生成树的树高小。
2、填空题
1.图的定义包含一个顶点集合和一个边集合。其中,顶点集合是一个有穷________集合。
2.用邻接矩阵存储图,占用存储空间数与图中顶点个数________关,与边数________关。
3.n (n﹥0) 个顶点的无向图最多有________条边,最少有________条边。
4.n (n﹥0) 个顶点的连通无向图最少有________条边。
5.若图G的邻接矩阵不对称,则图G一定是________向图。
6.n (n﹥0) 个顶点的连通无向图各顶点的度之和最少为________。
7.设图G = (V, E),V = {V0, V1, V2, V3}, E = {(V0, V1), (V0, V2), (V0, V3), (V1, V3)},则从顶点V0开始的图G的不同深度优先序列有________种,例如______________。
8.设图G = (V, E),V = {P, Q, R, S, T}, E = {<P, Q>, <P, R>, <Q, S>, <R, T>},从顶点P出发,对图G进行广度优先搜索所得的所有序列为__________和___________。
9.n (n﹥0) 个顶点的无向图中顶点的度的最大值为________。
10.在重连通图中每个顶点的度至少为________。
11.在非重连通图中进行深度优先搜索,则深度优先生成树的根为关节点的充要条件是它至少有________个子女。
12.(n﹥0) 个顶点的连通无向图的生成树至少有________条边。
13.101个顶点的连通网络N有100条边,其中权值为1, 2, 3, 4, 5, 6, 7, 8, 9, 10的边各10条,则网络N的最小生成树各边的权值之和为_________。
14.在使用Kruskal算法构造连通网络的最小生成树时,只有当一条候选边的两个端点不在同一个________上,才有可能加入到生成树中。
15.深度优先生成树的高度比广度优先生成树的高度________。
16.求解带权连通图最小生成树的Prim算法适合于________图的情形,而Kruskal算法适合于________图的情形。
17.求解最短路径的Dijkstra算法适用于各边上的权值________的情形。若设图的顶点数为n,则该算法的时间复杂度为________。
18.若对一个有向无环图进行拓扑排序,再对排在拓扑有序序列中的所有顶点按其先后次序重新编号,则在相应的邻接矩阵中所有________元素将集中到对角线以上。
参考答案: 1. 非空 2. 有, 无 3. n(n-1)/2, 0
4. n-1 5. 有 6. 2(n-1)
7. 4,V0V1V3V2(或V0V2V1V3, V0V2V3V1, V0V3V1V2)
8. PQRST和PRQTS 9. n-1 10. 2
11. 2 12. n-1 13. 550
14. 连通分量 15. 高 16. 稠密,稀疏
17. 非负,O(n2) 18. 非零(或值为1的)
3、判断题
1.一个图的子图可以是空图,顶点个数为0。
2.存储图的邻接矩阵中,矩阵元素个数不但与图的顶点个数有关,而且与图的边数也有关。
3.一个有1000个顶点和1000条边的有向图的邻接矩阵是一个稀疏矩阵。
4.对一个连通图进行一次深度优先搜索(depth first search)可以遍访图中的所有顶点。
5.有n (n≥1) 个顶点的无向连通图最少有n-1条边。
6.有n (n≥1) 个顶点的有向强连通图最少有n条边。
7.图中各个顶点的编号是人为的,不是它本身固有的,因此可以因为某种需要改变顶点的编号。
8.如果无向图中各个顶点的度都大于2,则该图中必有回路。
9.如果有向图中各个顶点的度都大于2,则该图中必有回路。
10.图的深度优先搜索(depth first search)是一种典型的回溯搜索的例子,可以通过递归算法求解。
11.图的广度优先搜索(breadth first search)算法不是递归算法。
12.有n个顶点、e条边的带权有向图的最小生成树一般由n个顶点和n-1条边组成。
13.对于一个边上权值任意的带权有向图,使用Dijkstra算法可以求一个顶点到其它各个顶点的最短路径。
14.对一个有向图进行拓扑排序(topological sorting),一定可以将图的所有顶点按其关键码大小排列到一个拓扑有序的序列中。
15.有回路的有向图不能完成拓扑排序。
16.对任何用顶点表示活动的网络(AOV网)进行拓扑排序的结果都是唯一的。
17.用边表示活动的网络(AOE网)的关键路径是指从源点到终点的路径长度最长的路径。
18.对于AOE网络,加速任一关键活动就能使整个工程提前完成。
19.对于AOE网络,任一关键活动延迟将导致整个工程延迟完成。
20.在AOE网络中,可能同时存在几条关键路径,称所有关键路径都需通过的有向边为桥。如果加速这样的桥上的关键活动就能使整个工程提前完成。
21.用邻接矩阵存储一个图时,在不考虑压缩存储的情况下,所占用的存储空间大小只与图中的顶点个数有关,而与图的边数无关。
22.邻接表只能用于有向图的存储,邻接矩阵对于有向图和无向图的存储都适用。
23.邻接矩阵只适用于稠密图(边数接近于顶点数的平方),邻接表适用于稀疏图(边数远小于顶点数的平方)
24.存储无向图的邻接矩阵是对称的,因此只要存储邻接矩阵的下(上)三角部分就可以了。
25.连通分量是无向图中的极小连通子图。
26.强连通分量是有向图中的极大强连通子图。
27.在AOE网络中一定只有一条关键路径。
参考答案: 1. 否 2. 否 3. 是 4. 是 5. 是
6. 否 7. 是 8. 是 9. 否 10. 是
11. 是 12. 否 13. 否 14. 否 15. 是
16. 否 17. 是 18. 否 19. 是 20. 是
21. 是 22. 否 23. 是 24. 是 25. 否
26. 是 27. 否
4、算法分析题
1.已知有向图的邻接表类的表示的形式描述如下:
struct Edge { //邻接表中边结点的定义
int dest; //邻接的结点
float cost; //边的权值
Edge * link;
};
template <class Type> struct Vertex { //邻接表中顶点的定义
Type data;
Edge *adj;
};
template <class Type> struct Graph { //邻接表
Vertex<Type> * NodeTable; //顶点表
int NumVertices; //当前顶点个数
int NumEdges; //当前边数
int Degree[MaxVertices]; //各个顶点的度的记录数组
}
//下列算法是计算有向图中各个顶点的度,并保存在数组Degree[ ]中。请在 _______处
//填入合适的内容,使其成为一个完整的算法。
void FindDegree ( ) {
int i; Edge * p = NULL;
for ( i = 0; i < NumVertices; i++ ) Degree[i] = _______(1)_______;
for ( i = 0; i < NumVertices; i++)
for ( p = NodeTable[i].adj; p != NULL; p = p->link ) {
_______(2)_______;
_______(3)_______;
}
};
2.已知有向图的邻接表类的表示的形式描述如下:
struct Edge { //邻接表中边结点的定义
int dest; //邻接的结点
float cost; //边的权值
Edge * link;
};
template <class Type> struct Vertex { //邻接表中顶点的定义
Type data;
Edge *adj;
};
template <class Type> struct Graph { //邻接表
Vertex<Type> * NodeTable; //顶点表
int NumVertices; //当前顶点个数
int NumEdges; //当前边数
int Degree[MaxVertices]; //各个顶点的度的记录数组
}
//下列算法是计算有向图G中一个顶点vi的入度。请在 _______处填入合适的内容,
//使其成为一个完整的算法。
void FindDegree ( int i ) {
int deg, j; Edge * p = NULL;
deg = 0;
for ( j = 0; j < NumVertices; j++ ) {
p = NodeTable[j].adj;
while ( _______(1) _______ ) {
p = p->link;
if ( p == NULL ) break;
}
if ( p != NULL ) _______(2) _______ ;
}
return deg;
}
3.已知有向图的邻接表类的表示的形式描述如下:
struct Edge { //邻接表中边结点的定义
int dest; //邻接的结点
float cost; //边的权值
Edge * link;
};
template <class Type> struct Vertex { //邻接表中顶点的定义
Type data;
Edge *adj;
};
template <class Type> struct Graph { //邻接表
Vertex<Type> * NodeTable; //顶点表
int NumVertices; //当前顶点个数
int NumEdges; //当前边数
int Degree[MaxVertices]; //各个顶点的度的记录数组
}
//下列算法是从有向图G中删除所有以vi为弧头的有向边。请在_______ 处填入合适
//的内容,使其成为一个完整的算法。
void DeletEdge ( int i ) {
int de = 0, j; Edge *p, *q;
if ( i >= NumVertices )
{ cout << "错误输入" << endl; exit (1); }
for ( j = 0; j < NumVertices; j++ ) {
p = NodeTable[j].adj;
while (_______(1)_______)
{ q = p; p = p->link; }
if ( p != NULL ) {
if ( p != NodeTable[j].adj ) q->link = p->link;
else _______(2)_______;
delete p;
de++;
}
}
NumEdges = NumEdges - de;
}
4.已知带权图的邻接矩阵表示和邻接表类表示的形式描述分别如下:
//(1)邻接矩阵的定义
#define INFINITY INT_MAX //INT_MAX为最大整数,表示∞
const int MaxVertices = 20;
template <class Type> struct AdjMatrix {
Type * NodeTable; //顶点表定义
float arr[Maxvertices][MaxVertices]; //邻接矩阵定义
int NumVertices; //当前顶点个数
int NumEdges; //当前边数
};
//(2) 邻接表定义
struct Edge { //邻接表中边结点的定义
int dest; //邻接的结点
float cost; //边的权值
Edge * link;
};
template <class Type> struct Vertex { //邻接表中顶点的定义
Type data;
Edge *adj;
};
template <class Type> struct AdjTable { //邻接表
Vertex<Type> * NodeTable; //顶点表
int NumVertices; //当前顶点个数
int NumEdges; //当前边数
}
//下列算法是根据一个图的邻接矩阵建立该图的邻接表,请在_______处填入合适
//的内容,使其成为一个完整的算法。
AdjTable<Type> * convertM ( ) {
//将图的邻接矩阵(用this指针指示)转换为邻接表,函数返回邻接表的地址。
AdjTable<Type> * A; Edge *e;
A->NodeTable = new Vertex<Type>[NumVertices];
A->NumEdges = NumEdges;
A->NumVertices = NumVertices;
for ( int i = 0; i < NumVertices; i++ ) {
A->NodeTable[i].data = NodeTable[i];
A->NodeTable[i].adj =_______(1)_______;
for ( int j = 0; j < NumVertices; j++ )
if ( arr[i][j] != INFINITY && arr[i][j] != 0 ) {
e = new Edge;
e->dest = j;
e->cost=_______(2)_______;
e->link = A->NodeTable[i].adj;
_______(3)_______;
}
}
return A;
}
5.已知带权图的邻接矩阵表示和邻接表类表示的形式描述分别如下:
//(1) 邻接矩阵的定义
#define INFINITY INT_MAX //INT_MAX为最大整数,表示∞
const int MaxVertices = 20;
template <class Type> struct AdjMatrix {
Type * NodeTable; //顶点表定义
float arr[Maxvertices][MaxVertices]; //邻接矩阵定义
int NumVertices; //当前顶点个数
int NumEdges; //当前边数
};
//(2) 邻接表定义
struct Edge { //邻接表中边结点的定义
int dest; //邻接的结点
float cost; //边的权值
Edge * link;
};
template <class Type> struct Vertex { //邻接表中顶点的定义
Type data;
Edge *adj;
};
template <class Type> struct AdjTable { //邻接表
Vertex<Type> * NodeTable; //顶点表
int NumVertices; //当前顶点个数
int NumEdges; //当前边数
}
//下列算法是根据一个图的邻接表存储结构建立该图的邻接矩阵存储结构,
//请在____________处填入合适的内容,使其成为一个完整的算法
AdjMatrix<Type> * convertAL( ) {
//将图的邻接表(用this指针指示)转换为邻接矩阵,函数返回邻接矩阵的地址。
AdjMatrix<Type> * A; int i, j; Edge *p;
A->NodeTable = new Vertex<Type>[NumVertices];
A->arr = new float [Maxvertices][MaxVertices];
A->NumEdges = NumEdges;
A->NumVertices = NumVertices;
for ( i = 0; i < NumVertices; i++ ) {
for ( j = 0; j < NumVertices; j++ ) A->arr[i][j] = INFINITY;
A->NodeTable[i] = _________(1)_________;
}
for ( i = 0; i < NumVertices; i++ ) {
p = NodeTable[i].adj;
while ( p != NULL ) {
A->arr[i][p->dest] = __________(2)__________;
________________(3)________________;
}
}
}
6.已知图的邻接表和逆邻接表的形式描述如下:
struct Edge { //结点定义
int dest; //邻接结点
float cost; //边的权值
Edge * link;
};
template <class Type> struct Vertex { //顶点定义
Type data;
Edge *adj;
};
template<class Type> struct Graph { //邻接表与逆邻接表定义
Vertex<Type> * NodeTable; //邻接表顶点表
Vertex<Type> * OppositNodeTable; //逆邻接表顶点表
int NumVertices; //当前顶点个数
int NumEdges; //当前边数
}
//下列算法是根据一个图的邻接表存储结构建立该图的逆邻接表存储结构,请
//在_______处填入合适的内容,使其成为一个完整的算法。
void convertM ( ) {
int i; Edge *p, *q;
OppositNodeTable = new Vertex<Type>[NumVertices];
for ( i = 0; i < NumVertices; i++ ) {
OppositNodeTable[i].data = NodeTable[i].data;
OppositNodeTable[i].adj = NULL;
}
for ( i = 0; i < NumVertices; i++ ) {
p = NodeTable[i].adj;
while ( p != NULL ) {
q = new Edge;
q->dest = i;
q->cost = p->cost;
_____________(1)_____________;
OppositNodeTable[p->dest].adj = q;
______________(2)_____________;
}
}
}
参考答案:
1.填空结果
(1) 0 //2分
(2) Degree[p->dest]++ //2分
(3) Degree[i]++ //2分 ( 注:(2), (3)互换也正确 )
2.填空结果
(1) p != NULL && p->dest != i //3分
(2) deg++; //3分
3.填空结果
(1) p != NULL && p->dest != i //3分
(2) NodeTable[j].adj = p->link; //3分
4.填空结果
(1) NULL //2分
(2) arr[i][j] //2分
(3) A->NodeTable[i].adj = e //2分
5.填空结果
(1) NodeTable[i].data //2分
(2) p->cost //2分
(3) p = p->link //2分
6.填空结果
(1) q->link = OppositNodeTable[p->dest].adj //3分
(2) p = p->link //3分
六、算法设计题
1.试编写函数用邻接表存储结构实现
template<class Type> int AdjTable <Type>::GetFirstNeighbor ( int v );
//给出顶点位置为v 的第一个邻接顶点的位置,如果找不到,则函数返回-1
2.用邻接表存储结构实现带权有向图的构造函数
template <class Type> AdjTable <Type>:: AdjTable ( int sz = DefaultSize ) :
MaxVertices(sz) { ……输入顶点个数、数据及各条边的信息,建立邻接表…… }
3.试编写函数用邻接表存储结构实现
template<class Type> int AdjTable <Type>::GetNextNeighbor ( int v, int w );
//给出顶点v的邻接顶点w的下一个邻接顶点的位置,若没有下一个邻接顶点,则函数返回-1
参考答案
1.算法实现如下
template<class Type> int AdjTable <Type>::GetFirstNeighbor ( int v ) {
//给出顶点位置为v 的第一个邻接顶点的位置,如果找不到,则函数返回-1
if ( v != -1 ) {
Edge *p = NodeTable (v).adj; //4分
if ( p != NULL ) return p->dest; //4分
}
return –1;
}
2.算法实现如下
template <class Type> AdjTable <Type>::AdjTable ( int sz = DefaultSize ) :
MaxVertices ( sz ) {
int i, k, j; Type tail, head; float weight; Edge *p;
NodeTalbe = new Vertex<Type>[MaxVertices];
cin >> NumVertices;
for ( i = 0; i < NumVertices; i++ ) { //4分
cin >> NodeTable[i].data;
NodeTable[i].adj = NULL;
}
cin >> NumEdges;
for ( i = 0; i < NumEdges; i++ ) { //4分
cin >> tail >> head >> weight;
k = GetVertexPos(tail); j = GetVertexPos(head);
p = new Edge;
p->dest = j; p->cost = weight;
p->link = NodeTable[k].adj; NodeTable[k].adj = p;
}
}
3.算法实现如下
template <class Type> int AdjTable <Type>::GetNextNeighbor ( int v, int w ) {
//给出顶点v的邻接顶点w的下一个邻接顶点的位置,若没有下一个邻接顶点,则
//函数返回-1
if ( v != -1 ) {
Edge * p = NodeTable[v].adj;
while ( p != NULL )
if (p->dest == w && p->link != NULL ) return p->link->dest;
else p = p->link;
}
return –1;
}