数据结构精录&总结Episode.7 数据结构入门之图(基于Visual C++)

今天酷狗上有人问我,为什么歌写的还行,却不火?

思前想后,觉得火这个东西的定义是复杂的。正是因为有太多和我一样的年轻人,在自己的事业方面沉不住气,还没有做出那么优秀的东西来却一味想着火,才会将自己陷入不能坚持于一件事的困窘之中。相反,有千千万万的方式让自己知名,但是这个世界知名的东西往往流于形式,这可能不是我们真正想要的。就好比我们都倡导不忘初心,可怜的是不忘初心这句话在传唱的过程中也渐渐地忘记了初心,这是多么令人慨叹的事啊。

技术这个东西,就算简单,就算低级,总得有人去搭建地基,就看每个人自己的选择罢了。


图存储结构和树有异曲同工之妙,不同的是,树中很少有提及环路的概念。尤其是在经过了一般的树、森林到二叉树的转换之后,便彻底不含有圈了。但是图中往往有圈,这种回路的结构让我们拥有了诸如邻接表邻接矩阵这样新的存储方式,更是有了后人痴迷追求的最短路径算法问题的诞生。

图的存储结构分为四种,含邻接矩阵,邻接表,十字链表以及邻接多重表。此处感谢@youngliu91 大佬的精妙总结。

1、邻接矩阵:实现图的最简单的方法之一是使用二维矩阵。在该矩阵实现中,每个行和列表示图中的顶点。存储在行 v 和列 w 的交叉点处的单元中的值表示是否存在从顶点 v 到顶点 w 的边。当两个顶点通过边连接时,我们说它们是相邻的。单元格中的值表示从顶点 v 到顶点 w 的边的权重。

 

邻接矩阵的优点是简单,对于小图,很容易看到哪些节点连接到其他节点。 然而,注意矩阵中的大多数单元格是空的。 因为大多数单元格是空的,我们说这个矩阵是“稀疏的”。矩阵不是一种非常有效的方式来存储稀疏数据。由于图中每个顶点有一行和一列,填充矩阵所需的边数为|V|^{2}。 当每个顶点连接到每个其他顶点时,矩阵是满的。

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 <iostream>
#include<algorithm>
#include<math.h>
#include<iomanip>
using namespace std;
*/

// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单
// 入门使用技巧: 
//   1. 使用解决方案资源管理器窗口添加/管理文件
//   2. 使用团队资源管理器窗口连接到源代码管理
//   3. 使用输出窗口查看生成输出和其他消息
//   4. 使用错误列表窗口查看错误
//   5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目
//   6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件
//邻接矩阵创建无向图
#include<iostream>
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;i++)//查找顶点信息的下标
       if(x==G.Vex[i])
        return i;
    return -1;//没找到
}


void CreateAMGraph(AMGragh &G)
{
    int i,j;
    VexType u,v;
    cout<<"请输入顶点数:"<<endl;
    cin>>G.vexnum;
    cout<<"请输入边数:"<<endl;
    cin>>G.edgenum;
    cout<<"请输入顶点信息:"<<endl;
    for(int i=0;i<G.vexnum;i++)//输入顶点信息,存入顶点信息数组
        cin>>G.Vex[i];
    for(int i=0;i<G.vexnum;i++)//初始化邻接矩阵所有值为0,如果是网,则初始化邻接矩阵为无穷大
		for(int j=0;j<G.vexnum;j++)
			G.Edge[i][j]=0;
    cout<<"请输入每条边依附的两个顶点:"<<endl;
    while(G.edgenum--)
    {
		cin>>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<<"输入顶点信息错!请重新输入!"<<endl;
			G.edgenum++;//本次输入不算
		}
    }
}

void print(AMGragh G)//输出邻接矩阵
{
    cout<<"图的邻接矩阵为:"<<endl;
    for(int i=0;i<G.vexnum;i++)
    {
        for(int j=0;j<G.vexnum;j++)
			cout<<G.Edge[i][j]<<"\t";
        cout<<endl;
    }
}


int main()
{
    AMGragh G;
    CreateAMGraph(G);
    print(G);
    return 0;
}

//创建有向图的邻接表
#include<iostream>
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;i<G.vexnum;i++)//查找顶点信息的下标
		if(x==G.Vex[i].data)
			return i;
    return -1;//没找到
}

void insertedge(ALGragh &G,int i,int j)//插入一条边
{
    AdjNode *s;
    s=new AdjNode;
    s->v=j;
    s->next=G.Vex[i].first;
    G.Vex[i].first=s;
}

