求城市间最短路径 并可视化表示【C语言】

问题描述

要求:
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可视化他的二叉树结构,然后我就想是否可以迁移到图结构的表示上,经过查阅官方文档,终于找到了图结构可视化的解决方案,顺利的完成了这次实验。本次实验中我不仅仅完成了实验的基本内容,更多的体会到自己综合能力和信息整合能力的提高,如查阅相关资料代码,综合这些不同的方法,为自己的实验服务,同时这次实验的完成也提高我对编程方面的自信,总之受益匪浅。

猜你喜欢

转载自blog.csdn.net/qq_41563601/article/details/123662444
今日推荐