图存储结构和树有异曲同工之妙,不同的是,树中很少有提及环路的概念。尤其是在经过了一般的树、森林到二叉树的转换之后,便彻底不含有圈了。但是图中往往有圈,这种回路的结构让我们拥有了诸如邻接表邻接矩阵这样新的存储方式,更是有了后人痴迷追求的最短路径算法问题的诞生。
图的存储结构分为四种,含邻接矩阵,邻接表,十字链表以及邻接多重表。此处感谢@youngliu91 大佬的精妙总结。
1、邻接矩阵:实现图的最简单的方法之一是使用二维矩阵。在该矩阵实现中,每个行和列表示图中的顶点。存储在行 v 和列 w 的交叉点处的单元中的值表示是否存在从顶点 v 到顶点 w 的边。当两个顶点通过边连接时,我们说它们是相邻的。单元格中的值表示从顶点 v 到顶点 w 的边的权重。
邻接矩阵的优点是简单,对于小图,很容易看到哪些节点连接到其他节点。 然而,注意矩阵中的大多数单元格是空的。 因为大多数单元格是空的,我们说这个矩阵是“稀疏的”。矩阵不是一种非常有效的方式来存储稀疏数据。由于图中每个顶点有一行和一列,填充矩阵所需的边数为。 当每个顶点连接到每个其他顶点时,矩阵是满的。
2、邻接表:实现稀疏连接图的更空间高效的方法是使用邻接表。在邻接表实现中,我们保存Graph 对象中的所有顶点的主列表,然后图中的每个顶点对象维护连接到的其他顶点的列表。在我们的顶点类的实现中,我们将使用字典而不是列表,其中字典键是顶点,值是权重。
邻接表实现的优点是它允许我们紧凑地表示稀疏图。 邻接表还允许我们容易找到直接连接到特定顶点的所有链接。
3、十字链表:十字链表(Orthogonal List)是有向图的另一种链式存储结构。该结构可以看成是将有向图的邻接表和逆邻接表结合起来得到的。用十字链表来存储有向图,可以达到高效的存取效果。同时,代码的可读性也会得到提升。
4、邻接多重表:邻接多重表(adjacent multiList)是无向图(网)的另一种链式存储结构。在此存储结构中,图的顶点信息存放在顶点数组中,数组元素有两个域:data域,存放与顶点相关的信息;firstedge域,指向一个单链表,此单链表存储所有依附于该顶点的边的信息。这些单链表的一个表结点对应一条边,表结点有六个域:mark为标志域,用来标记该边是否被访问过;ivex和jvex分别存放该边两个顶点在图中的位置;info域存放该边相关的信息,实际上就是弧的权值,对于无向图,info域可省略; ilink指向下一条依附于顶点ivex的边对应的表结点;jlink指向下一条依附于顶点jvex的边对应的表结点。
在邻接多重表中,所有依附于同一顶点的边串联在同一链表中,由于每条边依附于两个顶点,则每个边结点同时链接在两个链表中。可见,对无向图而言,其邻接多重表和邻接表的差别,仅仅在于同一条边在邻接表中用两个结点表示,而在邻接多重表中只有一个结点。因此,除了在边结点中增加一个标志域外,邻接多重表所需的存储量和邻接表相同。在邻接多重表上,各种基本操作的实现亦和邻接表相似。
图的遍历分为广度优先搜索BFS算法和深度优先搜索DFS算法,其具体内容可以用两句简单的口令总结:
BFS:先被访问的顶点,其邻接点先被访问
DFS:后被访问的顶点,其邻接点先被访问
什么意思呢?
广度优先搜索(Breadth_First Search) 遍历类似于树的按层次遍历的过程。假设从图中某顶点v 出发,在访问了v 之后依次访问v 的各个未曾访问过和邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使“先被访问的顶点的邻接点”先于“后被访问的顶点的邻接点”被访问,直至图中所有已被访问的顶点的邻接点都被访问到。若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。换句话说,广度优先搜索遍历图的过程中以v 为起始点,由近至远,依次访问和v 有路径相通且路径长度为1,2,…的顶点。
BFS和深度优先搜索类似之处在于,在遍历的过程中也需要一个访问标志数组。并且,为了顺次访问路径长度为2、3、…的顶点,需附设队列以存储已被访问的路径长度为1、2、… 的顶点。
深度优先搜索(Depth_Fisrst Search)遍历类似于树的先根遍历,是树的先根遍历的推广。假设初始状态是图中所有顶点未曾被访问,则深度优先搜索可从图中某个顶点发v 出发,访问此顶点,然后依次从v 的未被访问的邻接点出发深度优先遍历图,直至图中所有和v 有路径相通的顶点都被访问到;若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。显然,这是一个递归的过程。为了在遍历过程中便于区分顶点是否已被访问,需附设访问标志数组visited[0:n-1], ,其初值为FALSE ,一旦某个顶点被访问,则其相应的分量置为TRUE。
归纳起来,正如上面的两句口令。短短28字总结了如此长篇的算法规则,实属鞭辟入里的总结!
利用图的遍历,我们容易想到图的最小路径、最小生成树、拓扑排序和关键路径四个有趣的应用。这些个应用不仅在历年的考试中频频出现,也是今后我们做信息、自动化等行业必不可少的减少耗时的设计思路背景。
1、图的最短路径算法:
Dijkstra(迪杰斯特拉)算法
他的算法思想是按路径长度递增的次序一步一步并入来求取,是贪心算法的一个应用,用来解决单源点到其余顶点的最短路径问题。
算法思想:
首先,我们引入一个辅助向量D,它的每个分量D[i]表示当前找到的从起始节点v到终点节点vi的最短路径的长度。它的初始态为:若从节点v到节点vi有弧,则D[i]为弧上的权值,否则D[i]为∞,显然,长度为D[j] = Min{D[i] | vi ∈V}的路径就是从v出发最短的一条路径,路径为(v, vi)。
那么,下一条长度次短的最短路径是哪一条呢?假设次短路径的终点是vk,则可想而知,这条路径或者是(v, vk)或者是(v, vj, vk)。它的长度或者是从v到vk的弧上的权值,或者是D[j]和从vj到vk的权值之和。
一般情况下,假设S为已知求得的最短路径的终点集合,则可证明:一下条最短路径(设其终点为x)或者是弧(v, x)或者是中间只经过S中的顶点而最后到达顶点x的路径。这可用反证法来证明,假设此路径上有一个顶点不在S中,则说明存在一条终点不在S中而长度比此路径短的路径。但是这是不可能的。因为,我们是按路径常度的递增次序来产生个最短路径的,故长度比此路径端的所有路径均已产生,他们的终点必定在S集合中,即假设不成立。
因此下一条次短的最短路径的长度是:D[j] = Min{D[i] | vi ∈ V - S},其中,D[i]或者是弧(v, vi)的权值,或者是D[k](vk ∈ S)和弧(vk, vi)上权值之和。
Floyd(弗洛伊德)算法
Floyd算法是一个经典的动态规划算法。是解决任意两点间的最短路径(称为多源最短路径问题)的一种算法,可以正确处理有向图或负权的最短路径问题。(动态规划算法是通过拆分问题规模,并定义问题状态与状态的关系,使得问题能够以递推(分治)的方式去解决,最终合并各个拆分的小问题的解为整个问题的解。)
算法思想
从任意节点i到任意节点j的最短路径不外乎2种可能:1)直接从节点i到节点j,2)从节点i经过若干个节点k到节点j。所以,我们假设arcs(i,j)为节点i到节点j的最短路径的距离,对于每一个节点k,我们检查arcs(i,k) + arcs(k,j) < arcs(i,j)是否成立,如果成立,证明从节点i到节点k再到节点j的路径比节点i直接到节点j的路径短,我们便设置arcs(i,j) = arcs(i,k) + arcs(k,j),这样一来,当我们遍历完所有节点k,arcs(i,j)中记录的便是节点i到节点j的最短路径的距离。(由于动态规划算法在执行过程中,需要保存大量的临时状态(即小问题的解),因此它天生适用于用矩阵来作为其数据结构,因此在本算法中,我们将不使用Guava-Graph结构,而采用邻接矩阵来作为本例的数据结构)
2、图的最小生成树问题:对于一张图,我们有一个定理:n个点用n-1条边连接,形成的图形只可能是树。我们可以这样理解:树的每一个结点都有一个唯一的父亲,也就是至少有n条边,但是根节点要除外,所以就是n-1条边。还有一种理解:树里不存在环,那么既要连接n个点又不能形成环,只能用n-1条边。那么,对于一张n个点带权图,它的生成树就是用其中的n-1条边来连接这n个点,那么最小生成树就是n-1条边的边权之和最小的一种方案,简单的理解,就是用让这张图只剩下n-1条边,同时这n-1条边的边权总和最小。求最小生成树的过程,我们可以理解为建一棵树。要使边权总和最小,我们不难想到可以用贪心的思想:让最小生成树里的每一条边都尽可能小,那么我们有两种思路,分别对应着两种算法:
普里姆(Prim)算法
思想:先选取一个顶点加入最小生成树,再选取与该顶点相连的边中的最小权值对应的顶点加入生成树,将这两个顶点作为一棵新的最小生成树,继续判断与该树相连的边的最小权值对应的顶点,并将其加入最小生成树,直到所有顶点均加入生成树为止。
克鲁斯卡尔算法(Kruskal)
思想:将图的存储结构使用边集数组的形式表示,并将边集数组按权值从小到大排序,遍历边集数组,每次选取一条边并判断是否构成环路,不会构成环路则将其加入最小生成树,最终只会包含n-1条边(n为无向图的顶点数)。
其中边集数组的结构如图所示:
3、拓扑排序:拓扑排序是指将AOV网中的顶点排成一个线性序列,该序列必须满足:若从顶点i到顶点j有一条路径,则该序列中顶点i一定在顶点j之前。
4、关键路径:在AOE网中,从源点到汇点的带权路径长度最大的路径成为关键路径,关键路径上的活动称为关键活动。
P.S.AOV和AOE网对应的有向无环图(Directed Acycline Graph, DAG)是一类特殊的有向图。DAG有着广泛应用,其中AOE网和AOV网都是DAG的典型应用。具体有关AOV和AOE网图的知识可参看@Finley大佬总结的内容,截图如下。由于拓扑排序和关键路径对于非计算机专业学生并未有掌握要求,故此处从略。
上述总结对应的代码如下:具体内容已嵌入注释中。
// 第七章 图.cpp : 此文件不包含 "main" 函数。程序执行将分别在下述的各个cpp中。编写—JoeyBG,算法尚有不足之处,敬请谅解。
//
/*
#include
#include
#include
#include
using namespace std;
*/
// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单
// 入门使用技巧:
// 1. 使用解决方案资源管理器窗口添加/管理文件
// 2. 使用团队资源管理器窗口连接到源代码管理
// 3. 使用输出窗口查看生成输出和其他消息
// 4. 使用错误列表窗口查看错误
// 5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目
// 6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件
//邻接矩阵创建无向图
#include
using namespace std;
#define MaxVnum 100 //顶点数最大值
typedef char VexType; //顶点的数据类型,根据需要定义
typedef int EdgeType; //边上权值的数据类型,若不带权值的图,则为0或1
typedef struct{
VexType Vex[MaxVnum];
EdgeType Edge[MaxVnum][MaxVnum];
int vexnum,edgenum; //顶点数,边数
}AMGragh;
int locatevex(AMGragh G,VexType x)
{
for(int i=0;i>G.vexnum;
cout<<"请输入边数:"<>G.edgenum;
cout<<"请输入顶点信息:"<>G.Vex[i];
for(int i=0;i>u>>v;
i=locatevex(G,u);//查找顶点u的存储下标
j=locatevex(G,v);//查找顶点v的存储下标
if(i!=-1&&j!=-1)
G.Edge[i][j]=G.Edge[j][i]=1; //邻接矩阵储置1
else
{
cout<<"输入顶点信息错!请重新输入!"<
//创建有向图的邻接表
#include
using namespace std;
const int MaxVnum=100;//顶点数最大值
typedef char VexType;//顶点的数据类型为字符型
typedef struct AdjNode{ //定义邻接点类型
int v; //邻接点下标
struct AdjNode *next; //指向下一个邻接点
}AdjNode;
typedef struct VexNode{ //定义顶点类型
VexType data; // VexType为顶点的数据类型,根据需要定义
AdjNode *first; //指向第一个邻接点
}VexNode;
typedef struct{//定义邻接表类型
VexNode Vex[MaxVnum];
int vexnum,edgenum; //顶点数,边数
}ALGragh;
int locatevex(ALGragh G,VexType x)
{
for(int i=0;iv=j;
s->next=G.Vex[i].first;
G.Vex[i].first=s;
}
void printg(ALGragh G)//输出邻接表
{
cout<<"----------邻接表如下:----------"<v<<"] ";
t=t->next;
}
cout<>G.vexnum>>G.edgenum;
cout<<"请输入顶点信息:"<>G.Vex[i].data;
for(i=0;i>u>>v;
i=locatevex(G,u);//查找顶点u的存储下标
j=locatevex(G,v);//查找顶点v的存储下标
if(i!=-1&&j!=-1)
insertedge(G,i,j);
else
{
cout<<"输入顶点信息错!请重新输入!"<
//邻接表存储图的BFS算法
#include
#include//引入队列头文件
using namespace std;
const int MaxVnum=100;//顶点数最大值
bool visited[MaxVnum];//访问标志数组,其初值为"false"
typedef char VexType;//顶点的数据类型为字符型
typedef struct AdjNode{ //定义邻接点类型
int v; //邻接点下标
struct AdjNode *next; //指向下一个邻接点
}AdjNode;
typedef struct VexNode{ //定义顶点类型
VexType data; // VexType为顶点的数据类型,根据需要定义
AdjNode *first; //指向第一个邻接点
}VexNode;
typedef struct{//定义邻接表类型
VexNode Vex[MaxVnum];
int vexnum,edgenum; //顶点数,边数
}ALGragh;
int locatevex(ALGragh G,VexType x)
{
for(int i=0;iv=j;
s->next=G.Vex[i].first;
G.Vex[i].first=s;
}
void printg(ALGragh G)//输出邻接表
{
cout<<"----------邻接表如下:----------"<v<<"] ";
t=t->next;
}
cout<>G.vexnum>>G.edgenum;
cout<<"请输入顶点信息:"<>G.Vex[i].data;
for(i=0;i>u>>v;
i=locatevex(G,u);//查找顶点u的存储下标
j=locatevex(G,v);//查找顶点v的存储下标
if(i!=-1&&j!=-1)
insertedge(G,i,j);
else
{
cout<<"输入顶点信息错!请重新输入!"<Q; //创建一个普通队列(先进先出),里面存放int类型
cout<v;//w为u的邻接点
if(!visited[w])//w未被访问
{
cout<next;
}
}
}
void BFS_AL(ALGragh G)//非连通图,基于邻接表的广度优先遍历
{
for(int i=0;i>c;
v=locatevex(G,c);//查找顶点u的存储下标
if(v!=-1)
{
cout<<"广度优先搜索遍历连通图结果:"<
//邻接矩阵存储图的BFS算法
#include
#include//引入队列头文件
using namespace std;
#define MaxVnum 100 //顶点数最大值
bool visited[MaxVnum]; //访问标志数组,其初值为"false"
typedef char VexType; //顶点的数据类型,根据需要定义
typedef int EdgeType; //边上权值的数据类型,若不带权值的图,则为0或1
typedef struct{
VexType Vex[MaxVnum];
EdgeType Edge[MaxVnum][MaxVnum];
int vexnum,edgenum; //顶点数,边数
}AMGragh;
int locatevex(AMGragh G,VexType x)
{
for(int i=0;i>G.vexnum;
cout<<"请输入边数:"<>G.edgenum;
cout<<"请输入顶点信息:"<>G.Vex[i];
for(int i=0;i>u>>v;
i=locatevex(G,u);//查找顶点u的存储下标
j=locatevex(G,v);//查找顶点v的存储下标
if(i!=-1&&j!=-1)
G.Edge[i][j]=1; //邻接矩阵储置1,若无向图G.Edge[i][j]=G.Edge[j][i]=1
else
{
cout<<"输入顶点信息错!请重新输入!"<Q; //创建一个普通队列(先进先出),里面存放int类型
cout<>c;
v=locatevex(G,c);//查找顶点u的存储下标
if(v!=-1)
{
cout << "广度优先搜索遍历连通图结果:" <
//邻接表存储图的DFS算法
#include
using namespace std;
const int MaxVnum=100;//顶点数最大值
bool visited[MaxVnum]; //访问标志数组,其初值为"false"
typedef char VexType;//顶点的数据类型为字符型
typedef struct AdjNode{ //定义邻接点类型
int v; //邻接点下标
struct AdjNode *next; //指向下一个邻接点
}AdjNode;
typedef struct VexNode{ //定义顶点类型
VexType data; // VexType为顶点的数据类型,根据需要定义
AdjNode *first; //指向第一个邻接点
}VexNode;
typedef struct{//定义邻接表类型
VexNode Vex[MaxVnum];
int vexnum,edgenum; //顶点数,边数
}ALGragh;
int locatevex(ALGragh G,VexType x)
{
for(int i=0;iv=j;
s->next=G.Vex[i].first;
G.Vex[i].first=s;
}
void printg(ALGragh G)//输出邻接表
{
cout<<"----------邻接表如下:----------"<v<<"] ";
t=t->next;
}
cout<>G.vexnum>>G.edgenum;
cout<<"请输入顶点信息:"<>G.Vex[i].data;
for(i=0;i>u>>v;
i=locatevex(G,u);//查找顶点u的存储下标
j=locatevex(G,v);//查找顶点v的存储下标
if(i!=-1&&j!=-1)
{
insertedge(G,i,j);
insertedge(G,j,i);//无向图多插入一条边
}
else
{
cout<<"输入顶点信息错!请重新输入!"<v;//w为v的邻接点
if(!visited[w])//w未被访问
DFS_AL(G,w);//从w出发,递归深度优先遍历
p=p->next;
}
}
void DFS_AL(ALGragh G)//非连通图,基于邻接表的深度优先遍历
{
for(int i=0;i>c;
v=locatevex(G,c);//查找顶点u的存储下标
if(v!=-1)
{
cout<<"深度优先搜索遍历连通图结果:"<
//邻接矩阵存储图的DFS算法
#include
using namespace std;
#define MaxVnum 100 //顶点数最大值
bool visited[MaxVnum]; //访问标志数组,其初值为"false"
typedef char VexType; //顶点的数据类型,根据需要定义
typedef int EdgeType; //边上权值的数据类型,若不带权值的图,则为0或1
typedef struct{
VexType Vex[MaxVnum];
EdgeType Edge[MaxVnum][MaxVnum];
int vexnum,edgenum; //顶点数,边数
}AMGragh;
int locatevex(AMGragh G,VexType x)
{
for(int i=0;i>G.vexnum;
cout<<"请输入边数:"<>G.edgenum;
cout<<"请输入顶点信息:"<>G.Vex[i];
for(int i=0;i>u>>v;
i=locatevex(G,u);//查找顶点u的存储下标
j=locatevex(G,v);//查找顶点v的存储下标
if(i!=-1&&j!=-1)
G.Edge[i][j]=G.Edge[j][i]=1; //邻接矩阵储置1,若有向图G.Edge[i][j]=1
else
{
cout<<"输入顶点信息错!请重新输入!"<>c;
v=locatevex(G,c);//查找顶点u的存储下标
if(v!=-1)
{
cout<<"深度优先搜索遍历连通图结果:"<
//Dijkstra最短路径算法
#include
#include
#include
using namespace std;
const int MaxVnum=100; // 城市的个数可修改
const int INF=1e7; // 无穷大10000000
int dist[MaxVnum],p[MaxVnum];//最短距离和前驱数组
bool flag[MaxVnum]; //如果s[i]等于true,说明顶点i已经加入到集合S;否则顶点i属于集合V-S
typedef string VexType; //顶点的数据类型,根据需要定义
typedef int EdgeType; //边上权值的数据类型,若不带权值的图,则为0或1
typedef struct{
VexType Vex[MaxVnum];
EdgeType Edge[MaxVnum][MaxVnum];
int vexnum,edgenum; //顶点数,边数
}AMGragh;
int locatevex(AMGragh G,VexType x)
{
for(int i=0;i>G.vexnum;
cout<<"请输入边数:"<>G.edgenum;
cout<<"请输入顶点信息:"<>G.Vex[i];
for(int i=0;i>u>>v>>w;
i=locatevex(G,u);//查找顶点u的存储下标
j=locatevex(G,v);//查找顶点v的存储下标
if(i!=-1&&j!=-1)
G.Edge[i][j]=w; //有向图邻接矩阵
else
{
cout<<"输入顶点信息错!请重新输入!"<(dist[t]+G.Edge[t][j]))
{
dist[j]=dist[t]+G.Edge[t][j];
p[j]=t;
}
}
}
void findpath(AMGragh G,VexType u)
{
int x;
stackS;
cout<<"源点为:"<>u;
st=locatevex(G,u);//查找源点u的存储下标
Dijkstra(G,st);
cout<<"小明所在的位置:"<
//Floyd最短路径算法
#include
#include
#include
using namespace std;
#define MaxVnum 100 //顶点数最大值
const int INF=1e7; // 无穷大10000000
typedef string VexType; //顶点的数据类型,根据需要定义
typedef int EdgeType; //边上权值的数据类型,若不带权值的图,则为0或1
typedef struct{
VexType Vex[MaxVnum];
EdgeType Edge[MaxVnum][MaxVnum];
int vexnum,edgenum; //顶点数,边数
}AMGragh;
int dist[MaxVnum][MaxVnum],p[MaxVnum][MaxVnum];
int locatevex(AMGragh G,VexType x)
{
for(int i=0;i>G.vexnum;
cout<<"请输入边数:"<>G.edgenum;
cout<<"请输入顶点信息:"<>G.Vex[i];
for(int i=0;i>u>>v>>w;
i=locatevex(G,u);//查找顶点u的存储下标
j=locatevex(G,v);//查找顶点v的存储下标
if(i!=-1&&j!=-1)
G.Edge[i][j]=w; //有向图邻接矩阵存储权值
}
}
void Floyd(AMGragh G) //用Floyd算法求有向网G中各对顶点i和j之间的最短路径
{
int i,j,k;
for(i=0;i";
}
}
int main()
{
VexType start,destination;
int u,v;
system("color 0d");
AMGragh G;
CreateAMGraph(G);
Floyd(G);
print(G);
cout<<"请依次输入路径的起点与终点的名称:";
cin>>start>>destination;
u=locatevex(G,start);
v=locatevex(G,destination);
DisplayPath(G,u,v);
cout<
//Prim最小生成树算法
#include
using namespace std;
const int INF=0x3fffffff;
const int N=100;
bool s[N];
int c[N][N],closest[N],lowcost[N];
void Prim(int n, int u0, int c[N][N])
{ //顶点个数n、开始顶点u0、带权邻接矩阵C[n][n]
//如果s[i]=true,说明顶点i已加入最小生成树
//的顶点集合U;否则顶点i属于集合V-U
//将最后的相关的最小权值传递到数组lowcost
s[u0]=true; //初始时,集合中U只有一个元素,即顶点u0
int i,j;
for(i=1;i<=n;i++)
{
if(i!=u0)
{
lowcost[i]=c[u0][i];
closest[i]=u0;
s[i]=false;
}
else
lowcost[i]=0;
}
for(i=1;i<=n;i++) //在集合中V-u中寻找距离集合U最近的顶点t
{
int temp=INF;
int t=u0;
for(j=1;j<=n;j++)
{
if((!s[j])&&(lowcost[j]>n>>m;
int sumcost=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
c[i][j]=INF;
cout<<"输入结点数u,v和边值w:"<>u>>v>>w;
c[u][v]=c[v][u]=w;
}
cout<<"输入任一结点u0:"<>u0 ;
//计算最后的lowcos的总和,即为最后要求的最小的费用之和
Prim(n,u0,c);
cout<<"数组lowcost的内容为"<
//Kruskal最小生成树算法
#include
#include
using namespace std;
const int N=100;
int nodeset[N];
int n, m;
struct Edge{
int u;
int v;
int w;
}e[N*N];
bool comp(Edge x, Edge y){
return x.w>n>>m;
Init(n);
cout<<"输入结点数u,v和边值w:"<>e[i].u>>e[i].v>>e[i].w;
sort(e,e+m,comp);
int ans=Kruskal(n);
cout<<"最小的花费是:"<
//拓扑排序算法
#include
#include
#include
using namespace std;
const int MaxVnum=100;//顶点数最大值
int indegree[MaxVnum];//入度数组
typedef string VexType;//顶点的数据类型为字符型
typedef struct AdjNode{ //定义邻接点类型
int v; //邻接点下标
struct AdjNode *next; //指向下一个邻接点
}AdjNode;
typedef struct VexNode{ //定义顶点类型
VexType data; // VexType为顶点的数据类型,根据需要定义
AdjNode *first; //指向第一个邻接点
}VexNode;
typedef struct{//包含邻接表和逆邻接表
VexNode Vex[MaxVnum]; //定义邻接表
VexNode converse_Vex[MaxVnum]; //定义逆邻接表
int vexnum,edgenum; //顶点数,边数
}ALGragh;
int locatevex(ALGragh G,VexType x)
{
for(int i=0;iv=j;
s1->next=G.Vex[i].first;
G.Vex[i].first=s1;
s2=new AdjNode;//创建逆邻接表结点
s2->v=i;
s2->next=G.converse_Vex[j].first;
G.converse_Vex[j].first=s2;
}
void printg(ALGragh G)//输出邻接表
{
cout<<"----------邻接表如下:----------"<v<<"] ";
t=t->next;
}
cout<v<<"] ";
t=t->next;
}
cout<>G.vexnum>>G.edgenum;
cout<<"请输入顶点信息:"<>G.Vex[i].data;
G.converse_Vex[i].data=G.Vex[i].data;
G.Vex[i].first=NULL;
G.converse_Vex[i].first=NULL;
}
cout<<"请依次输入每条边的两个顶点u,v"<>u>>v;
i=locatevex(G,u);//查找顶点u的存储下标
j=locatevex(G,v);//查找顶点v的存储下标
if(i!=-1&&j!=-1)
insertedge(G,i,j);
else
{
cout<<"输入顶点信息错!请重新输入!"<next;
count++;
}
}
indegree[i]=count;
}
cout<<"入度数组为:"<S; //初始化一个栈S,需要引入头文件#include
FindInDegree(G); //求出各顶点的入度存入数组indegree[]中
for(i=0;iv; //k为i的邻接点
--indegree[k]; //i的每个邻接点的入度减1
if(indegree[k]==0) //若入度减为0,则入栈
S.push(k);
p=p->next; //p指向顶点i下一个邻接结点
}
}
if(m
//关键路径问题算法
#include
#include
#include
using namespace std;
const int MaxVnum=100;//顶点数最大值
int indegree[MaxVnum];//入度数组
int ve[MaxVnum]; //事件vi的最早发生时间
int vl[MaxVnum]; //事件vi的最迟发生时间
typedef string VexType;//顶点的数据类型为字符型
typedef struct AdjNode{ //定义邻接点类型
int v; //邻接点下标
int weight; //权值
struct AdjNode *next; //指向下一个邻接点指针
}AdjNode;
typedef struct VexNode{ //定义顶点类型
VexType data; //VexType为顶点的数据类型,根据需要定义
AdjNode *first; //指向第一个邻接点指针
}VexNode;
typedef struct{ //包含邻接表和逆邻接表
VexNode Vex[MaxVnum]; //定义邻接表
VexNode converse_Vex[MaxVnum]; //定义逆邻接表
int vexnum,edgenum; //顶点数,边数
}ALGragh;
int locatevex(ALGragh G,VexType x)
{
for(int i=0;iv=j;
s1->weight=w;
s1->next=G.Vex[i].first;
G.Vex[i].first=s1;
//创建逆邻接表结点
s2=new AdjNode;
s2->v=i;
s2->weight=w;
s2->next=G.converse_Vex[j].first;
G.converse_Vex[j].first=s2;
}
void printg(ALGragh G)//输出邻接表
{
cout<<"----------邻接表如下:----------"<v<<" "<weight<<"] ";
t=t->next;
}
cout<v<<" "<weight<<"] ";
t=t->next;
}
cout<>G.vexnum>>G.edgenum;
cout<<"请输入顶点信息:"<>G.Vex[i].data;
G.converse_Vex[i].data=G.Vex[i].data;
G.Vex[i].first=NULL;
G.converse_Vex[i].first=NULL;
}
cout<<"请依次输入每条边的两个顶点及权值u,v,w"<>u>>v>>w;
i=locatevex(G,u);//查找顶点u的存储下标
j=locatevex(G,v);//查找顶点v的存储下标
if(i!=-1&&j!=-1)
insertedge(G,i,j,w);
else
{
cout<<"输入顶点信息错!请重新输入!"<next;
count++;
}
}
indegree[i]=count;
}
cout<<"入度数组为:"<S; //初始化一个栈S,需要引入头文件#include
FindInDegree(G); //求出各顶点的入度存入数组indegree[]中
for(i=0;iv; //k为i的邻接点
--indegree[k]; //i的每个邻接点的入度减1
if(indegree[k]==0) //若入度减为0,则入栈
S.push(k);
p=p->next; //p指向顶点i下一个邻接结点
}
printg(G);
}
if(mv; //j为邻接顶点的序号
if(ve[j]weight) //更新顶点j的最早发生时间ve[j]
ve[j]=ve[k]+p->weight;
p=p->next; //p指向k的下一个邻接顶点
}
}
for(i=0;i=0;i--)
{
k=topo[i]; //取得逆拓扑序列中的顶点序号k
AdjNode *p=G.Vex[k].first; //p指向k的第一个邻接顶点
while(p!=NULL)
{ //根据k的邻接点,更新k的最迟发生时间
j=p->v; //j为邻接顶点的序号
if(vl[k]>vl[j]-p->weight) //更新顶点k的最迟发生时间vl[k]
vl[k]=vl[j]-p->weight;
p=p->next; //p指向k的下一个邻接顶点
}
}
cout<<"事件的最早发生时间和最迟发生时间:"<v; //j为i的邻接顶点的序号
e=ve[i]; //计算活动的最早开始时间e
l=vl[j]-p->weight; //计算活动的最迟开始时间l
if(e==l) //若为关键活动,则输出
cout<<"<"< ";
p=p->next; //p指向i的下一个邻接顶点
}
}
return true;
}
int main()
{
ALGragh G;
int *topo=new int[G.vexnum];
CreateALGraph(G);//创建有向图的邻接表和逆邻接表
printg(G);//输出邻接表和逆邻接表
CriticalPath(G,topo);
return 0;
}
/*
参考资料:
1、陈小玉:趣学数据结构,人民邮电出版社,2019.09
*/
由于图章节北理乐学平台是没有给出具体的代码练习题的,仅有知识点考核的选择题,在仔细阅读了上述总结的概念和代码后,正确回答它们不难,此略。