void printg(ALGragh G)//输出邻接表
{
   cout<<"----------邻接表如下:----------"<<endl;
   for(int i=0;i<G.vexnum;i++)
   {
       AdjNode *t=G.Vex[i].first;
       cout<<cout<<G.Vex[i].data<<":  ";
       while(t!=NULL)
       {
           cout<<"["<<t->v<<"]  ";
           t=t->next;
       }
       cout<<endl;
   }
}

void CreateALGraph(ALGragh &G)//创建有向图邻接表
{
    int i,j;
    VexType u,v;
    cout<<"请输入顶点数和边数:"<<endl;
    cin>>G.vexnum>>G.edgenum;
    cout<<"请输入顶点信息:"<<endl;
    for(i=0;i<G.vexnum;i++)//输入顶点信息,存入顶点信息数组
        cin>>G.Vex[i].data;
    for(i=0;i<G.vexnum;i++)
        G.Vex[i].first=NULL;
    cout<<"请依次输入每条边的两个顶点u,v"<<endl;
    while(G.edgenum--)
    {
        cin>>u>>v;
        i=locatevex(G,u);//查找顶点u的存储下标
        j=locatevex(G,v);//查找顶点v的存储下标
        if(i!=-1&&j!=-1)
            insertedge(G,i,j);
        else
        {
           cout<<"输入顶点信息错!请重新输入!"<<endl;
           G.edgenum++;//本次输入不算
        }
    }
}

int main()
{
    ALGragh G;
    CreateALGraph(G);//创建有向图邻接表
    printg(G);//输出邻接表
    return 0;
}
//邻接表存储图的BFS算法
#include<iostream>
#include<queue>//引入队列头文件
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;i<G.vexnum;i++)//查找顶点信息的下标
       if(x==G.Vex[i].data)
        return i;
    return -1;//没找到
}

void insertedge(ALGragh &G,int i,int j)//插入一条边
{
    AdjNode *s;
    s=new AdjNode;
    s->v=j;
    s->next=G.Vex[i].first;
    G.Vex[i].first=s;
}

void printg(ALGragh G)//输出邻接表
{
   cout<<"----------邻接表如下:----------"<<endl;
   for(int i=0;i<G.vexnum;i++)
   {
       AdjNode *t=G.Vex[i].first;
       cout<<G.Vex[i].data<<":  ";
       while(t!=NULL)
       {
           cout<<"["<<t->v<<"]  ";
           t=t->next;
       }
       cout<<endl;
   }
}

void CreateALGraph(ALGragh &G)//创建有向图邻接表
{
    int i,j;
    VexType u,v;
    cout<<"请输入顶点数和边数:"<<endl;
    cin>>G.vexnum>>G.edgenum;
    cout<<"请输入顶点信息:"<<endl;
    for(i=0;i<G.vexnum;i++)//输入顶点信息,存入顶点信息数组
        cin>>G.Vex[i].data;
    for(i=0;i<G.vexnum;i++)
        G.Vex[i].first=NULL;
    cout<<"请依次输入每条边的两个顶点u,v"<<endl;
    while(G.edgenum--)
    {
        cin>>u>>v;
        i=locatevex(G,u);//查找顶点u的存储下标
        j=locatevex(G,v);//查找顶点v的存储下标
        if(i!=-1&&j!=-1)
            insertedge(G,i,j);
        else
        {
			cout<<"输入顶点信息错!请重新输入!"<<endl;
			G.edgenum++;//本次输入不算
        }
    }
}

void BFS_AL(ALGragh G,int v)//基于邻接表的广度优先遍历
{
    int u,w;
    AdjNode *p;
    queue<int>Q; //创建一个普通队列(先进先出),里面存放int类型
    cout<<G.Vex[v].data<<"\t";
    visited[v]=true;
    Q.push(v); //源点v入队
    while(!Q.empty()) //如果队列不空
    {
        u=Q.front();//取出队头元素赋值给u
        Q.pop(); //队头元素出队
        p=G.Vex[u].first;
        while(p)//依次检查u的所有邻接点
        {
            w=p->v;//w为u的邻接点
            if(!visited[w])//w未被访问
            {
               cout<<G.Vex[w].data<<"\t";
               visited[w]=true;
               Q.push(w);
            }
            p=p->next;
        }
    }
}

void BFS_AL(ALGragh G)//非连通图,基于邻接表的广度优先遍历
{
    for(int i=0;i<G.vexnum;i++)//非连通图需要查漏点,检查未被访问的顶点
    	if(!visited[i])//i未被访问,以i为起点再次广度优先遍历
       		BFS_AL(G,i);
}

