问题描述
要求:
1.城市网络图的邻接矩阵信息存储在文本文件mat.txt中
2.求城市D到其他城市的最短路径,及其最小代价
3.可视化表示最短路径
矩阵mat如下。99999表示不可达
0 12 99999 99999 99999 16 14
12 0 10 99999 99999 7 99999
99999 10 0 3 5 6 99999
99999 99999 3 0 4 99999 99999
99999 99999 5 4 0 2 8
16 7 6 99999 2 0 9
14 99999 99999 99999 8 9 0
代码
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
#define MAX 7 // 矩阵最大容量
#define INF (~(0x1<<31)) // 最大值(即0X7FFFFFFF)
#define isLetter(a) ((((a)>='a')&&((a)<='z')) || (((a)>='A')&&((a)<='Z')))
#define LENGTH(a) (sizeof(a)/sizeof(a[0]))
// 邻接矩阵
typedef struct _graph
{
char vexs[MAX]; // 顶点集合
int vexnum; // 顶点数
int edgnum; // 边数
int matrix[MAX][MAX]; // 邻接矩阵
}Graph, *PGraph;
// 边的结构体
typedef struct _EdgeData
{
char start; // 边的起点
char end; // 边的终点
int weight; // 边的权重
}EData;
/*
* 返回ch在matrix矩阵中的位置
*/
static int get_position(Graph G, char ch)
{
int i;
for(i=0; i<G.vexnum; i++)
if(G.vexs[i]==ch)
return i;
return -1;
}
/*
* 读取一个输入字符
*/
/*
* 创建图(用已提供的矩阵)
*/
Graph* create_example_graph()
{
char vexs[] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G'};
int matrix[MAX][MAX];//定义矩阵
int lines=0;//矩阵行数
FILE *fp;
int count;
fp = fopen("mat.txt", "r");//打开文件
if (fp==NULL ) {
//若打开文件失败则退出
puts("不能打开文件!");
}
while(lines < MAX)
{
for(count = 0; count < MAX; count ++)
if(fscanf(fp, "%d",&matrix[lines][count]) == EOF) break;//读取数据
if(feof(fp)) break;//判断是否文件结束。
lines++;//读取一行成功,增加行数。
}
fclose(fp);//关闭文件。
int vlen = LENGTH(vexs);
int i, j;
Graph* pG;
// 输入"顶点数"和"边数"
if ((pG=(Graph*)malloc(sizeof(Graph))) == NULL )
return NULL;
memset(pG, 0, sizeof(Graph));
// 初始化"顶点数"
pG->vexnum = vlen;
// 初始化"顶点"
for (i = 0; i < pG->vexnum; i++)
pG->vexs[i] = vexs[i];
// 初始化"边"
for (i = 0; i < pG->vexnum; i++)
for (j = 0; j < pG->vexnum; j++)
pG->matrix[i][j] = matrix[i][j];
// 统计边的数目
for (i = 0; i < pG->vexnum; i++)
for (j = 0; j < pG->vexnum; j++)
if (i!=j && pG->matrix[i][j]!=99999)
pG->edgnum++;
pG->edgnum /= 2;
return pG;
}
/*
* 返回顶点v的第一个邻接顶点的索引,失败则返回-1
*/
static int first_vertex(Graph G, int v)
{
int i;
if (v<0 || v>(G.vexnum-1))
return -1;
for (i = 0; i < G.vexnum; i++)
if (G.matrix[v][i]!=0 && G.matrix[v][i]!=INF)
return i;
return -1;
}
/*
* 返回顶点v相对于w的下一个邻接顶点的索引,失败则返回-1
*/
static int next_vertix(Graph G, int v, int w)
{
int i;
if (v<0 || v>(G.vexnum-1) || w<0 || w>(G.vexnum-1))
return -1;
for (i = w + 1; i < G.vexnum; i++)
if (G.matrix[v][i]!=0 && G.matrix[v][i]!=INF)
return i;
return -1;
}
/*
* 打印矩阵队列图
*/
void print_graph(Graph G)
{
int i,j;
printf("Martix Graph:\n");
for (i = 0; i < G.vexnum; i++)
{
for (j = 0; j < G.vexnum; j++)
printf("%10d ", G.matrix[i][j]);
printf("\n");
}
}
int mat2dot(Graph G,int start,int path[],int path_len,int loss){
char d[20];
sprintf(d,"%c到%c.dot",G.vexs[start],G.vexs[path[0]]);
FILE *fpWrite=fopen(d,"w");
if(fpWrite==NULL)
{
return 0;
}
fprintf(fpWrite,"#@startdo\ngraph demo {\nrankdir=RL\n");
fprintf(fpWrite,"label=\"%c to %c shortes loss is %d\" \n",G.vexs[start],G.vexs[path[0]],loss);
fprintf(fpWrite,"%c [color=red]\n",G.vexs[start]);
for (int i = 0; i < G.vexnum; i++)
{
for (int j = 0; j <i; j++)
{
if(G.matrix[i][j]>=9999 || G.matrix[i][j]==0){
continue;
}
int i_is_inpath=false;
int j_is_inpath=false;
int kss=0;
for(kss=0; kss<=path_len; kss++){
if(path[kss]==j){
j_is_inpath=true;
fprintf(fpWrite,"%c [color=red]\n",G.vexs[j]);
}
else if(path[kss]==i){
fprintf(fpWrite,"%c [color=red]\n",G.vexs[i]);
i_is_inpath=true;
}else
{
}
}
if(i_is_inpath && j_is_inpath){
fprintf(fpWrite," %c -- %c [label=%d,color=red, fontcolor=red]\n",G.vexs[i],G.vexs[j],G.matrix[i][j]);
}
else{
fprintf(fpWrite," %c -- %c [label=%d]\n",G.vexs[i],G.vexs[j],G.matrix[i][j]);
}
}
}
fprintf(fpWrite,"}\n#@enddot");
fclose(fpWrite);
char cmd[50];
sprintf(cmd,"dot %s -T png -o %c到%c.png",d,G.vexs[start],G.vexs[path[0]]);
system(cmd);
return 0;
}
/*
* Dijkstra最短路径。
* 即,统计图(G)中"顶点vs"到其它各个顶点的最短路径。
*
* 参数说明:
* G -- 图
* vs -- 起始顶点(start vertex)。即计算"顶点vs"到其它顶点的最短路径。
* prev -- 前驱顶点数组。即,prev[i]的值是"顶点vs"到"顶点i"的最短路径所经历的全部顶点中,位于"顶点i"之前的那个顶点。
* dist -- 长度数组。即,dist[i]是"顶点vs"到"顶点i"的最短路径的长度。
*/
void dijkstra(Graph G, int vs, int prev[], int dist[])
{
int i,j,k;
int min;
int tmp;
int flag[MAX]; // flag[i]=1表示"顶点vs"到"顶点i"的最短路径已成功获取。
// 初始化
for (i = 0; i < G.vexnum; i++)
{
flag[i] = 0; // 顶点i的最短路径还没获取到。
prev[i] = 0; // 顶点i的前驱顶点为0。
dist[i] = G.matrix[vs][i];// 顶点i的最短路径为"顶点vs"到"顶点i"的权。
}
// 对"顶点vs"自身进行初始化
flag[vs] = 1;
dist[vs] = 0;
// 遍历G.vexnum-1次;每次找出一个顶点的最短路径。
for (i = 1; i < G.vexnum; i++)
{
// 寻找当前最小的路径;
// 即,在未获取最短路径的顶点中,找到离vs最近的顶点(k)。
min = INF;
for (j = 0; j < G.vexnum; j++)
{
if (flag[j]==0 && dist[j]<min)
{
min = dist[j];
k = j;
}
}
// 标记"顶点k"为已经获取到最短路径
flag[k] = 1;
// 修正当前最短路径和前驱顶点
// 即,当已经"顶点k的最短路径"之后,更新"未获取最短路径的顶点的最短路径和前驱顶点"。
for (j = 0; j < G.vexnum; j++)
{
tmp = (G.matrix[k][j]==INF ? INF : (min + G.matrix[k][j])); // 防止溢出
if (flag[j] == 0 && (tmp < dist[j]) )
{
dist[j] = tmp;
prev[j] = k;
}
}
}
// 打印dijkstra最短路径的结果
printf("从城市%c 出发 \n", G.vexs[vs]);
for (i = 0; i < G.vexnum; i++){
if(i==vs)
continue;
printf("%c到%c的最小代价为: %d\n", G.vexs[vs], G.vexs[i], dist[i]);
// 建立一个栈转置用
char stack[MAX];
int path[MAX];
int top = 0 ;
stack[top++]=G.vexs[i];
path[top-1]=i;
int k =i ;
while(prev[k]!=0){
stack[top++]=G.vexs[prev[k]];
path[top-1]=prev[k];
k=prev[k];
}
stack[top]=G.vexs[vs];
path[top]=vs;
printf("路径为:");
mat2dot(G,vs, path,top,dist[i]);
while(top!=-1){
if(top ==0)
printf("%c",stack[top--]);
else
printf("%c->",stack[top--]);
}
printf("\n");
}
}
void main()
{
int prev[MAX] = {
0};
int dist[MAX] = {
0};
Graph* pG;
// 自定义"图"(输入矩阵队列)
//pG = create_graph();
// 采用已有的"图"
pG = create_example_graph();
//print_graph(*pG);
dijkstra(*pG, 3, prev, dist);
/**
*
* 部分引用 @author skywang
* @date 2014/04/24
*/
}
运行效果
需要安装软件graphviz用于可视化。
主要遇到的一些问题
1.如何读取文本文件?
使用fopen函数读取一个文本文件,然后使用fscanf函数 将读取到的文件,存储到matrix矩阵中,这样就完成了文本文件的读取,详细说明 见注释。
int matrix[MAX][MAX];//定义矩阵
int lines=0;//矩阵行数
FILE *fp;
int count;
fp = fopen("mat.txt", "r");//打开文件
if (fp==NULL ) {
//若打开文件失败则退出
puts("不能打开文件!");
}
while(lines < MAX)
{
for(count = 0; count < MAX; count ++)
if(fscanf(fp, "%d",&matrix[lines][count]) == EOF) break;//读取数据
if(feof(fp)) break;//判断是否文件结束。
lines++;//读取一行成功,增加行数。
}
fclose(fp);//关闭文件。
2.如何可视化图结构?
这里用到了一个软件叫做graphviz,也可以作为插件使用。这个是使用dot语言进行编程的。将原有的图结构,转化为相对应的dot代码,然后执行这个代码就可以生成对应的可视化界面了
比如复制下面的代码,测试一下
#@startdot
graph demo {
a--b
a--c
}
#@enddot
点击这里测试
就会生成这样的结果,更详细的dot语言教学看这里
3.如何拼接字符串并写进文件?
最熟悉的是printf函数进行控制台的输出,将输出写入文件,这就需要和文件相关的函数fprint,将字符串输入到文件流中。首先打开文件指针,然后进行写入操作,然后关闭文件指针。
FILE *fpWrite=fopen("a.txt","w");
if(fpWrite==NULL)
return 0;
fprintf(fpWrite,"hello world");
fclose(fpWrite)
4.如何动态的生成文件名,实现不同的最短路径输入到不同的dot文件中?
因为想将不同的最短路径写入不同的文件中,所以文件名应该根据内容不断变化,而不是只有“data.txt”。那就需要定义一个字符数组用于存放文件名。然后使用sprintf进行写入操作,代码如下
char filename[20];
sprintf(filename,"%c到%c.dot",G.vexs[start],G.vexs[path[0]]);
FILE *fpWrite=fopen(filename,"w");
心得
通过本次实验,更加了解了迪杰斯特拉最短路径算法的现实应用,使得自己学到的数据结构知识不仅仅停留在书本上。其中,令我印象深刻的是图结构可视化,因为之前没有接触过相关知识,所以卡住了很久,终于看到一篇博客的博主,使用graphviz可视化他的二叉树结构,然后我就想是否可以迁移到图结构的表示上,经过查阅官方文档,终于找到了图结构可视化的解决方案,顺利的完成了这次实验。本次实验中我不仅仅完成了实验的基本内容,更多的体会到自己综合能力和信息整合能力的提高,如查阅相关资料代码,综合这些不同的方法,为自己的实验服务,同时这次实验的完成也提高我对编程方面的自信,总之受益匪浅。