一、引言
感谢宝子们一键三连支持,火速更新中~~
本文介绍了图的创建,深度优先遍历,广度优先遍历,普利姆算法最小生成树,克鲁斯卡尔算法最小生成树,迪杰斯特拉最短路径,弗洛伊德算法最短路径。
二、目的
- 掌握图的存储结构
- 掌握图的基本操作
- 掌握图的简单应用
三、环境与设备
操作系统:Windows 10
编译器:Visual Studio 2021
四、存储结构
typedef int DataType;
typedef int VRType;
typedef enum{DN,DG,UDN,UDG}GraphKind; //枚举图的 4 种类型
//****************************边表结点****************************
typedef struct node
{
int adjvex;/*存储与之联系的顶点下标*/
VRType weight;/*若为无权图,则无该部分,若为有权图,则存储权值*/
struct node *nextarc;/*下一条边*/
}ArcNode;
//****************************顶点表结点****************************
typedef struct
{
DataType vex;
ArcNode *firstArc;/*与当前顶点有联系的第一条边或者弧*/
}VertexNode;
//****************************邻接表结构****************************
typedef struct
{
VertexNode adjList[MAX];/*顶点表结点数组*/
int vexnum;/*图的实际顶点数*/
int arcnum;/*边数或弧数*/
GraphKind kind;/*图的类型*/
}ALGraph;
五、函数
(1) void CreateAL(ALGraph *G,FILE *fp)//创建图
(2) void ALDepthFirstSearch(ALGraph *G)//图的深度优先遍历
(3) void ALBreadthFirstSearch(ALGraph *G) //图的广度优先遍历
(4) void MiniTree(ALGraph *G) //普利姆算法最小生成树
(5) void kruskal(ALGraph *G) //克鲁斯卡尔算法最小生成树
(6) void MiniDis(ALGraph *G,int sVex) //迪杰斯特拉最短路径
(7) void Floyd(ALGraph *G) //弗洛伊德算法最短路径
六、核心代码
#include<stdio.h>
#include<stdlib.h>
#include<limits.h>
#define MAX 100
typedef int DataType;
typedef int VRType;
typedef enum{DN,DG,UDN,UDG}GraphKind;
//枚举图的4种类型
//****************************边表结点****************************
typedef struct node
{
int adjvex;/*存储与之联系的顶点下标*/
VRType weight;/*若为无权图,则无该部分,若为有权图,则存储权值*/ struct node *nextarc;/*下一条边*/
}ArcNode;
//****************************顶点表结点****************************
typedef struct
{
DataType vex;
ArcNode *firstArc;/*与当前顶点有联系的第一条边或者弧*/ }VertexNode;
//****************************邻接表结构****************************
typedef struct
{
VertexNode adjList[MAX];/*顶点表结点数组*/
int vexnum;/*图的实际顶点数*/
int arcnum;/*边数或弧数*/
GraphKind kind;/*图的类型*/
}ALGraph;
//****************************创建图****************************
void CreateAL(ALGraph *G,FILE *fp)
{
int i,j,temp;
ArcNode *arc;
//printf("请输入图的顶点数:");
fscanf(fp,"%d",&G->vexnum);
//printf("请输入图的种类:");
fscanf(fp,"%d",&G->kind);
for(i=0;i<G->vexnum;i++)
fscanf(fp,"%d",&G->adjList[i].vex); //printf("请输入顶点信息:"); G->adjList[i].firstArc=NULL;
}
G->arcnum=0;
//printf("请输入邻接表:\n");
for(i=0;i<G->vexnum;i++)
{
for(j=0;j<G->vexnum;j++)
{
fscanf(fp,"%d",&temp);
if(temp)
{
arc=malloc(sizeof(ArcNode));
arc->adjvex=j;
arc->weight=temp;
arc->nextarc=G->adjList[i].firstArc;
G->adjList[i].firstArc=arc;
G->arcnum++;
}
}
}
if(G->kind==0 || G->kind==2)
G->arcnum/=2;
fclose(fp);
}
//****************************输出图****************************
void OutputAL(ALGraph *G)
{
int i,j;
ArcNode *arc;
int temp[MAX];
printf("\n 图的顶点数:%d\n",G->vexnum);
printf("图的边数:%d\n",G->arcnum);
printf("图的种类:%d\n",G->kind);
printf("顶点编号:\n");
for(i=0;i<G->vexnum;i++)
{
printf("V%d\t",G->adjList[i].vex);
}
printf("\n 邻接矩阵如下:\n");
for(i=0;i<G->vexnum;i++)
{
for(j=0;j<G->vexnum;j++)temp[j]=0;
arc=G->adjList[i].firstArc;
while(arc!=NULL)
{
temp[arc->adjvex]=arc->weight;
arc=arc->nextarc;
}
for(j=0;j<G->vexnum;j++)
printf("%d\t",temp[j]);
printf("\n");
}
printf("\n\n");
}
//****************************非递归深度优先遍历****************************
int visited[MAX];//存储结点是否访问过
void ALDepthFirstSearch1(ALGraph *G)
{
int i,j;
ArcNode *arc;
int num=0;
for(i=0;i<G->vexnum;i++)
{
visited[i]=0;//初始化所有结点都未访问过
}
i=0;num=0;
while(num<G->vexnum)
{
if(!visited[i])
{
printf("v%d ",G->adjList[i].vex);
visited[i]=1;
num++;
}
arc=G->adjList[i].firstArc;
while(arc!=NULL && visited[arc->adjvex])//跳过已经访问过的表结点
{
arc=arc->nextarc;
}
if(arc==NULL)
{
for(j=0;j<G->vexnum;j++)//下一个没被访问的顶点开始
{
if(visited[j]==0)
{
i=j;
break;
}
}
}
else
i=arc->adjvex;
}
printf("\n");
}
//****************************递归深度优先遍历**************************** void DepthFirstSearch(ALGraph *G,DataType i) {
ArcNode *arc;
printf("v%d ",G->adjList[i].vex);
visited[i]=1;
arc=G->adjList[i].firstArc;
while(arc!=NULL)
{
if(!visited[arc->adjvex])
DepthFirstSearch(G,arc->adjvex);
arc=arc->nextarc;
}
}
void ALDepthFirstSearch(ALGraph *G)
{
int visited[MAX];//存储结点是否访问过
int num=0;
for(int i=0;i<G->vexnum;i++)
{
visited[i]=0;//初始化所有结点都未访问过
}
for(int i=0;i<G->vexnum;i++)
{
if(!visited[i])
DepthFirstSearch(G,i);
}
}
//****************************广度优先遍历**************************** void ALBreadthFirstSearch(ALGraph *G)
{
int i,j;
ArcNode *arc;
int visited[MAX];//存储结点是否访问过
int s[MAX],front=0,rear=0;
for(i=0;i<G->vexnum;i++)
{
visited[i]=0;//初始化所有结点都未访问过
}
i=0;
s[rear]=i;rear=(rear+1)%MAX;
visited[i]=1;
while(front!=rear)
{
i=s[front];//出队列
front=(front+1)%MAX;
printf("v%d ",G->adjList[i].vex);
arc=G->adjList[i].firstArc;
while(arc!=NULL)
{
if(!visited[arc->adjvex])//未访问的邻接点入队
{
s[rear]=arc->adjvex;
rear=(rear+1)%MAX;
visited[arc->adjvex]=1;
}
arc=arc->nextarc;
}
if(front==rear)
{
for(j=0;j<G->vexnum;j++)//如果有顶点没访问过则入队
{
if(!visited[j])
{
s[rear]=j;
rear=(rear+1)%MAX;
visited[j]=1;
}
}
}
}
printf("\n");
}
//**************************** 普 里 姆 算 法 求 最 小 生 成 树
****************************
void MiniTree(ALGraph *G)
{
int i,j,k;
ArcNode *arc;
int visited[MAX];
// 记录 U 到 V-U 具有代价最小的边 struct
{
int minlen; DataType sVex;//U
}Len[MAX];//若干条边中记录当前结点到 U 中的代价最小的边 struct
{
DataType sVex;//起点 DataType eVex;//终点
}list[MAX];//已选边
int num,min,loc;//顶点数、最小权值、权值最小的顶点 for(j=0;j<G->vexnum;j++)
{
Len[j].minlen=0;//到各点的权值初始化为 0 visited[j]=0;//初始化所有结点都未访问过
}
i=0;
visited[i]=1;
num=0;
list[num].sVex=i;
list[num].eVex=i;
num++; while(num<G->vexnum)
{
arc=G->adjList[i].firstArc;
while(arc!=NULL) //求 i 的所有邻接边中权值最小的
{
j=arc->adjvex; if(arc->weight!=0 && visited[j]!=1)
{
if(Len[j].minlen==0 || arc->weight<Len[j].minlen)
{
Len[j].minlen=arc->weight;
Len[j].sVex=i;
}
}
arc=arc->nextarc;
}
min=INT_MAX;
for(j=0;j<G->vexnum;j++) //求所有待选边中权值最小的
{
if(Len[j].minlen!=0 && visited[j]!=1 && min>Len[j].minlen)
{
min=Len[j].minlen;
loc=j;
}
}
list[num].sVex=Len[loc].sVex;//新添边的起点为权值最小的边的起点
list[num].eVex=loc;
visited[loc]=1;//标记权值最小的边的终点已访问
i=list[num].eVex;//已刚选的顶点作为起点,即顶点 i 并入 U
num++;
}
printf("普里姆算法,最小生成树序列为:\n");
for(j=1;j<G->vexnum;j++)
{
printf("Edge:v%d->v%d\n",G->adjList[list[j].sVex].vex,G->adjList[list[j].eVex]);
}
}
//**************************** 克 鲁 斯 卡 尔 算 法 求 最 小 生 成 树
****************************
void kruskal(ALGraph *G)
{
int i,j,num,min,row,col,tag;
int visited[MAX];
ArcNode *arc,*temp;
struct
{
DataType sVex;
DataType eVex;
}MiniTree[MAX];
int s[MAX],front,rear;//队列
num=0;//边的数目
while(num<G->vexnum-1)
{
min=INT_MAX;
for(i=0;i<G->vexnum;i++)
{
arc=G->adjList[i].firstArc;
while(arc)//求每一个顶点权值最小的邻接点
{
j=arc->adjvex;
if(arc->weight!=0 && arc->weight<min)
{
min=arc->weight;
row=i;
col=j;
temp=arc;
}
arc=arc->nextarc;
}
}
temp->weight=0;
MiniTree[num].sVex=row;
MiniTree[num].eVex=col;
num++;//新增一条边,但可能无效,因为可能构成环路
//以下算法判断是否构成环路
for(i=0;i<num-1;i++)
visited[i]=0;
front=rear=0;
tag=1;
while(tag)
{
for(i=0;i<num-1;i++)
{
if(MiniTree[i].sVex==col)
{
s[rear]=MiniTree[i].eVex;
rear=(rear+1)%MAX;
visited[i]=1;
}
else if(MiniTree[i].eVex==col)
{
s[rear]=MiniTree[i].sVex;
rear=(rear+1)%MAX;
visited[i]=1;
}
}
while(front!=rear)
{
col=s[front];
front=(front+1)%MAX;
if(col==row)
{
tag=0;
num--;
break;
}
for(i=0;i<num-1;i++)
{
if(visited[i]==0 && MiniTree[i].sVex==col)
{
s[rear]=MiniTree[i].eVex;
rear=(rear+1)%MAX;
visited[i]=1;
}
else if(visited[i]==0 && MiniTree[i].eVex==col)
{
s[rear]=MiniTree[i].sVex;
rear=(rear+1)%MAX;
visited[i]=1;
}
}
}
if(front==rear)break;
}
}
printf("\n 克鲁斯卡尔算法,最小生成树序列为:\n");
for(i=0;i<num;i++)
printf("v%d->v%d\n",G->adjList[MiniTree[i].sVex].vex,G->adjList[MiniTree[i].eVex].vex);
}
//**************************** 迪 杰 斯 特 拉 算 法 求 最 短 路 径
****************************
void MiniDis(ALGraph *G,int sVex)
{//sVex 是起点的存储下标
struct
{
int dis;//最短路径值
DataType path[MAX];//路径序列
int top;//顶点数目
}minipath[MAX];
int s[MAX],front=0,rear=0;
int i,j,temp;
ArcNode *arc;
for(i=0;i<G->vexnum;i++)
{
minipath[i].dis=0;//赋值为 0 表示走不通
minipath[i].top=1;
minipath[i].path[minipath[i].top-1]=sVex;
}
s[rear]=sVex;rear=(rear+1)%MAX;
while(front!=rear)
{
i=s[front];front=(front+1)%MAX;
arc=G->adjList[i].firstArc;
while(arc!=NULL)
{
j=arc->adjvex;
temp=arc->weight+minipath[i].dis;
if(minipath[j].dis!=0 && arc->adjvex!=sVex && temp<minipath[j].dis)
{
minipath[j]=minipath[i];
minipath[j].dis=temp;
minipath[j].path[minipath[j].top]=j;
minipath[j].top++;
s[rear]=j;rear=(rear+1)%MAX;
}
else if(minipath[j].dis == 0 && arc->adjvex!=sVex)
{
minipath[j]=minipath[i];
minipath[j].dis=temp;
minipath[j].path[minipath[j].top]=j;
minipath[j].top++;
s[rear]=j;rear=(rear+1)%MAX;
}
arc=arc->nextarc;
}
}
printf("\n 最短路径如下:\n");
for(i=0;i<G->vexnum;i++)
{
if(i!=sVex)
{
if(minipath[i].top>1)
{
printf("v%d->",sVex);
for(j=1;j<minipath[i].top-1;j++)
{
printf("v%d->",minipath[i].path[j]);
}
printf("v%d,dis:%d",minipath[i].path[j],minipath[i].dis);
}
else
printf("v%d 到 v%d 不通",sVex,i);
printf("\n");
}
}
}
//**************************** 弗 洛 伊 德 算 法 求 最 短 路 径
****************************
void Floyd(ALGraph *G)
{
int PathMatrix[MAX][MAX];
int DistanceMatrix[MAX][MAX];
int v, w, k;
ArcNode *arc;
int temp[MAX];
//初始化 floyd 算法的两个矩阵
for(v=0;v<G->vexnum;v++)
{
arc=G->adjList[v].firstArc;
for(w=G->vexnum-1;w>=0;w--)
{
if(arc!=NULL && arc->adjvex==w)
{
DistanceMatrix[v][w] = arc->weight;
arc=arc->nextarc;
}
else
{
DistanceMatrix[v][w] = 1000;//赋值不能超过最大值的一半
}
PathMatrix[v][w] = w;
}
}
//k 为中间点
for(k=0;k<G->vexnum;k++)
{
//v 为起点
for(v=0;v<G->vexnum;v++)
{
//w 为终点
for(w=0;w<G->vexnum;w++)
{
if(DistanceMatrix[v][w] > (DistanceMatrix[v][k] + DistanceMatrix[k][w])){
DistanceMatrix[v][w] = DistanceMatrix[v][k] + DistanceMatrix[k][w];//更
新最小路径
PathMatrix[v][w] = PathMatrix[v][k];//更新最小路径中间顶点
}
}
}
}
v = 0;
w = 5;
//求 0 到 3 的最小路径
printf("\nv%d->v%d 的最小路径为:%d\n", v, w, DistanceMatrix[v][w]); k = PathMatrix[v][w];
printf("path: v%d", v);//打印起点
while(k != w){
printf("->v%d", k);//打印中间点
k = PathMatrix[k][w];
}
printf("->v%d\n", w);
}
int TopologicalSort(ALGraph *G,int *topo)
{
int i, m, k,j;
int s[MAX],top;
ArcNode *arc;
int indegree[MAX];
for(i=0;i<G->vexnum;i++)
{
indegree[i]=0;
for(j=0;j<G->vexnum;j++)
{
arc=G->adjList[j].firstArc;
while(arc!=NULL)
{
if(arc->adjvex==i)
indegree[i]++;
arc=arc->nextarc;
}
}
}
top=0;
for (i = 0; i < G->vexnum; i++)
{
if(!indegree[i])
{
s[top]=i;
top++;
}
}
m = 0;
while(top>0)
{
top--;
i=s[top];
topo[m] = i;
++m;
arc=G->adjList[i].firstArc;
while (arc != NULL)
{
k = arc->adjvex; --indegree[k];
if (indegree[k] == 0)
{
s[top]=k;
top++;
}
arc = arc->nextarc;
}
}
topo[m] = -1;
if (m < G->vexnum)
{
printf("ERROR!\n");
return 0;
}
printf("\n 拓扑排序序列为:\n");
for (i = 0; topo[i] != -1; i++)
{
printf("v%d ", G->adjList[topo[i]].vex);
}
return 1;
}
//求各顶点的最晚发生时间并计算出各边的最早和最晚开始时间
int CriticalPath(ALGraph *G)
{
int i, j, k, e, l;
int *ve, *vl;
int topo[MAX];
ArcNode *p;
ve = (int *)malloc(sizeof(int)*G->vexnum);
vl = (int *)malloc(sizeof(int)*G->vexnum);
if (!TopologicalSort(G, topo))
return 0;
for (i = 0; i < G->vexnum; i++)
ve[i] = 0;
for (i = 0; i < G->vexnum; i++)
{
k = topo[i];
p = G->adjList[k].firstArc; while (p)
{
j = p->adjvex;
if (ve[j] < ve[k] + p->weight) ve[j] = ve[k] + p->weight;
p = p->nextarc;
}
}
for (i = 0; i < G->vexnum; i++)
vl[i] = ve[G->vexnum - 1];
for (i = G->vexnum - 1; i >= 0; i--)
{
k = topo[i];
p = G->adjList[k].firstArc; while (p)
{
j = p->adjvex;
if (vl[k]>vl[j] - p->weight) vl[k] = vl[j] - p->weight;
p = p->nextarc;
}
}
printf("\n");
for (i = 0; i < G->vexnum; i++)
{
p = G->adjList[i].firstArc; while (p)
{
j = p->adjvex; e = ve[i];
l = vl[j] - p->weight; if (e == l)
printf("v%d->v%d\n", G->adjList[i].vex, G->adjList[j].vex); p = p->nextarc;
}
}
return 1;
}
void main()
{
ALGraph G;
FILE *fp;
int opt;
int topo[MAX];
printf("请选择相应操作:\n1:深度优先\n2:广度优先\n3:普利姆算法最小生成树\n4:克鲁
斯卡尔算法最小生成树\n5:迪杰斯特拉最短路径\n6:弗洛伊德算法最短路径\n7:拓扑排序\n8:
关键路径\n9:显示\n0:结束\n");
scanf("%d",&opt);
while(opt)
{
switch(opt)
{
case 1:
fp=fopen("t1234.txt","r");
CreateAL(&G,fp);
//OutputAL(&G);
printf("深度优先序列:\n");
ALDepthFirstSearch(&G);
break;
case 2:
fp=fopen("t1234.txt","r");
CreateAL(&G,fp);
printf("深度优先序列:\n");
ALBreadthFirstSearch(&G);
break;
case 3:
fp=fopen("t1234.txt","r");
CreateAL(&G,fp);
MiniTree(&G);
break;
case 4:
fp=fopen("t1234.txt","r");
CreateAL(&G,fp);
kruskal(&G);
break;
case 5:
fp=fopen("MiniDis.txt","r");
CreateAL(&G,fp);
MiniDis(&G,0);
break;
case 6:
fp=fopen("MiniDis.txt","r");
CreateAL(&G,fp);
Floyd(&G);
break;
case 7:
fp=fopen("CriticalPath.txt","r");
CreateAL(&G,fp);
TopologicalSort(&G,topo);
break;
case 8:
fp=fopen("CriticalPath.txt","r");
CreateAL(&G,fp);
CriticalPath(&G);
break;
case 9:
OutputAL(&G);
break;
default:
printf("输出错误!");
}
printf("请选择相应操作:\n1:深度优先\n2:广度优先\n3:普利姆算法最小生成树\n4:
克鲁斯卡尔算法最小生成树\n5:迪杰斯特拉最短路径\n6:弗洛伊德算法最短路径\n7:拓扑排
序\n8:关键路径\n9:显示\n0:结束\n");
scanf("%d",&opt);
}
}
七、调试界面
八、总结
好好学习,天天向上。本章较难理解,宝子们好好练习。