int main()
{
    ALGragh G;
    int v;
    VexType c;
    CreateALGraph(G);//创建有向图邻接表
    printg(G);//输出邻接表
    cout<<"请输入遍历连通图的起始点:";
	cin>>c;
	v=locatevex(G,c);//查找顶点u的存储下标
    if(v!=-1)
    {
        cout<<"广度优先搜索遍历连通图结果:"<<endl;
        BFS_AL(G,v);
    }
    else
        cout<<"输入顶点信息错!请重新输入!"<<endl;
    return 0;
}
//邻接矩阵存储图的BFS算法
#include<iostream>
#include<queue>//引入队列头文件
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;i++)//查找顶点信息的下标
		if(x==G.Vex[i])
			return i;
    return -1;//没找到
}

void CreateAMGraph(AMGragh &G)//创建有向图的邻接矩阵
{
    int i,j;
    VexType u,v;
    cout<<"请输入顶点数:"<<endl;
    cin>>G.vexnum;
    cout<<"请输入边数:"<<endl;
    cin>>G.edgenum;
    cout<<"请输入顶点信息:"<<endl;
    for(int i=0;i<G.vexnum;i++)//输入顶点信息,存入顶点信息数组
        cin>>G.Vex[i];
    for(int i=0;i<G.vexnum;i++)//初始化邻接矩阵所有值为0,如果是网,则初始化邻接矩阵为无穷大
		for(int j=0;j<G.vexnum;j++)
			G.Edge[i][j]=0;
    cout<<"请输入每条边依附的两个顶点:"<<endl;
    while(G.edgenum--)
    {
		cin>>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<<"输入顶点信息错!请重新输入!"<<endl;
			G.edgenum++;//本次输入不算
		}
    }
}

void print(AMGragh G)//输出邻接矩阵
{
    cout<<"图的邻接矩阵为:"<<endl;
    for(int i=0;i<G.vexnum;i++)
    {
        for(int j=0;j<G.vexnum;j++)
         cout<<G.Edge[i][j]<<"\t";
        cout<<endl;
    }
}

void BFS_AM(AMGragh G,int v)//基于邻接矩阵的广度优先遍历
{
    int u,w;
    queue<int>Q; //创建一个普通队列(先进先出),里面存放int类型
    cout<<G.Vex[v]<<"\t";
    visited[v]=true;
    Q.push(v); //源点v入队
    while(!Q.empty()) //如果队列不空
    {
        u=Q.front();//取出队头元素赋值给u
        Q.pop(); //队头元素出队
        for(w=0;w<G.vexnum;w++)//依次检查u的所有邻接点
        {
            if(G.Edge[u][w]&&!visited[w])//u、w邻接而且w未被访问
            {
               cout<<G.Vex[w]<<"\t";
               visited[w]=true;
               Q.push(w);
            }
        }
    }
}

int main()
{
    int v;
    VexType c;
    AMGragh G;
    CreateAMGraph(G);
    print(G);
    cout << "请输入遍历连通图的起始点:";
	cin>>c;
	v=locatevex(G,c);//查找顶点u的存储下标
    if(v!=-1)
    {
        cout << "广度优先搜索遍历连通图结果:" <<endl;
        BFS_AM(G,v);
    }
    else
        cout << "输入顶点信息错!请重新输入!"<<endl;
    return 0;
}
//邻接表存储图的DFS算法
#include<iostream>
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;i<G.vexnum;i++)//查找顶点信息的下标
		if(x==G.Vex[i].data)
			return i;
    return -1;//没找到
}

void insertedge(ALGragh &G,int i,int j)//插入一条边
{
    AdjNode *s;
    s=new AdjNode;
    s->v=j;
    s->next=G.Vex[i].first;
    G.Vex[i].first=s;
}

void printg(ALGragh G)//输出邻接表
{
   cout<<"----------邻接表如下:----------"<<endl;
   for(int i=0;i<G.vexnum;i++)
   {
       AdjNode *t=G.Vex[i].first;
       cout<<G.Vex[i].data<<":  ";
       while(t!=NULL)
       {
           cout<<"["<<t->v<<"]  ";
           t=t->next;
       }
       cout<<endl;
   }
}

void CreateALGraph(ALGragh &G)//创建无向图邻接表
{
    int i,j;
    VexType u,v;
    cout<<"请输入顶点数和边数:"<<endl;
    cin>>G.vexnum>>G.edgenum;
    cout<<"请输入顶点信息:"<<endl;
    for(i=0;i<G.vexnum;i++)//输入顶点信息,存入顶点信息数组
        cin>>G.Vex[i].data;
    for(i=0;i<G.vexnum;i++)
        G.Vex[i].first=NULL;
    cout<<"请依次输入每条边的两个顶点u,v"<<endl;
    while(G.edgenum--)
    {
        cin>>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<<"输入顶点信息错!请重新输入!"<<endl;
           G.edgenum++;//本次输入不算
        }
    }
}

