一、实验目的及要求
1.熟悉各种图的存储结构(邻接矩阵和邻接表)。
2.掌握图的深度优先和广度优先遍历算法。
3.掌握克鲁斯卡尔算法生成最小生成树的方法。
4.掌握狄克斯特拉算法计算最短路径和最短路径长度的方法。
二、实验内容(或实验原理、实验拓扑)
1.采用克鲁斯卡尔(Kruskal)算法,输出下面无向带权图G的最小生成树。
三、实验设计方案(包括实验步骤、设计思想、算法描述或开发流程等)
(一)在graph.h中创建图的两种存储结构:
1.定义邻接矩阵的定点类型VertexType;
2.定义完整的图邻接矩阵类型MatGraph。
3.定义邻接表类型的边结点类型ArcNode;
4.定义邻接表类型的头结点类型VNode;
5.定义完整的图邻接表类型AdjGraph。
(二)在graph.cpp中完成邻接矩阵和邻接表的基本运算算法:
1. 创建图的邻接矩阵CreateMat(MatGraph &g,int A[MAXV][MAXV],int n,int e);
2. 输出邻接矩阵g DispMat(MatGraph g);
3. 创建图的邻接表CreateAdj(AdjGraph *&G,int A[MAXV][MAXV],int n,int e);
4. 输出邻接表G DispAdj(AdjGraph *G);
5. 销毁图的邻接表DestroyAdj(AdjGraph *&G)。
(三)在Kruskal.cpp中完成图的基本运算算法:
1. 对E[0…n-1]按递增有序进行直接插入排序InsertSort(Edge E[],int n);
2. 完成克鲁斯卡尔算法Kruskal(MatGraph g);
3.主函数 main()根据问题依次调用基本操作函数并编写通俗易懂的语句输出。
四、实验结果(包括设计效果、测试数据、运行结果等)
运行结果如下:
五、实验小结(包括收获、心得体会、注意事项、存在问题及解决办法、建议等)
克鲁斯卡尔算法的时间复杂度为O(elog2e),适合于求边稀疏的网的最小生成树。克鲁斯卡尔算法过程如下,首先考虑问题的出发点,为使生成树上边的权值之和达到最小,则应使生成树中每一条边的权值尽可能地小。算法思想是将图中所有边按权值递增顺序排列,依次选定取权值较小的边,但要求后面选取的边不能与前面选取的边构成回路,若构成回路,则放弃该条边,再去选后面权值较大的边,n个顶点的图中,选够n-1条边即可。
六、附录(包括作品、流程图、源程序及命令清单等)
graph.h:
//图的两种存储结构
#define INF 32767 //定义∞
#define MAXV 100 //最大顶点个数
typedef char InfoType;
//以下定义邻接矩阵类型
typedef struct
{ int no; //顶点编号
InfoType info; //顶点其他信息
} VertexType; //顶点类型
typedef struct
{ int edges[MAXV][MAXV]; //邻接矩阵数组
int n,e; //顶点数,边数
VertexType vexs[MAXV]; //存放顶点信息
} MatGraph; //完整的图邻接矩阵类型
//以下定义邻接表类型
typedef struct ANode
{ int adjvex; //该边的邻接点编号
struct ANode *nextarc; //指向下一条边的指针
int weight; //该边的相关信息,如权值(用整型表示)
} ArcNode; //边结点类型
typedef struct Vnode
{ InfoType info; //顶点其他信息
int count; //存放顶点入度,仅仅用于拓扑排序
ArcNode *firstarc; //指向第一条边
} VNode; //邻接表头结点类型
typedef struct
{ VNode adjlist[MAXV]; //邻接表头结点数组
int n,e; //图中顶点数n和边数e
} AdjGraph; //完整的图邻接表类型
graph.cpp:
//图的基本运算算法
#include <stdio.h>
#include <malloc.h>
#include "graph.h"
//邻接矩阵的基本运算算法
void CreateMat(MatGraph &g,int A[MAXV][MAXV],int n,int e) //创建图的邻接矩阵
{
int i,j;
g.n=n; g.e=e;
for (i=0;i<g.n;i++)
for (j=0;j<g.n;j++)
g.edges[i][j]=A[i][j];
}
void DispMat(MatGraph g) //输出邻接矩阵g
{
int i,j;
for (i=0;i<g.n;i++)
{
for (j=0;j<g.n;j++)
if (g.edges[i][j]!=INF)
printf("%4d",g.edges[i][j]);
else
printf("%4s","∞");
printf("\n");
}
}
//邻接表的基本运算算法
void CreateAdj(AdjGraph *&G,int A[MAXV][MAXV],int n,int e) //创建图的邻接表
{
int i,j;
ArcNode *p;
G=(AdjGraph *)malloc(sizeof(AdjGraph));
for (i=0;i<n;i++) //给邻接表中所有头结点的指针域置初值
G->adjlist[i].firstarc=NULL;
for (i=0;i<n;i++) //检查邻接矩阵中每个元素
for (j=n-1;j>=0;j--)
if (A[i][j]!=0 && A[i][j]!=INF) //存在一条边
{ p=(ArcNode *)malloc(sizeof(ArcNode)); //创建一个结点p
p->adjvex=j;
p->weight=A[i][j];
p->nextarc=G->adjlist[i].firstarc; //采用头插法插入结点p
G->adjlist[i].firstarc=p;
}
G->n=n; G->e=n;
}
void DispAdj(AdjGraph *G) //输出邻接表G
{
int i;
ArcNode *p;
for (i=0;i<G->n;i++)
{
p=G->adjlist[i].firstarc;
printf("%3d: ",i);
while (p!=NULL)
{
printf("%3d[%d]→",p->adjvex,p->weight);
p=p->nextarc;
}
printf("∧\n");
}
}
void DestroyAdj(AdjGraph *&G) //销毁图的邻接表
{ int i;
ArcNode *pre,*p;
for (i=0;i<G->n;i++) //扫描所有的单链表
{ pre=G->adjlist[i].firstarc; //p指向第i个单链表的首结点
if (pre!=NULL)
{ p=pre->nextarc;
while (p!=NULL) //释放第i个单链表的所有边结点
{ free(pre);
pre=p; p=p->nextarc;
}
free(pre);
}
}
free(G); //释放头结点数组
}
Kruskal.cpp:
//Kruskal算法
#include "graph.cpp"
#define MaxSize 100
typedef struct
{
int u; //边的起始顶点
int v; //边的终止顶点
int w; //边的权值
}Edge;
void InsertSort(Edge E[],int n) //对E[0..n-1]按递增有序进行直接插入排序
{
int i,j;
Edge temp;
for (i=1;i<n;i++)
{
temp=E[i];
j=i-1; //从右向左在有序区E[0..i-1]中找E[i]的插入位置
while (j>=0 && temp.w<E[j].w)
{
E[j+1]=E[j]; //将关键字大于E[i].w的记录后移
j--;
}
E[j+1]=temp; //在j+1处插入E[i]
}
}
void Kruskal(MatGraph g) //Kruskal算法
{
int i,j,u1,v1,sn1,sn2,k;
int vset[MAXV]; //记录每个顶点所在的连通分量编号
Edge E[MaxSize]; //存放所有边
k=0; //E数组的下标从0开始计
for (i=0;i<g.n;i++) //由g产生的边集E
for (j=0;j<=i;j++)
{
if (g.edges[i][j]!=0 && g.edges[i][j]!=INF)
{
E[k].u=i;E[k].v=j;E[k].w=g.edges[i][j];
k++;
}
}
InsertSort(E,g.e); //采用直接插入排序对E数组按权值递增排序
for (i=0;i<g.n;i++) //初始化辅助数组
vset[i]=i;
k=1; //k表示当前构造生成树的第几条边,初值为1
j=0; //E中边的下标,初值为0
while (k<g.n) //生成的边数小于n时循环
{
u1=E[j].u;v1=E[j].v; //取一条边的头尾顶点
sn1=vset[u1];
sn2=vset[v1]; //分别得到两个顶点所属的集合编号
if (sn1!=sn2) //两顶点属于不同的集合,该边是最小生成树的一条边
{
printf(" (%d,%d):%d\n",u1,v1,E[j].w);
k++; //生成边数增1
for (i=0;i<g.n;i++) //两个集合统一编号
if (vset[i]==sn2) //集合编号为sn2的改为sn1
vset[i]=sn1;
}
j++; //扫描下一条边
}
}
int main()
{
MatGraph g;
int A[MAXV][MAXV]={
{0,5,8,7,INF,3},
{5,0,4,INF,INF,INF},
{8,4,0,5,INF,9},
{7,INF,5,0,5,6},
{INF,INF,INF,5,0,1},
{3,INF,9,6,1,0}};
int n=6, e=10;
CreateMat(g,A,n,e); //建立邻接矩阵
printf("图G的邻接矩阵:\n");
DispMat(g); //输出邻接矩阵
printf("Kruskal算法结果\n");
Kruskal(g);
return 1;
}