数据结构(C语言)— 图的相关实验
实验目的
1、熟练掌握图的邻接矩阵与邻接表存储结构及其应用。
2、能设计出基于两种遍历算法的相关问题的求解,如深度遍历生成树的求解,广度遍历生成树的求解。
3、理解并掌握最小生成树算法的基本思想及其算法方法。
4、理解并掌握最短路径算法的基本思想及其算法方法。
5、理解并掌握拓扑排序算法的基本思想及其算法方法。
实验内容
1、编写程序输出以邻接表为存储结构的无向图的各顶点的度。
2、图采用邻接表存储结构,编程对图进行广度优先遍历。
3、图采用邻接表存储结构,编程对图进行深度优先遍历。
4、无向图采用邻接矩阵存储结构,编程实现Prim求解最小生成树算法。
5、无向图采用邻接矩阵存储结构,编程实现Dijkstra求单源最短路径算法。
6、编程实现图的拓扑排序算法。
算法描述及实验步骤
1编写程序输出以邻接表为存储结构的无向图的各顶点的度。
在编写这道题的过程中,首先需要创建ljb.h,在表中编写create函数去创建一个邻接表。通过书本上的描述,需要定义一个邻接表的类型,里面需要包括存放头节点的顺序表,并记录图的顶点数和边数。再定义头节点的信息是顶点信息以及一个指向边表结点的指针。再在边表中定义存放边表的节点还有next邻接点。
再编写创建的函数。首先为了避免每次输入的麻烦,我们可以创建一个txt文件去存放我们的信息。再通过create函数去打开这个文件并fscanf去进行文件的读取。首先读入边数和顶点数,然后再每次根据究竟是有向图1还是无向图0来分别读取数据并将数据填入我们所创建的邻接表中。最后再关闭文件。
对于输出每个头节点的度,我们只需要遍历它存放头节点的顺序表当中的每一个头节点,然后在各自头节点的指针指向下去通过count记录下它边链表有多少个这样的edgenode就可以得到每个头节点的度了。
2图采用邻接表存储结构,编程对图进行广度优先遍历。
广度优先遍历我们需要运用队列的思想去先进先出的输出各个图中的节点。首先我们需要考虑连通分量的问题,假如一个图中有多个连通分量,我们则需要分开去考虑。所以我们需要编写一个bfstraverse去对每一个结点先进行遍历,再将visitied数组中标记为0的去进行遍历,最后遍历完一遍后如果还有节点标记为0则为非连通分量,就再次进行遍历。
对于已知顶点i如何去进行广度优先的遍历呢。我们运用了队列的特点。首先输出顶点i的值,然后再根据i的边链表去将其一个一个挂载的节点入队列,然后每次从前往后出队列一个节点,再去遍历这个节点的边链表,再将其节点入队列。另外,如果输出了一个节点,则需要将他的visited对应标记设置为1,表示为这个连通分量中已经遍历过。
3图采用邻接表存储结构,编程对图进行深度优先遍历。
深度优先遍历我们是运用了递归的思想。当然我们首先也需要考虑连通分量的问题,假如一个图中有多个连通分量,我们需要分开考虑。所以我们需要编写一个dfstraverse去对每一个节点去进行遍历,再将visited数组中标记为0的去进行深度遍历,最后遍历完一遍如果还有节点标记为0则说明是非连通分量,就再此对这个节点进行深度遍历。
已知顶点的深度遍历需要首先访问顶点,然后将顶点的visted设置为1,在通过头节点的指向边链表的指针去设置一个p指针。如果这个p指针指向的边链表的节点已经被访问过则p=p->next 否则递归去输出这个节点作为头节点时他的边链表中的节点。这样运用递归的思想,就可以深度遍历了。
4无向图采用邻接矩阵存储结构,编程实现Prim求解最小生成树算法。
这一题不再是邻接表而是邻接矩阵,所以我们需要引入ljjz.h然后创建create函数。邻接矩阵主要是运用了二维数组的思想,去将有关系的两节点的矩阵位置设为权值。
最小生成树的思想,我们需要去创建一个tree的结构体,去存放路径的两栖边序号。以及它的边长。
首先我们得初始化入选点,并初始化生成树边集tree。从顶点0开始,将与0有关的结点长度设为权值。然后依次求出当前第k条最小两栖边,并加入tree中,如果找到了之后最小的权值,则将tree [v]换到前面,并由于新顶点v的加入,修改两栖边的基本信息。再对交换过位置之后的tree再次进行最小权值的比较。最后输出。
5无向图采用邻接矩阵存储结构,编程实现Dijkstra求单源最短路径算法。
首先还是需要引入ljjz.h,因为其是采用邻接矩阵存储结构。为了求单源最短路径,与最小生成树不同的是,最小生成树比较的是边,而最短路径比较的是从顶点开始的路径长短,为了记录每一步走向的上一个节点以及他们的长度,我们设计了两个数组去存放目前比较的数据以及这个节点上一个节点。
首先我们需要对这些数组进行初始化。将对于自身的比较的p表示的上一个节点的索引存储为-1,他们之间的路径长度设置为0.并首先让final[v0]设置为true。依次找出n-1个节点加入到s中,循环找到路径最小的那个节点,把k索引记录下来,然后去输出,把对应的final数组标记为true,然后再将这个节点放到v中,再去修改与各节点的距离,把修改后的值放入d数组中。
最后用队列的思想,因为最后的记录的dist是倒叙往回查看上一个节点的。如果要正输出有向图的最短路径则需要运用队列的思想去先进先出输出。
6编程实现图的拓扑排序算法。
拓扑排序也需要用到链表的思想,但是我们需要对lib.h进行一些改编。同样我们需要定义边节点,邻接表结构,还有带顶点入度的头节点。但是在带顶点的入度的头节点中,我们需要新增一个id去记录目前他除去上一个节点的入度以后所剩的入度个数,当入度个数为0时去进行入队列。
首先我们需要将所有的flag设置为0,因为目前没有一个节点输出。我们再去做一个循环,去判断是否目前有节点的id为0 且这个节点没有被输出过则这个节点需要入队列。当队列不为空时,则先让第一次id为0的节点输出,出队列。然后将与这个id为0的firstedge边链表所链接的节点的各自id都减1,如果边链表中有id自减之后便为了0,则入队列。标记这个的已经入队列的结点flag为1,为了方便之后不再重复判断。
调试过程及运行结果
1、编写程序输出以邻接表为存储结构的无向图的各顶点的度。
#include <stdio.h>
#include <stdlib.h>
#include "ljb.h"
void degree(LinkedGraph g)
{
int i,count=0;
EdgeNode *p;
for(i=0;i<g.n;i++)
{
p=g.adjlist[i].FirstEdge;
printf("%c为头节点的度数为:",g.adjlist[i].vertex);
while(p)
{
count++;
p=p->next;
}
printf("%d\n",count);
count=0;
}
}
int main()
{
LinkedGraph g;
create(&g,"wutest.txt",0);
print(&g);
degree(g);
return 0;
}
2、图采用邻接表存储结构,编程对图进行广度优先遍历。
#include <stdio.h>
#include <stdlib.h>
#include "ljb.h"
int visited[M];
/*从顶点i开始广度优先遍历图的联通分量*/
void bfs(LinkedGraph *g,int i)
{
EdgeNode *p;
int j;
int queue[M],front,rear;
front=rear=0;
printf("%c ",g->adjlist[i].vertex);//访问顶点i
visited[i]=1;
queue[rear++]=i;
while(rear>front)
{
j=queue[front++];
p=g->adjlist[i].FirstEdge;
while(p)
{
if(visited[p->adjvex]==0)
{
printf("%c ",g->adjlist[p->adjvex].vertex);
queue[rear++]=p->adjvex;
visited[p->adjvex]=1;
}
p=p->next;
}
}
}
/*广度优先遍历图,返回联通分量个数*/
int BfsTraverse(LinkedGraph g)
{
int i,count=0;
for(i=0;i<g.n;i++)
{
visited[i]=0;
}
for(i=0;i<g.n;i++)
{
if(!visited[i])
{
printf("\n");
count++;
bfs(&g,i);
}
}
return count;
}
int main()
{
LinkedGraph g;
int count;
create(&g,"wutest.txt",0);
print(&g);
printf("广度优先遍历序列为:");
count=BfsTraverse(g);
printf("\n该图共有%d个联通分量。\n",count);
return 0;
}
3、图采用邻接表存储结构,编程对图进行深度优先遍历。
#include <stdio.h>
#include <stdlib.h>
#include "ljb.h"
int visited[M];
/*从顶点i开始深度优先遍历图的联通分量*/
void dfs(LinkedGraph *g,int i)
{
EdgeNode *p;
printf("visit vertex:%c \n",g->adjlist[i].vertex);//访问顶点i
visited[i]=1;
p=g->adjlist[i].FirstEdge;
while(p)
{
if(!visited[p->adjvex]) dfs(g,p->adjvex);
p=p->next;
}
}
/*深度优先遍历图*/
void DfsTraverse(LinkedGraph *g)
{
int i;
for(i=0;i<g->n;i++)
{
visited[i]=0;
}
for(i=0;i<g->n;i++)
{
if(!visited[i]) dfs(g,i);
}
}
int main()
{
LinkedGraph *g;
create(&g,"wutest.txt",0);
print(&g);
printf("深度优先遍历序列为:");
DfsTraverse(&g);
return 0;
}
4、无向图采用邻接矩阵存储结构,编程实现Prim求解最小生成树算法。
#include "ljjz.h"
typedef struct edgedata/*用于保存最小生成树的边类型定义*/
{
int beg,en; /*beg,en是边顶点序号*/
int length;/*边长*/
}edge;
void prim(Mgraph g,edge tree[M-1])
{
edge x;
int d,min,j,k,s,v;
for(v=1;v<=g.n-1;v++)
{
tree[v-1].beg=0;
tree[v-1].en=v;
tree[v-1].length=g.edges[0][v];
}
for(k=0;k<g.n-3;k++)
{
min=tree[k].length;
s=k;
for(j=k+1;j<g.n-1;j++)
{
if(tree[j].length<min)
{
min=tree[j].length;
s=j;
}
}
v=tree[s].en;
x=tree[s];tree[s]=tree[k];tree[k]=x;
for(j=k+1;j<g.n-2;j++)
{
d=g.edges[v][tree[j].en];
if(d<tree[j].length)
{
tree[j].length=d;
tree[j].beg=v;
}
}
}
printf("\n The minimumcost spanning tree is:\n");
for(j=0;j<g.n-2;j++)
{
printf("\n%c---%c %d\n",g.vexs[tree[j].beg],g.vexs[tree[j].en],tree[j].length);
}
printf("\nThe root of it is %c\n",g.vexs[0]);
}
int main()
{
Mgraph g;
edge tree[M-1];
create(&g,"prim.txt",0);
prim(g,tree);
return 0;
}
5、无向图采用邻接矩阵存储结构,编程实现Dijkstra求单源最短路径算法。
#include "ljjz_short.h"
#include <stdio.h>
#include <stdlib.h>
typedef enum{FALSE,TRUE} boolean;/*false为0,true为1*/
typedef int dist[M]; /* 距离向量类型*/
typedef int path[M]; /* 路径类型*/
void dijkstra(Mgraph g,int v0,path p,dist d)
{
boolean final[M];
int i,k,j,v,min,x;
int dxian;
for(v=0;v<g.n;v++)
{
final[v]=FALSE;
d[v]=g.edges[v0][v];
dxian=g.edges[v0][v];
if(d[v]<FINITY && d[v]!=0) p[v]=v0;
else p[v]=-1;
}
final[v0]=TRUE;d[v0]=0;
/* 第2步 依次找出n-1个结点加入S中 */
for (i=1;i<g.n;i++)
{
min=FINITY;
for(k=0;k<g.n;++k)
{
if(!final[k] && d[k]<min)
{
v=k;
min=d[k];
}
}
if (min==FINITY) return ;
final[v]=TRUE;
/*第3步 修改S与V-S中各结点的距离*/
for (k=0;k<g.n;++k)
if (!final[k] && (min+g.edges[v][k]<d[k]))
{
d[k]=min+g.edges[v][k];
p[k]=v;
}
}
}
/*函数功能:输出有向图的最短路径
函数参数:邻接矩阵g;路径向量p;距离向量d
*/
void print_gpd(Mgraph g,path p,dist d)
{
int st[M],i,pre,top=-1;
for (i=0;i<g.n;i++)
{ printf("\nDistancd: %7d , path:" ,d[i]);
st[++top]=i;
pre=p[i];
while (pre!=-1) /*从第i个顶点开始向前搜索最短路径上的顶点*/
{ st[++top]=pre;
pre=p[pre];
}
while (top>0)
printf("%2d",st[top--]);
}
}
int main()
{ Mgraph g; /* 有向图 */
path p; /* 路径向量 */
dist d; /* 最短路径向量 */
int v0;
creat(&g,"T5.txt",1); /*假设图8.21所示的有向网G21的输入文件为g21.txt */
print(g); /*输出图的邻接矩阵*/
printf("\n");
printf("请输入源点编号:");
scanf("%d",&v0); /*输入源点*/
dijkstra(g,v0,p,d); /*求v0到其他各点的最短距离*/
/*输出V0到其它各点的路径信息及距离*/
print_gpd(g,p,d);
return 0;
}
6、编程实现图的拓扑排序算法。
#define M 20
#include <stdio.h>
#include <stdlib.h>
#include "tuopu.h"
int TopSort(AovGraph g)
{
int k=0,i,j,v,flag[M];
int queue[M];
int front,rear;
EdgeNode* p;
front = rear=0;
for(i=0;i<g.n;i++) flag[i]=0;
for(i=0;i<g.n;i++)
if(g.adjlist[i].id==0 && flag[i]==0)
{
printf("初始id为0的为%d",i);
queue[rear++]=i;flag[i]=1;
}
printf("\nAOV网的拓扑排序是:\n");
while(front<rear)
{
v=queue[front++];
printf("%c",g.adjlist[v].vertex);
k++;
p=g.adjlist[v].FirstEdge;
while(p)
{
j=p->adjvex;
if(--g.adjlist[j].id==0 && flag[j]==0)
{
queue[rear++]=j;
flag[j]=1;
}
p=p->next;
}
}
return k;
}
int main()
{ AovGraph g; /* 有向图 */
create(&g,"tuopu.txt",1); /*假设图8.21所示的有向网G21的输入文件为g21.txt */
print(&g); /*输出图的邻接矩阵*/
printf("\n");
TopSort(g);
return 0;
}
一些头文件
1、邻接表
#include <stdio.h>
#include <stdlib.h>
#define M 20
typedef char datatype;
typedef struct node{
int adjvex;
struct node *next;
}EdgeNode;
typedef struct vnode{
datatype vertex;
EdgeNode *FirstEdge;
}VertexNode;
typedef struct{
VertexNode adjlist[M];
int n,e;
}LinkedGraph;
/*建立图的单链表*/
void create(LinkedGraph *g,char *filename,int c)
{
int i,j,k;
EdgeNode *s;
FILE *fp;
fp=fopen(filename,"r");
if(fp)
{
fscanf(fp,"%d%d",&g->n,&g->e);
for(i=0;i<g->n;i++)
{
fscanf(fp,"%1s",&g->adjlist[i].vertex);
g->adjlist[i].FirstEdge=NULL;
}
for(k=0;k<g->e;k++)
{
fscanf(fp,"%d%d",&i,&j);
s=(EdgeNode *)malloc(sizeof(EdgeNode));
s->adjvex=j;
s->next=g->adjlist[i].FirstEdge;
g->adjlist[i].FirstEdge=s;
if(c==0)
{
s=(EdgeNode *)malloc(sizeof(EdgeNode));
s->adjvex=i;
s->next=g->adjlist[j].FirstEdge;
g->adjlist[j].FirstEdge=s;
}
}
fclose(fp);
}
else g->n=0;
}
/*---函数print():输出邻接表存储结构---*/
void print(LinkedGraph *g)
{ EdgeNode *p;
int i;
for (i=0;i<g->n;i++)
{ printf("%c",g->adjlist[i].vertex);
p=g->adjlist[i].FirstEdge;
while (p)
{ printf("-->%d",p->adjvex);
p=p->next;
}
printf("\n");
}
}
2、邻接矩阵
#include <stdio.h>
#define FINITY 5000
#define M 20
/*邻接矩阵定义*/
typedef char vertextype;
typedef int edgetype;
typedef struct{
vertextype vexs[M];
edgetype edges[M][M];
int n,e;
}Mgraph;
/*建立图的邻接矩阵存储结构*/
void create(Mgraph *g,char *s,int c)
{
int i,j,k,w;
FILE *rf;
rf= fopen(s,"r");//打开文件
if(rf)
{
fscanf(rf,"%d%d",&g->n,&g->e);//读入头结点个数和边数
for(i=0;i<g->n;i++)//扫描加入所有头节点
{fscanf(rf," %c",&g->vexs[i]);
printf("%c",g->vexs[i]);}
for(i=0;i<g->n;i++)//赋初值
for(j=0;j<g->n;j++)
if(i==j) g->edges[i][j]=0;
else g->edges[i][j]=FINITY;//为啥不全等与0??
for(k=0;k<g->e;k++)
{
fscanf(rf,"%d%d%d",&i,&j,&w);
g->edges[i][j]=w;
if(c==0) g->edges[j][i]=w;
}
fclose(rf);
}
else
{
printf("文件打开失败");
g->n=0;
}
}
3、拓扑
#include <stdio.h>
#include <stdlib.h>
#define M 20
typedef char vertextype;
typedef struct node{
int adjvex;
struct node *next;
}EdgeNode;
typedef struct de{
EdgeNode* FirstEdge;
vertextype vertex;
int id;
}vertexnode;
typedef struct{
vertexnode adjlist[M];
int n,e;
}AovGraph;
/*建立图的单链表*/
void create(AovGraph *g,char *filename,int c)
{
int i,j,k,n,e;
int id;
EdgeNode *s;
FILE *fp;
fp=fopen(filename,"r");
if(fp)
{
fscanf(fp,"%d%d",&g->n,&g->e);
n=g->n;e=g->e;
for(i=0;i<g->n;i++)
{
fscanf(fp,"%1s",&g->adjlist[i].vertex);
g->adjlist[i].id=0;
g->adjlist[i].FirstEdge=NULL;
}
for(k=0;k<g->e;k++)
{
fscanf(fp,"%d%d",&i,&j);
id=g->adjlist[j].id;
g->adjlist[j].id++;
id=g->adjlist[j].id;
s=(EdgeNode *)malloc(sizeof(EdgeNode));
s->adjvex=j;
s->next=g->adjlist[i].FirstEdge;
g->adjlist[i].FirstEdge=s;
if(c==0)
{
s=(EdgeNode *)malloc(sizeof(EdgeNode));
s->adjvex=i;
s->next=g->adjlist[j].FirstEdge;
g->adjlist[j].FirstEdge=s;
}
}
fclose(fp);
}
else g->n=0;
}
/*---函数print():输出邻接表存储结构---*/
void print(AovGraph *g)
{ EdgeNode *p;
int i;
for (i=0;i<g->n;i++)
{ printf("%c",g->adjlist[i].vertex);
p=g->adjlist[i].FirstEdge;
while (p)
{ printf("-->%d",p->adjvex);
p=p->next;
}
printf("%4d",g->adjlist[i].id);
printf("\n");
}
}
总结
在整个实验中,我深刻理解了图的邻接矩阵以及邻接表创建图的方法,还有深度和广度遍历图,以及通过prim算法去输出最小生成树,还有Dijkstra求单源最短路径的算法。通过这些算法,我理解了在地图的求取时间最短以及路程最短的方案中,是如何利用权值找到最短路径的。虽然对于图的算法还不能完全自己写,但是有了更深入的了解。收益颇丰。