void DFS_AL(ALGragh G,int v)//基于邻接表的深度优先遍历
{
    int w;
    AdjNode *p;
    cout<<G.Vex[v].data<<"\t";
    visited[v]=true;
    p=G.Vex[v].first;
    while(p)//依次检查v的所有邻接点
    {
		w=p->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<G.vexnum;i++)//非连通图需要查漏点,检查未被访问的顶点
		if(!visited[i])//i未被访问,以i为起点再次广度优先遍历
			DFS_AL(G,i);
}

int main()
{
    ALGragh G;
    int v;
    VexType c;
    CreateALGraph(G);//创建有向图邻接表
    printg(G);//输出邻接表
    cout<<"请输入遍历连通图的起始点:";
	cin>>c;
	v=locatevex(G,c);//查找顶点u的存储下标
    if(v!=-1)
    {
        cout<<"深度优先搜索遍历连通图结果:"<<endl;
        DFS_AL(G,v);
    }
    else
        cout<<"输入顶点信息错!请重新输入!"<<endl;
    return 0;
}
//邻接矩阵存储图的DFS算法
#include<iostream>
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;i++)//查找顶点信息的下标
		if(x==G.Vex[i])
			return i;
    return -1;//没找到
}

void CreateAMGraph(AMGragh &G)//创建无向图的邻接矩阵
{
    int i,j;
    VexType u,v;
    cout<<"请输入顶点数:"<<endl;
    cin>>G.vexnum;
    cout<<"请输入边数:"<<endl;
    cin>>G.edgenum;
    cout<<"请输入顶点信息:"<<endl;
    for(int i=0;i<G.vexnum;i++)//输入顶点信息,存入顶点信息数组
        cin>>G.Vex[i];
    for(int i=0;i<G.vexnum;i++)//初始化邻接矩阵所有值为0,如果是网,则初始化邻接矩阵为无穷大
		for(int j=0;j<G.vexnum;j++)
			G.Edge[i][j]=0;
    cout<<"请输入每条边依附的两个顶点:"<<endl;
    while(G.edgenum--)
    {
       cin>>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<<"输入顶点信息错!请重新输入!"<<endl;
           G.edgenum++;//本次输入不算
       }
    }
}

void print(AMGragh G)//输出邻接矩阵
{
    cout<<"图的邻接矩阵为:"<<endl;
    for(int i=0;i<G.vexnum;i++)
    {
        for(int j=0;j<G.vexnum;j++)
			cout<<G.Edge[i][j]<<"\t";
        cout<<endl;
    }
}

void DFS_AM(AMGragh G,int v)//基于邻接矩阵的深度优先遍历
{
    int w;
    cout<<G.Vex[v]<<"\t";
    visited[v]=true;
    for(w=0;w<G.vexnum;w++)//依次检查v的所有邻接点
		if(G.Edge[v][w]&&!visited[w])//v、w邻接而且w未被访问
			DFS_AM(G,w);//从w顶点开始递归深度优先遍历
}

int main()
{
    int v;
    VexType c;
    AMGragh G;
    CreateAMGraph(G);
    print(G);
    cout<<"请输入遍历连通图的起始点:";
	cin>>c;
	v=locatevex(G,c);//查找顶点u的存储下标
    if(v!=-1)
    {
        cout<<"深度优先搜索遍历连通图结果:"<<endl;
        DFS_AM(G,v);
    }
    else
        cout<<"输入顶点信息错!请重新输入!"<<endl;
    return 0;
}
//Dijkstra最短路径算法
#include<iostream>
#include<cstring>
#include<stack>
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;i++)//查找顶点信息的下标
		if(x==G.Vex[i])
			return i;
    return -1;//没找到
}

void CreateAMGraph(AMGragh &G)
{
    int i,j,w;
    VexType u,v;
    cout<<"请输入顶点数:"<<endl;
    cin>>G.vexnum;
    cout<<"请输入边数:"<<endl;
    cin>>G.edgenum;
    cout<<"请输入顶点信息:"<<endl;
    for(int i=0;i<G.vexnum;i++)//输入顶点信息,存入顶点信息数组
        cin>>G.Vex[i];
    for(int i=0;i<G.vexnum;i++)//初始化邻接矩阵为无穷大
		for(int j=0;j<G.vexnum;j++)
			G.Edge[i][j]=INF;
    cout<<"请输入每条边依附的两个顶点及权值:"<<endl;
    while(G.edgenum--)
    {
       cin>>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<<"输入顶点信息错!请重新输入!"<<endl;
           G.edgenum++;//本次输入不算
       }
    }
}

