目录
一.课本知识点
1.图的定义和术语
- 图的定义:
- 图的基本术语:
- 不同结构中逻辑关系的对比
- 顶点的度:在无向图中,顶点v的度是指与该顶点相关联的边数,通常记为TD (v)。
- 弧头和尾:
- 无向完全图:在无向图中,如果任意两个顶点之间都存在边,则称该图为无向完全图。
- 有向完全图:在有向图中,如果任意两个顶点之间都存在方向相反的两条弧,则称该图为有向完全图。
- 含有n个顶点的无向完全图有多少条边? n×(n-1)/2条边
含有n个顶点的有向完全图有多少条弧? n×(n-1)条边 - 稀疏图:称边数很少的图为稀疏图;通常边数<<n^2
- 稠密图:称边数很多的图为稠密图。
- 权:是指对边赋予的有意义的数值量。
- 网:边上带权的图,也称网图。
- 路径:在无向图G=(V, E)中,从顶点vp到顶点vq之间的路径是一个顶点序列(vp=vi0,vi1,vi2, …, vim=vq),其中,(vij-1,vij)∈E(1≤j≤m)。若G是有向图,则路径也是有方向的,顶点序列满足<vij-1,vij>∈E。
- 路径长度:
- 回路(环):第一个顶点和最后一个顶点相同的路径。
- 简单路径:序列中顶点不重复出现的路径。
- 简单回路(简单环):除了第一个顶点和最后一个顶点外,其余顶点不重复出现的回路。
- 子图:若图G=(V,E),G'=(V',E'),如果V'V 且E' E ,则称图G'是G的子图。
- 连通图:在无向图中,如果从一个顶点vi到另一个顶点vj(i≠j)有路径,则称顶点vi和vj是连通的。如果图中任意两个顶点都是连通的,则称该图是连通图。
- 连通分量:非连通图的极大连通子图称为连通分量。
- 强连通图:在有向图中,对图中任意一对顶点vi和vj (i≠j),若从顶点vi到顶点vj和从顶点vj到顶点vi均有路径,则称该有向图是强连通图。
- 强连通分量:非强连通图的极大强连通子图。
- 生成树:n个顶点的连通图G的生成树是包含G中全部顶点的一个极小连通子图。
- 生成森林:在非连通图中,由每个连通分量都可以得到一棵生成树,这些连通分量的生成树就组成了一个非连通图的生成森林。
- 图的抽象数据类型:
ADT Graph { 数据对象V:v是具有相同特性的数据元素的集合,称为顶点集。 数据关系R:R={VR};VR={<v,w>|v,w∈V 且 P(v,w), <v,w>表示从v到w的弧, 谓词P(v,w)定义了弧<v,w>的意义或信息} 基本操作P: CreatGraph ( &G, V,VR); 初始条件:V是图的顶点集,VR是图中弧的集合。 操作结果:按V和VR的定义构造图G。 InsertVex ( &G, v); 初始条件:图G存在,v和图中顶点有相同特征。 操作结果:在图G中添加新顶点。 ………………(参见P156-257) }
-
图的基本操作:
CreatGraph(&G, V, VR) // 按定义(V, VR) 构造图 DestroyGraph(&G) // 销毁图 LocateVex(G, u); // 若G中存在顶点u,则返回该顶点在图中位置,否则返回其它信息。 GetVex(G, v); // 返回 v 的值。 PutVex(&G, v, value); // 对 v 赋值value。 FirstAdjVex(G, v); // 返回 v 的“第一个邻接点” 。若该顶点 //在 G 中没有邻接点,则返回“空”。 NextAdjVex(G, v, w); // 返回 v 的(相对于 w 的)“下一个邻接点”。 // 若 w 是 v 的最后一个邻接点, // 则返回“空”。 InsertVex(&G, v); //在图G中增添新顶点v。 DeleteVex(&G, v); // 删除G中顶点v及其相关的弧。 InsertArc(&G, v, w); // 在G中增添弧<v,w>,若G是无向的, //则还增添对称弧<w,v>。 DeleteArc(&G, v, w); //在G中删除弧<v,w>,若G是无向的, //则还删除对称弧<w,v>。 DFSTraverse(G, v, Visit()); //从顶点v起深度优先遍历图G,并对每 //个顶点调用函数Visit一次且仅一次。 BFSTraverse(G, v, Visit()); //从顶点v起广度优先遍历图G,并对每 //个顶点调用函数Visit一次且仅一次。
2.图的存储结构
a.邻接矩阵(数组)表示
- 是否可以采用顺序存储结构存储图?
- 邻接矩阵(数组)表示法
- 代码
Typedef struct ArcCell { //弧(边)结点的定义 VRType adj; // VRType可以是int类型 //顶点间关系,无权图取1或0;有权图取权值类型 InfoType *info; } ArcCell, AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; Typedef enum {DG,DN,UDG,UDN} GraphKind Typedef struct { //图的定义 VertexType vexs [MAX_VERTEX_NUM ] ; //顶点表 // VertexType 可以是char类型 AdjMatrix arcs; //邻接矩阵 int Vernum, arcnum; //顶点总数,弧(边)总数 GraphKind kind; //图的种类标志 } Mgraph;
-
用邻接矩阵构造无向网的算法
Status CreateUDN ( Mgraph &G ) { //无向网的构造,用邻接矩阵表示 Scanf (&G.vexnum, &G.arcnum); //输入总顶点数,总弧数和信息 for ( i=0;i<G.vexnum,;++i) scanf (&G.vexs[i] ); //输入顶点值,存入一维向量中 //对邻接矩阵n*n个单元初始化,adj=∞ for( i=0; i<G. vexnum; ++i ) for ( j=0; j<G.vexnum; ++j ) G.arcs[i][j]={ INFINITY,NULL}; //#include <limits.h>,#define INFINITY INT_MAX for ( k=0; k<G.arcnum; ++k) { //给邻接矩阵有关单元赋初值(循环次数=弧数) scanf(&v1, &v2, &w); //输入弧的两顶点以及对应权值 i=LocateVex(G,v1); j=LocateVex(G,v2); //找到两顶点在矩阵中的位置 G.arcs[i][j].adj=w; //输入对应权值 G.arcs[j][i] = G.arcs [i] [j]; //无向网是对称矩阵 } return OK; } // CreateUDN
对于n个顶点e条弧的网, 建网时间效率 = O(n2+n+e*n)
b. 邻接表表示
- 图的邻接表存储表示
Typedef struct VNode{ //头结点结构-顶点结构 VertexType data; //顶点信息 ArcNode * firstarc; //指向依附该顶点的第一条弧的指针 } VNode, AdjList[ MAX_VERTEX_NUM ]; Typedef struct ArcNode { //表结点结构-边结构 int adjvex; //该弧所指向的顶点位置 struct ArcNode *nextarcs; //指向下一条弧的指针 InfoArc *info; //该弧相关信息的指针 } ArcNode; Typedef struct { //图结构 AdjList vertics ; //应包含邻接表 int vexnum, arcnum; //应包含顶点总数和弧总数 int kind; //还应说明图的种类(用标志) } ALGraph;
-
当邻接表的存储结构形成后,图便唯一确定!
-
邻接表与邻接矩阵有什么异同之处?
c. 十字链表![](https://img-blog.csdnimg.cn/6d40e08c84eb43ce954ea4266a6df994.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAaGVudV9OZXd4YzAz,size_20,color_FFFFFF,t_70,g_se,x_16)
![](https://img-blog.csdnimg.cn/682eb9f80d534ec68ba85b932a9077ab.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAaGVudV9OZXd4YzAz,size_20,color_FFFFFF,t_70,g_se,x_16)
- 代码
#define MAX_VERTEX_NUM 20 Typedef struct ArcBox { //弧结点结构 int tailvex , headvex ; struct ArcBox * hlink , tlink; InfoType *info; } ArcBox; Typedef struct VexNode{ //顶点结构 VertexType data; ArcBox * firstin,*firstout; }VexNode; Typedef struct { VexNode xlist[ MAX_VERTEX_NUM ]; //表头向量 int vexnum, arcnum; }OLGraph; //图结构
d. 邻接多重表![](https://img-blog.csdnimg.cn/9fd22a8b06a94f27bc52f03392a09293.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAaGVudV9OZXd4YzAz,size_20,color_FFFFFF,t_70,g_se,x_16)
![](https://img-blog.csdnimg.cn/6fe4c2a118394d2d87233b726912bec1.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAaGVudV9OZXd4YzAz,size_20,color_FFFFFF,t_70,g_se,x_16)
3.图的遍历
- 图的遍历操作要解决的关键问题
a.深度优先遍历(DFS)
- 基本思想 :
- 深度优先搜索(遍历)步骤:
- 计算机如何实现DFS?
- 在图的邻接表中如何进行DFS?
- 深度优先遍历图的算法:
Boolean visited [MAX]; // 访问标志数组 Status (*VisitFunc) (int v); //函数指针变量 Void DFSTraverse( Graph G, Status (*Visit) (int v)) { // 对图G做深度优先遍历 VisitFunc = Visit; // 使用全局变量VisitFunc,使DFS不必设函数指针参数 for (v=0; v<G.vexnum; ++v) visited[v] = FALSE; // 访问标志数组初始化 for (v=0; v<G.vexnum; ++v) //对于连通图从某一个结点出发就可以访问到所有结点,如果是非连通图需要从多个结点出发才能访问所有结点。 if (!visited[v]) DFS(G,v); // 对尚未访问的顶点调用DFS } Void DFS (Graph G, int v) { // 从第v个顶点出发递归地深度优先遍历图G visited [v] = TRUE; VisitFunc (v); //访问第v个顶点 for ( w = FirstAdjVex (G, v); w>=0; w = NextAdjVex (G, v, w) ) if (!visited[w]) DFS(G,w); // 对v的尚未访问的邻接点w递归调用DFS } Status FirstAdjVex(MGraph G,int v) { int i; for(i=0;i<G.vexnum;i++) if(G.arcs[v][i].adj!=INFINITY) return i; return OVERFLOW;} Status NextAdjVex(MGraph G,int v,int w) { int i; for(i=w+1;i<G.vexnum;i++) if(G.arcs[v][i].adj!=INFINITY) return i; return OVERFLOW;}
-
DFS 算法效率分析:
b.广度优先遍历![](https://img-blog.csdnimg.cn/a9fe261c40f9447fa4af9456c0515c77.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAaGVudV9OZXd4YzAz,size_20,color_FFFFFF,t_70,g_se,x_16)
![](https://img-blog.csdnimg.cn/92d981d1d6574aa19616176b4e809f5b.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAaGVudV9OZXd4YzAz,size_20,color_FFFFFF,t_70,g_se,x_16)
- BFS算法如何编程?
Void BFSTraverse ( Graph G, Status (* Visit) (int v) ) { for ( v=0; v<G.vexnum; ++v ) visited[v] = FALSE ; InitQuene(Q); // 置空的辅助队列Q for ( v=0; v<G.vexnum; ++v ) if (!visited[v] ) // v尚未访问 { visited[v] = TRUE; Visit(v); EnQuene (Q,v); // v入队列 while (!QueneEmpty(Q)) { DeQuene (Q, u); // 队头元素出队并置为u for (w=FirstAdjVex(G,u); w>=0; w=NextAdjVex(G,u,w)) if (! Visited [w] ) // w为u的尚未访问的邻接顶点 { Visited [w] = TRUE; Visit(w); EnQuene (Q,w); } //if } //while } // if } // BFSTraverse
-
BFS 算法效率分析:
4.图的连通性问题
a.求图的生成树![](https://img-blog.csdnimg.cn/10c0a0206853479d86fbdaef784439db.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAaGVudV9OZXd4YzAz,size_20,color_FFFFFF,t_70,g_se,x_16)
![](https://img-blog.csdnimg.cn/365f38950f854440a91702527b6c589d.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAaGVudV9OZXd4YzAz,size_20,color_FFFFFF,t_70,g_se,x_16)
b.求最小生成树![](https://img-blog.csdnimg.cn/b0bc1311e6be4f2fa5e5c2b8f8bd90fd.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAaGVudV9OZXd4YzAz,size_20,color_FFFFFF,t_70,g_se,x_16)
- Kruskal(克鲁斯卡尔)算法
- Prim(普里姆)算法
- 计算机内怎样实现Prim(普里姆)算法?
void MiniSpanTree_P ( MGraph G, VertexType u)
{ //用普里姆算法从顶点u出发构造网G的最小生成树
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; // 初始,U={u}
for (i=0; i<G.vexnum; ++i) //选择其余G.vexnum-1个顶点
{ k = minimum(closedge);
// 求出T的下一个结点:第k顶点
printf (closedge[k].adjvex, G.vexs[k]);
// 输出生成树上一条边和对应的顶点
closedge[k].lowcost = 0; // 第k顶点并入U集
for (j=0; j<G.vexnum; ++j) //修改其它顶点的最小边
if (G.arcs[k][j].adj < closedge[j].lowcost)
//新顶点并入U后重新选择最小边
closedge[j] = { G.vexs[k], G.arcs[k][j].adj };
}//for
}// MiniSpanTree
- 比较两种算法
5.有向无环图及其应用
a. AOV网—拓扑排序
进行拓扑排序的方法:重复选择没有直接前驱的顶点。
b. AOE网—关键路径![](https://img-blog.csdnimg.cn/a20ec917abcb48c9bd170c7fb8b5c85a.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAaGVudV9OZXd4YzAz,size_20,color_FFFFFF,t_70,g_se,x_16)
![](https://img-blog.csdnimg.cn/ad1bd194807043ea9059098293bc1ace.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAaGVudV9OZXd4YzAz,size_20,color_FFFFFF,t_70,g_se,x_16)
![](https://img-blog.csdnimg.cn/6bdcfc10c97a4d04ae93398645e64e81.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAaGVudV9OZXd4YzAz,size_20,color_FFFFFF,t_70,g_se,x_16)
- 关键路径:在AOE网中,从始点到终点具有最大路径长度(该路径上的各个活动所持续的时间之和)的路径称为关键路径。
- 关键活动:关键路径上的活动称为关键活动。
- 事件的最早发生时间Ve(j):
- 事件的最迟发生时间Vl(i):
- 活动as的最早开始时间e(s):
- 活动as的最迟开始时间l(s):
6.最短路径
a.单源最短路径 (Dijkstra算法)![](https://img-blog.csdnimg.cn/b17ac9a5460f4dddbf939504491cbf59.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAaGVudV9OZXd4YzAz,size_20,color_FFFFFF,t_70,g_se,x_16)
![](https://img-blog.csdnimg.cn/7c84f2283e3f49b99ce12dd90d725020.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAaGVudV9OZXd4YzAz,size_20,color_FFFFFF,t_70,g_se,x_16)
b.Dijkstra(迪杰斯特拉)算法![](https://img-blog.csdnimg.cn/f60fc09f190e457c9a3d360e38567287.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAaGVudV9OZXd4YzAz,size_20,color_FFFFFF,t_70,g_se,x_16)
- Dijkstra算法描述:
(1)设A[n][n]为有向网的带权邻接矩阵, A[i][j]表示弧(vi,vj )的权值, S为已找到从源点v0出发的最短路径的终点集合,初始状态为{v0}. 辅助数组dist[n]为各终点当前找到的最短路径的长度,初始值为: dist[i]=A[v0 ,i] //即邻接矩阵中第v0行的权值 (2)选择u,使得 dist[u]=min{ dist[w] | w∈V-S } // w是S集之外的顶点,dist[u]是从源点v0到S集外所有顶点 的弧中最短的一条 S= S ∪{u} //将u加入S集 (3)对于所有不在S中的终点w,若 dist[u]+ A[u,w]< dist[w] // 即(v0,u)+(u,w)<(v0,w) 则修改dist[w]为: dist[w]= dist[u]+ A[u,w] (4)重复操作(2)、(3)共n-1次,由此求得从v0到各终点的最短路径。
c.所有顶点之间的最短路径(Floyd算法)
二.练习题
题组一:
题组二:
一、单选题
( c )1. 在一个图中,所有顶点的度数之和等于图的边数的 倍。
A.1/2 B. 1 C. 2 D. 4
( b )2. 在一个有向图中,所有顶点的入度之和等于所有顶点的出度之和的 倍。
A.1/2 B. 1 C. 2 D. 4
( b )3. 有8个结点的无向图最多有 条边。
A.14 B. 28 C. 56 D. 112
( c )4. 有8个结点的无向连通图最少有 条边。
A.5 B. 6 C. 7 D. 8
( c )5. 有8个结点的有向完全图有 条边。
A.14 B. 28 C. 56 D. 112
( b )6. 用邻接表表示图进行广度优先遍历时,通常是采用 来实现算法的。
A.栈 B. 队列 C. 树 D. 图
( a )7. 用邻接表表示图进行深度优先遍历时,通常是采用 来实现算法的。
A.栈 B. 队列 C. 树 D. 图
( c )8. 已知图的邻接矩阵,根据算法思想,则从顶点0出发按深度优先遍历的结点序列是
( d )9. 已知图的邻接矩阵同上题8,根据算法,则从顶点0出发,按深度优先遍历的结点序列是
A. 0 2 4 3 1 5 6 B. 0 1 3 5 6 4 2 C. 0 4 2 3 1 6 5 D. 0 1 3 4 2 5 6
(b)10. 已知图的邻接矩阵同上题8,根据算法思想,则从顶点0出发,按广度优先遍历的结点序列是
A. 0 2 4 3 6 5 1 B. 0 1 3 6 4 2 5 C. 0 4 2 3 1 5 6 D. 0 1 3 4 2 5 6
( c )11. 已知图的邻接矩阵同上题8,根据算法,则从顶点0出发,按广度优先遍历的结点序列是
A. 0 2 4 3 1 6 5 B. 0 1 3 5 6 4 2 C. 0 1 2 3 4 6 5 D. 0 1 2 3 4 5 6
( d )12. 已知图的邻接表如下所示,根据算法,则从顶点0出发按深度优先遍历的结点序列是
( a )13. 已知图的邻接表如下所示,根据算法,则从顶点0出发按广度优先遍历的结点序列是
( a )14. 深度优先遍历类似于二叉树的
A.先序遍历 B. 中序遍历 C. 后序遍历 D. 层次遍历
( d )15. 广度优先遍历类似于二叉树的
A.先序遍历 B. 中序遍历 C. 后序遍历 D. 层次遍历
( a )16. 任何一个无向连通图的最小生成树
A.只有一棵 B. 一棵或多棵 C. 一定有多棵 D. 可能不存在
(注,生成树不唯一,但最小生成树唯一,即边权之和或树权最小的情况唯一)
二、填空题
1. 图有 邻接矩阵 、 邻接表 等存储结构,遍历图有 深度优先遍历 、 广度优先遍历 等方法。
2. 有向图G用邻接表矩阵存储,其第i行的所有元素之和等于顶点i的 出度 。
3. 如果n个顶点的图是一个环,则它有 n 棵生成树。 (以任意一顶点为起点,得到n-1条边)
4. n个顶点e条边的图,若采用邻接矩阵存储,则空间复杂度为 O(n2) 。
5. n个顶点e条边的图,若采用邻接表存储,则空间复杂度为 O(n+e) 。
6. 设有一稀疏图G,则G采用 邻接表 存储较省空间。
7. 设有一稠密图G,则G采用 邻接矩阵 存储较省空间。
8. 图的逆邻接表存储结构只适用于 有向 图。
9. 已知一个图的邻接矩阵表示,删除所有从第i个顶点出发的方法是 将邻接矩阵的第i行全部置0 。
10. 图的深度优先遍历序列 不是 惟一的。
11. n个顶点e条边的图采用邻接矩阵存储,深度优先遍历算法的时间复杂度为 O(n2) ;若采用邻接表存储时,该算法的时间复杂度为 O(n+e) 。
12. n个顶点e条边的图采用邻接矩阵存储,广度优先遍历算法的时间复杂度为 O(n2) ;若采用邻接表存储,该算法的时间复杂度为 O(n+e) 。
13. 图的BFS生成树的树高比DFS生成树的树高 小或相等 。
14. 用普里姆(Prim)算法求具有n个顶点e条边的图的最小生成树的时间复杂度为 O(n2) ;用克鲁斯卡尔(Kruskal)算法的时间复杂度是 O(elog2e) 。
15. 若要求一个稀疏图G的最小生成树,最好用 克鲁斯卡尔(Kruskal) 算法来求解。
16. 若要求一个稠密图G的最小生成树,最好用 普里姆(Prim) 算法来求解。
17. 用Dijkstra算法求某一顶点到其余各顶点间的最短路径是按路径长度 递增 的次序来得到最短路径的。
18. 拓扑排序算法是通过重复选择具有 0 个前驱顶点的过程来完成的。
三、分析求解题