void Dijkstra(AMGragh G,int u)
{
	for(int i=0;i<G.vexnum;i++)
	{
		dist[i]=G.Edge[u][i]; //初始化源点u到其他各个顶点的最短路径长度
		flag[i]=false;
		if(dist[i]==INF)
			p[i]=-1; //源点u到该顶点的路径长度为无穷大,说明顶点i与源点u不相邻
		else
			p[i]=u; //说明顶点i与源点u相邻,设置顶点i的前驱p[i]=u
    }
    dist[u]=0;
    flag[u]=true;   //初始时,集合S中只有一个元素:源点u
    for(int i=0;i<G.vexnum;i++)
    {
        int temp=INF,t=u;
        for(int j=0;j<G.vexnum;j++) //在集合V-S中寻找距离源点u最近的顶点t
			if(!flag[j]&&dist[j]<temp)
			{
				t=j;
				temp=dist[j];
			}
        if(t==u) return ; //找不到t,跳出循环
        flag[t]=true;  //否则,将t加入集合
        for(int j=0;j<G.vexnum;j++)//更新与t相邻接的顶点到源点u的距离
			if(!flag[j]&&G.Edge[t][j]<INF)
				if(dist[j]>(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;
	stack<int>S;
	cout<<"源点为:"<<u<<endl;
	for(int i=0;i<G.vexnum;i++)
	{
		x=p[i];
	    if(x==-1&&u!=G.Vex[i])
	    {
	        cout<<"源点到其它各顶点最短路径为:"<<u<<"--"<<G.Vex[i]<<"    sorry,无路可达"<<endl;
	        continue;
	    }
	    while(x!=-1)
	    {
			S.push(x);
			x=p[x];
	    }
	    cout<<"源点到其它各顶点最短路径为:";
	    while(!S.empty())
	    {
			cout<<G.Vex[S.top()]<<"--";
			S.pop();
	    }
	    cout<<G.Vex[i]<<"    最短距离为:"<<dist[i]<<endl;
	}
}

int main()
{
    AMGragh G;
    int st;
    VexType u;
    CreateAMGraph(G);
    cout<<"请输入源点的信息:"<<endl;
    cin>>u;
    st=locatevex(G,u);//查找源点u的存储下标
    Dijkstra(G,st);
    cout<<"小明所在的位置:"<<u<<endl;
    for(int i=0;i<G.vexnum;i++)
    {
         cout<<"小明:"<<u<<" - "<<"要去的位置:"<<G.Vex[i];
         if(dist[i]==INF)
           cout<<"sorry,无路可达"<<endl;
         else
           cout<<"最短距离为:"<<dist[i]<<endl;
     }
    findpath(G,u);
    return 0;
}

//Floyd最短路径算法
#include<iostream>
#include<cstring>
#include<windows.h>
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;i++)//查找顶点信息的下标
       if(x==G.Vex[i])
        return i;
    return -1;//没找到
}

void CreateAMGraph(AMGragh &G)//创建无向图的邻接矩阵
{
    int i,j,w;
    VexType u,v;
    cout<<"请输入顶点数:"<<endl;
    cin>>G.vexnum;
    cout<<"请输入边数:"<<endl;
    cin>>G.edgenum;
    cout<<"请输入顶点信息:"<<endl;
    for(int i=0;i<G.vexnum;i++)//输入顶点信息,存入顶点信息数组
        cin>>G.Vex[i];
    for(int i=0;i<G.vexnum;i++)//初始化邻接矩阵所有值为0,若是网,则初始化为无穷大
		for(int j=0;j<G.vexnum;j++)
	        if(i!=j)
	            G.Edge[i][j]=INF;
	        else
	            G.Edge[i][j]=0; //注意i==j时,设置为0
    cout<<"请输入每条边依附的两个顶点及权值:"<<endl;
    while(G.edgenum--)
    {
       cin>>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<G.vexnum;i++)          		//各对结点之间初始已知路径及距离
      for(j=0;j<G.vexnum;j++)
      {
          dist[i][j]=G.Edge[i][j];
          if(dist[i][j]<INF && i!=j)
			p[i][j]=i;  	//如果i和j之间有弧,则将j的前驱置为i
          else p[i][j]=-1;  //如果i和j之间无弧,则将j的前驱置为-1
      }
	for(k=0;k<G.vexnum; k++)
		for(i=0;i<G.vexnum; i++)
			for(j=0;j<G.vexnum; j++)
				if(dist[i][k]+dist[k][j]<dist[i][j])//从i经k到j的一条路径更短
                {
					dist[i][j]=dist[i][k]+dist[k][j]; //更新dist[i][j]
					p[i][j]=p[k][j];       //更改j的前驱为k
				}
}

void print(AMGragh G)
{
    int i,j;
    for(i=0;i<G.vexnum;i++)//输出最短距离数组
    {
        for(j=0;j<G.vexnum;j++)
            cout<<dist[i][j]<<"\t";
        cout<<endl;
    }
    cout<<endl;
    for(i=0;i<G.vexnum;i++)//输出前驱数组
    {
        for(j=0;j<G.vexnum;j++)
            cout<<p[i][j]<<"\t";
        cout<<endl;
    }
}

void DisplayPath(AMGragh G,int s,int t )//显示最短路径
{
	if(p[s][t]!=-1)
    {
		DisplayPath(G,s,p[s][t]);
		cout<<G.Vex[p[s][t]]<<"-->";
	}
}

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<<G.Vex[v]<<endl;
	cout<<"最短路径的长度为:"<<dist[u][v]<<endl;
	cout<<endl;
    return 0;
}
//Prim最小生成树算法
#include<iostream>
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]<temp))
            {
                t=j;
                temp=lowcost[j];
            }
        }
        if(t==u0)
            break;       //找不到t,跳出循环
        s[t]=true;     //否则,讲t加入集合U
        for(j=1;j<=n;j++) //更新lowcost和closest
        {
            if((!s[j])&&(c[t][j]<lowcost[j]))
            {
                lowcost[j]=c[t][j];
                closest[j]=t;
            }
        }
    }
}

int main()
{
    int n,m,u,v,w;
    int u0;
    cout<<"输入结点数n和边数m:"<<endl;
    cin>>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:"<<endl;
    for(int i=1;i<=m;i++)
    {
        cin>>u>>v>>w;
        c[u][v]=c[v][u]=w;
    }
    cout<<"输入任一结点u0:"<<endl;
    cin>>u0 ;
    //计算最后的lowcos的总和,即为最后要求的最小的费用之和
    Prim(n,u0,c);
    cout<<"数组lowcost的内容为"<<endl;
    for(int i=1;i<=n;i++)
        cout<<lowcost[i]<<" ";
    cout<<endl;
    for(int i=1;i<=n;i++)
       sumcost+=lowcost[i];
    cout<<"最小的花费是:"<<sumcost<<endl;
    return 0;
}
//Kruskal最小生成树算法
#include<iostream>
#include<algorithm>
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<y.w;
}
void Init(int n)
{
    for(int i=1;i<=n;i++)
        nodeset[i]=i;
}
int Merge(int a, int b)
{
    int p=nodeset[a];
    int q=nodeset[b];
    if(p==q) return 0;
    for(int i=1;i<=n;i++)//检查所有结点,把集合号是q的改为p
    {
      if(nodeset[i]==q)
        nodeset[i]=p;//a的集合号赋值给b集合号
    }
    return 1;
}
int Kruskal(int n)
{
    int ans=0;
    for(int i=0;i<m;i++)
        if(Merge(e[i].u,e[i].v))
        {
            ans+=e[i].w;
            n--;
            if(n==1)
                return ans;
        }
    return 0;
}
int main()
{
    cout<<"输入结点数n和边数m:"<<endl;
    cin>>n>>m;
    Init(n);
    cout<<"输入结点数u,v和边值w:"<<endl;
    for(int i=1;i<=m;i++)
        cin>>e[i].u>>e[i].v>>e[i].w;
    sort(e,e+m,comp);
    int ans=Kruskal(n);
    cout<<"最小的花费是:"<<ans<<endl;
    return 0;
}
//拓扑排序算法
#include<iostream>
#include<cstring>
#include<stack>
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;i<G.vexnum;i++)//查找顶点信息的下标
		if(x==G.Vex[i].data)
			return i;
    return -1;//没找到
}

void insertedge(ALGragh &G,int i,int j)//插入一条边
{
    AdjNode *s1,*s2;
    s1=new AdjNode;//创建邻接表结点
    s1->v=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<<"----------邻接表如下:----------"<<endl;
   for(int i=0;i<G.vexnum;i++)
   {
       AdjNode *t=G.Vex[i].first;
       cout<<G.Vex[i].data<<":  ";
       while(t!=NULL)
       {
           cout<<"["<<t->v<<"]  ";
           t=t->next;
       }
       cout<<endl;
   }
   cout<<"----------逆邻接表如下:----------"<<endl;
   for(int i=0;i<G.vexnum;i++)
   {
       AdjNode *t=G.converse_Vex[i].first;
       cout<<G.converse_Vex[i].data<<":  ";
       while(t!=NULL)
       {
           cout<<"["<<t->v<<"]  ";
           t=t->next;
       }
       cout<<endl;
   }
}

void CreateALGraph(ALGragh &G)//创建有向图的邻接表和逆邻接表
{
    int i,j;
    VexType u,v;
    cout<<"请输入顶点数和边数:"<<endl;
    cin>>G.vexnum>>G.edgenum;
    cout<<"请输入顶点信息:"<<endl;
    for(i=0;i<G.vexnum;i++)//输入顶点信息,存入顶点信息数组
    {
        cin>>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"<<endl;
    while(G.edgenum--)
    {
        cin>>u>>v;
        i=locatevex(G,u);//查找顶点u的存储下标
        j=locatevex(G,v);//查找顶点v的存储下标
        if(i!=-1&&j!=-1)
            insertedge(G,i,j);
        else
        {
           cout<<"输入顶点信息错!请重新输入!"<<endl;
           G.edgenum++;//本次输入不算
        }
    }
}

void FindInDegree(ALGragh G)//求出各顶点的入度存入数组indegree中
{
	int i,count;
	for(i=0;i<G.vexnum;i++)
    {
        count=0;
        AdjNode *p=G.converse_Vex[i].first;
		if(p)
		{
			while(p)
			{
				p=p->next;
				count++;
			}
		}
		indegree[i]=count;
	}
	cout<<"入度数组为:"<<endl;
	for(int i=0;i<G.vexnum;i++)//输出入度数组
       cout<<indegree[i]<<"\t";
    cout<<endl;
}

bool TopologicalSort(ALGragh G, int topo[])//拓扑排序
{
    //有向图G采用邻接表存储结构
    //若G无回路,则生成G的一个拓扑序列topo[]并返回true,否则false
	int i,m;
	stack<int>S;      //初始化一个栈S,需要引入头文件#include<stack>
    FindInDegree(G);  //求出各顶点的入度存入数组indegree[]中
    for(i=0;i<G.vexnum;i++)
		if(!indegree[i])//入度为0者进栈
            S.push(i);
	m=0;            //对输出顶点计数,初始为0
	while(!S.empty())//栈S非空
    {
		i=S.top();    //取栈顶顶点i
        S.pop();      //栈顶顶点i出栈
		topo[m]=i;    //将i保存在拓扑序列数组topo中
		m++;          //对输出顶点计数
		AdjNode *p=G.Vex[i].first;  //p指向i的第一个邻接点
		while(p) //i的所有邻接点入度减1
        {
			int k=p->v;			 //k为i的邻接点
			--indegree[k];       //i的每个邻接点的入度减1
			if(indegree[k]==0)  //若入度减为0,则入栈
				S.push(k);
			p=p->next;      //p指向顶点i下一个邻接结点
		}
	}
	if(m<G.vexnum)//该有向图有回路
        return false;
	else
		return true;
}

int main()
{
    ALGragh G;
    int *topo=new int[G.vexnum];
    CreateALGraph(G);//创建有向图的邻接表和逆邻接表
    printg(G);//输出邻接表和逆邻接表
    if(TopologicalSort(G,topo))
    {
        cout<<"拓扑序列为:"<<endl;
        for(int i=0;i<G.vexnum;i++)//输出拓扑序列
            cout<<topo[i]<<"\t";
    }
    else
        cout<<"该图有环,无拓扑序列!"<<endl;
    return 0;
}
//关键路径问题算法
#include<iostream>
#include<cstring>
#include<stack>
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;i<G.vexnum;i++)//查找顶点信息的下标
		if(x==G.Vex[i].data)
			return i;
    return -1;//没找到
}

void insertedge(ALGragh &G,int i,int j,int w)//插入一条边
{
    AdjNode *s1,*s2;
    //创建邻接表结点
    s1=new AdjNode;
    s1->v=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<<"----------邻接表如下:----------"<<endl;
   for(int i=0;i<G.vexnum;i++)
   {
       AdjNode *t=G.Vex[i].first;
       cout<<G.Vex[i].data<<":  ";
       while(t!=NULL)
       {
           cout<<"["<<t->v<<" "<<t->weight<<"]     ";
           t=t->next;
       }
       cout<<endl;
   }
   cout<<"----------逆邻接表如下:----------"<<endl;
   for(int i=0;i<G.vexnum;i++)
   {
       AdjNode *t=G.converse_Vex[i].first;
       cout<<G.converse_Vex[i].data<<":  ";
       while(t!=NULL)
       {
           cout<<"["<<t->v<<" "<<t->weight<<"]     ";
           t=t->next;
       }
       cout<<endl;
   }
}

void CreateALGraph(ALGragh &G)//创建有向图的邻接表和逆邻接表
{
    int i,j,w;
    VexType u,v;
    cout<<"请输入顶点数和边数:"<<endl;
    cin>>G.vexnum>>G.edgenum;
    cout<<"请输入顶点信息:"<<endl;
    for(i=0;i<G.vexnum;i++)//输入顶点信息,存入顶点信息数组
    {
        cin>>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"<<endl;
    while(G.edgenum--)
    {
        cin>>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<<"输入顶点信息错!请重新输入!"<<endl;
           G.edgenum++;//本次输入不算
        }
    }
}

void FindInDegree(ALGragh G)//求出各顶点的入度存入数组indegree中
{
	int i,count;
	for(i=0;i<G.vexnum; i++)
    {
        count=0;
        AdjNode *p=G.converse_Vex[i].first;
		if(p)
		{
			while(p)
			{
				p=p->next;
				count++;
			}
		}
		indegree[i]=count;
	}
	cout<<"入度数组为:"<<endl;
	for(int i=0;i<G.vexnum;i++)//输出入度数组
       cout<<indegree[i]<<"\t";
    cout<<endl;
}

bool TopologicalSort(ALGragh G, int topo[])//拓扑排序
{
    //有向图G采用邻接表存储结构
    //若G无回路,则生成G的一个拓扑序列topo[]并返回true,否则false
	int i,m;
	stack<int>S;      //初始化一个栈S,需要引入头文件#include<stack>
    FindInDegree(G);  //求出各顶点的入度存入数组indegree[]中
    for(i=0;i<G.vexnum;i++)
		if(!indegree[i])//入度为0者进栈
            S.push(i);
	m=0;            //对输出顶点计数,初始为0
	while(!S.empty())//栈S非空
    {
		i=S.top();    //取栈顶顶点i
        S.pop();      //栈顶顶点i出栈
		topo[m]=i;    //将i保存在拓扑序列数组topo中
		m++;          //对输出顶点计数
		AdjNode *p=G.Vex[i].first;  //p指向i的第一个邻接点
		while(p) //i的所有邻接点入度减1
        {
			int k=p->v;			 //k为i的邻接点
			--indegree[k];       //i的每个邻接点的入度减1
			if(indegree[k]==0)  //若入度减为0,则入栈
			  S.push(k);
			p=p->next;      //p指向顶点i下一个邻接结点
		}
		printg(G);
	}
	if(m<G.vexnum)//该有向图有回路
        return false;
	else
		return true;
}

bool CriticalPath(ALGragh G,int topo[])//G为邻接表存储的有向网,输出G的各项关键活动
{
    int n,i,k,j,e,l;
    if(TopologicalSort(G,topo))
    {
        cout<<"拓扑序列为:"<<endl;
        for(int i=0;i<G.vexnum;i++)//输出拓扑序列
            cout<<topo[i]<<"\t";
        cout<<endl;
    }
    else
        cout<<"该图有环,无拓扑序列!"<<endl;
    n=G.vexnum;                 //n为顶点个数
    for(i=0;i<n;i++)     //给每个事件的最早发生时间置初值0
		ve[i]=0;
    //按拓扑次序求每个事件的最早发生时间
    printg(G);
    for(i=0;i<n; i++)
    {
		k=topo[i];                    //取得拓扑序列中的顶点序号k
		AdjNode *p=G.Vex[k].first;    //p指向k的第一个邻接顶点
		while(p!=NULL)
		{            	             //依次更新k的所有邻接顶点的最早发生时间
			j=p->v;                   //j为邻接顶点的序号
			if(ve[j]<ve[k]+p->weight)   //更新顶点j的最早发生时间ve[j]
				ve[j]=ve[k]+p->weight;
			p=p->next;                //p指向k的下一个邻接顶点
		}
    }
    for(i=0;i<n;i++)                 			//给每个事件的最迟发生时间置初值ve[n-1]
		vl[i]=ve[n-1];
    //按逆拓扑次序求每个事件的最迟发生时间
    for(i=n-1;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<<"事件的最早发生时间和最迟发生时间:"<<endl;
	for(int i=0;i<n;i++)
       cout<<ve[i]<<"\t"<<vl[i]<<endl;

    //判断每一活动是否为关键活动
    cout<<"关键活动路径权值之和为:"<<vl[n-1]<<endl; 
	cout<<endl;
	cout<<"关键活动路径为:";
    for(i=0;i<n; i++)                //每次循环针对vi为活动开始点的所有活动
    {
        AdjNode *p=G.Vex[i].first;    //p指向i的第一个邻接顶点
        while(p!=NULL)
        {
			j=p->v;             	  //j为i的邻接顶点的序号
			e=ve[i];                 //计算活动<vi, vj>的最早开始时间e
			l=vl[j]-p->weight;      //计算活动<vi, vj>的最迟开始时间l
			if(e==l)               	//若为关键活动,则输出<vi, vj>
				cout<<"<"<<G.Vex[i].data<<","<<G.Vex[j].data<<">    ";
			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
*/

由于图章节北理乐学平台是没有给出具体的代码练习题的,仅有知识点考核的选择题,在仔细阅读了上述总结的概念和代码后,正确回答它们不难,此略。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值