1.9.课设实验-数据结构-图-校园跑最短路径

题目:

使用C语言/C++设计校园跑最短路径。

参考代码:

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<limits.h>

#define MAX_VERTICES 10
#define INF INT_MAX 
/*INT_MAX:C语言标准库中定义的常量,表示int类型的最大值,通常为2,147,483,647,
此时表示顶点之间无法直达*/
/*#define INF (INT_MAX / 2)更加安全,因为可以防止累加时溢出;
最保守就是#define INF 1000000,使用一个足够大的数代替INT_MAX,避免溢出*/

//图的顶点的结构体 
typedef struct 
{
	//1.顶点名称(地点名)
    char* name; 
    //2.顶点编号
    int id;    
} Vertex;

//图的结构体定义
typedef struct 
{
	//1.顶点数量
    int vertexCount;  
	//2.顶点数组->存储顶点                  
    Vertex vertices[MAX_VERTICES];
	//3.邻接矩阵存储边权重(本实验采取邻接矩阵存储图->邻接矩阵表示图的具体内容)      
    int adjacencyMatrix[MAX_VERTICES][MAX_VERTICES]; 
    //4.Floyd算法距离矩阵
    /*比如dist[3][5] = 150意味着从3号点位到5号点位目前找到的最短路径是150米*/
    int dist[MAX_VERTICES][MAX_VERTICES];
	//5.Floyd算法路径矩阵 
	/*假设path[1][7] = 4,说明从1号点去7号点时,建议先到4号点中转*/
    int path[MAX_VERTICES][MAX_VERTICES]; 
}Graph;

/*------------------------------------------赵旭文------------------------------------------*/
//初始化图的函数 
void initGraph(Graph* graph) {
	//1.初始时图中没有任何顶点,因此顶点个数为0 
    graph->vertexCount = 0;
    //2.本例采用邻接矩阵存储图
	/*对角线初始化为0,其余为无穷大,因为对角线表示自己到自己*/ 
    for (int i = 0; i < MAX_VERTICES; i++) { //外层循环是行 
        for (int j = 0; j < MAX_VERTICES; j++) { //内层循环是列 
            //2.1.初始化边的权值,自己到自己为0,其余为无穷即不存在(i==j时结果为true,就为0) 
            graph->adjacencyMatrix[i][j] = (i == j) ? 0 : INF;
            //2.2.初始化距离,初始时对角顶点的距离为0 
            graph->dist[i][j] = (i == j) ? 0 : INF;
            //2.3.初始中转点,初始时路径矩阵初始化为-1,代表没有任何中转点 
            graph->path[i][j] = -1;  
        }
    }
}

//图中插入顶点的函数
/*形参需要目标图,插入顶点的名称以及插入顶点的编号*/ 
int insertVertex(Graph* graph,const char* name, int id) {
	//1.判断图是否已满,若满了就无法继续添加 
    if (graph->vertexCount >= MAX_VERTICES) 
	{
        printf("图已满,无法插入新顶点。\n");
        return -1;
    }
    //2.此时可以插入新顶点,需要判断顶点编号是否越界 
    if (id < 0 || id >= MAX_VERTICES) 
	{
        printf("顶点ID超出范围。\n");
        return -1;
    }
    //3.继续检查顶点id是否已存在
    for (int i = 0; i < graph->vertexCount; i++) 
	{
        if (graph->vertices[i].id == id) 
		{
            printf("顶点ID %d 已存在。\n", id);
            return -1;
        }
    }
    //4.此时要插入的顶点合法 
    /*graph->vertices[graph->vertexCount].name = (char*)name;
	->如果需要可以强制转换为变量,const为常量 */
	/*分配内存并复制字符串,
	对于malloc函数:内存分配失败时返回空指针NULL,成功时返回分配内存的起始地址(void*类型指针),
	因此为了保证安全性,调用malloc函数后要强制转换成所需类型*/ 
    graph->vertices[graph->vertexCount].name = (char*)malloc(strlen(name) + 1);
    if (graph->vertices[graph->vertexCount].name == NULL) 
	{
        printf("内存分配失败。\n");
        return -1;
    }
    strcpy(graph->vertices[graph->vertexCount].name, name);
    graph->vertices[graph->vertexCount].id = id;
    graph->vertexCount++;
    //5.正常返回 
    return 0;
}

//添加图中边的权值的函数(即设置边之间的距离)
/*形参需要图,需要操作的两个顶点的编号、要设置的顶点间的距离*/
void addEdge(Graph* graph, int id1, int id2, int distance) {
	//1.定义两个变量要来查找要操作的两个顶点在图中是否存在 
    int index1 = -1, index2 = -1;
    //2.查找要操作的顶点在图中对应的索引
    for (int i = 0; i < graph->vertexCount; i++) 
	{
        if (graph->vertices[i].id == id1) index1 = i;
        if (graph->vertices[i].id == id2) index2 = i;
    }
    //3.判断是否查找成功->只要有一个顶点不在图中,那么所操作的边就不存在 
    if (index1 == -1 || index2 == -1) 
	{
        printf("顶点ID不存在。\n");
        return;
    }
    //4.此时可以设置边的权值 
    /*由于是无向图,所以两个顶点间来回的距离一样*/
    graph->adjacencyMatrix[index1][index2] = distance;
    graph->adjacencyMatrix[index2][index1] = distance;
    graph->dist[index1][index2] = distance; //两顶点间当前最短路径就是此时设置的距离 
    graph->dist[index2][index1] = distance;
}

//插入校园跑中各个顶点以及设置顶点之间的权值的函数 
void setVertex_Edge(Graph* graph)
{
	//1.插入5个校园地点
    printf("初始化校园跑地点... \n");
    insertVertex(graph, "图书馆", 0); //赋值过去的地点是常量 
    insertVertex(graph, "教学楼", 1);
    insertVertex(graph, "体育馆", 2);
    insertVertex(graph, "食堂", 3);
    insertVertex(graph, "宿舍", 4);
    //2.设置各个地点之间的距离(单位:米)
    printf("设置地点间距离... \n");
    addEdge(graph, 0, 1, 300);  // 图书馆-教学楼
    addEdge(graph, 0, 2, 500);  // 图书馆-体育馆
    addEdge(graph, 0, 3, 400);  // 图书馆-食堂
    addEdge(graph, 0, 4, 600);  // 图书馆-宿舍
    
    addEdge(graph, 1, 2, 350);  // 教学楼-体育馆
    addEdge(graph, 1, 3, 450);  // 教学楼-食堂
    addEdge(graph, 1, 4, 550);  // 教学楼-宿舍
    
    addEdge(graph, 2, 3, 200);  // 体育馆-食堂
    addEdge(graph, 2, 4, 700);  // 体育馆-宿舍
    
    addEdge(graph, 3, 4, 300);  // 食堂-宿舍
} 
/*------------------------------------------赵旭文------------------------------------------*/


/*------------------------------------------青格勒------------------------------------------*/
//使用Floyd算法计算所有顶点对之间的最短路径的函数 
/*Floyd算法计算了所有地点之间的最短路径*/
void floydAlgorithm(Graph* graph) {
    //1.初始化距离矩阵和路径矩阵
    for (int i = 0; i < graph->vertexCount; i++) //外层循环为行 
	{
        for (int j = 0; j < graph->vertexCount; j++) //内层循环为列 
		{
			/*1.1.初始时各个顶点间的最短路径就是各个顶点间的距离*/
            graph->dist[i][j] = graph->adjacencyMatrix[i][j];
            /*1.2.特殊判断*/
            if (graph->dist[i][j] < INF && i != j) 
			{
				//表示如果i和j直接相连,路径经过i
                graph->path[i][j] = i; 
            } 
			else 
			{
				//此时表示不直接相连或者i为j 
                graph->path[i][j] = -1;
            }
        }
    }
    //2.使用Floyd算法得出最短路径 
    for (int k = 0; k < graph->vertexCount; k++) /*外层循环k表示当前中转点*/
	{ 
        for (int i = 0; i < graph->vertexCount; i++) /*内层两个循环用来判断新的中转点和最短路径长度*/
		{
            for (int j = 0; j < graph->vertexCount; j++) 
			{
                /*如果通过k顶点可以使路径更短*/
                if (graph->dist[i][k] < INF && graph->dist[k][j] < INF && 
                    graph->dist[i][j] > graph->dist[i][k] + graph->dist[k][j]) 
				{
					//更新i和j之间的最短路径和中转顶点为k(从i到j改为更近的k到j) 
                    graph->dist[i][j] = graph->dist[i][k] + graph->dist[k][j];
                    graph->path[i][j] = graph->path[k][j];
                }
            }
        }
    }
}

//获取从地点start到地点end的最短路径详情的函数
/*形参需要图、起点、终点、中转地点数组、
记录一条完整路径中包含的顶点总数(C语言函数参数默认是值传递,要修改外部变量必须用指针)*/ 
void getShortestPath(Graph* graph, int start, int end, int path[], int* pathLength) {
	//1.判断起点到终点是否是不可达到 
    if (graph->dist[start][end] == INF) 
	{
		//此时代表起点到终点无法达到,直接结束函数 
        *pathLength = 0;
        return;
    }
    //2.此时起点可以到达终点 
    /*2.1.定义中间中转地点数组记录中转地点*/
    int tempPath[MAX_VERTICES];
    /*2.2.定义变量记录当前处理的地点*/
    int index = 0;
    /*2.3.定义变量记录当前终点,最开始就是形参里的终点*/
    int current = end;
    //3.反向追踪路径
    /*之所以反向追踪,是因为如果正向追踪,只是知道了目的地,但不知道从起点出发下一步要去哪儿, 
	反向追踪的话只需要查看前一站是哪里即可,
	类似跑步时回头看脚印,直接沿着成功的路径返回,避免重复探索*/
    while (current != start) //只要没到起点就一直往前找 
	{
		//4.把当前终点赋值给index上的地点,然后index++ 
        tempPath[index++] = current;
        //5.得出当前终点current的前一个顶点 
        /*graph->path[start][current]存储的是:从start到current的最短路径中,
		current的前一个顶点是谁,也就是start到current的中转地点*/
        current = graph->path[start][current];
        /*6.安全判断:如果current变成-1,说明路径追踪过程中出现了断裂,
		-1在path矩阵中表示"没有前驱顶点"或"不可达",
		这种情况应该立即终止函数,避免无限循环 */
        if (current == -1) 
		{
            *pathLength = 0;
            return;
        }
    }
    /*7.循环结束后:补上起点。
	为什么需要这一步?while循环在current == start时终止,所以起点没有被记录,
	需要手动将起点加入路径数组,此时tempPath中存储的是完整的反向路径*/
    tempPath[index++] = start; //这里是先赋值到index上即while结束时指向的start,再index++ 
    //8.反转路径并再反转,就是完整的最短路径内容
	/*8.1.记录一条完整路径中包含的顶点总数:index经过while循环和起点补充后,
	index的值等于路径中顶点的总数,
	index为0,多一个顶点就++,最后多了个起点也++*/ 
    *pathLength = index; 
    /*8.2.反转中转顶点存入path,就是完整的最短路径,
	index - 1:获取最后一个有效索引,因为数组索引从0开始,所以最大索引是元素个数减1;
	- i:从后往前遍历,比如当i=0时,取最后一个元素*/
    for (int i = 0; i < index; i++) 
	{
        path[i] = tempPath[index - 1 - i];
    }
}

//打印任意两地点间的最短路径的函数 
void printAllShortestPaths(Graph* graph) {
	printf("============================================ \n");
    printf("各个地点间的最短路径信息:\n");
    //1.循环打印各个顶点到各个顶点之间的最短距离 
    for (int i = 0; i < graph->vertexCount; i++) //外层循环为行 
	{
        for (int j = i + 1; j < graph->vertexCount; j++) //内层循环为列 
		{
			//2.进行判断两个地点之间是否是无穷的距离 
            if (graph->dist[i][j] < INF) 
			{
				/*3.此时两个地点之间可达,输出两个地点间的最短路径长度,
				  最短路径长度之前已经通过Floyd算法得出 */
                printf("%s →%s:%d米\n", 
                       graph->vertices[i].name, 
                       graph->vertices[j].name, 
                       graph->dist[i][j]);
                //4.打印详细最短路径
                /*4.1.定义数组path记录中转地点*/
                int path[MAX_VERTICES];
                /*4.2.定义变量记录一条完整路径中包含的顶点总数,
				初始为0,因为一开始没给出起点和终点*/
                int pathLength;
                /*4.3.调用getShortestPath函数得出最短路径详情*/
                getShortestPath(graph, i, j, path, &pathLength);
                //4.4.打印起点到终点的最短路径详情 
                printf("   路径:");
                for (int k = 0; k < pathLength; k++) 
				{
					//首先打印当前顶点 
                    printf("%s", graph->vertices[path[k]].name);
                    //再打印一个指向下一个顶点的符号,注:(最后一个地点后不加箭头) 
                    if (k < pathLength - 1) 
					{
                        printf(" → "); 
                    }
                }
                printf("\n");
            } 
			else 
			{
				//此时地点之间的距离为无穷,不可达 
                printf("%s →%s:不可达\n", 
                       graph->vertices[i].name, 
                       graph->vertices[j].name);
            }
            printf("--------------------------------------------\n");
        }
    }
}
/*------------------------------------------青格勒------------------------------------------*/


/*------------------------------------------黄黄------------------------------------------*/
//用来寻找访问所有地点的最短环路的函数 
void findShortestTour(Graph* graph) {
	//1.判断能否执行,至少需要两个地点 
    if (graph->vertexCount < 2) 
	{
        printf("地点数量不足,无法计算环路。\n");
        return;
    }
    /*此时可以计算环路*/
    //2.使用最近邻算法作为近似解,环路方案使用最近邻算法,是近似最优解 
    /*最近邻算法:贪心策略,每次选择最近的未访问点;
	近似解:承认不是绝对最优,但在可接受时间内找到较好解;
	现实考量:完全遍历所有排列在顶点多时计算量爆炸*/
	/*2.1.定义访问标记数组:初始全为0,表示所有地点都未访问,
	访问后标记为1,避免重复访问*/
    int visited[MAX_VERTICES] = {0}; //之后的就是int的默认值即0
	/*2.2.定义环路路径记录器,大小为顶点数+1,因为最后要回到起点*/ 
    int tour[MAX_VERTICES + 1];
    /*2.3.定义总里程累加器,从0开始,每走一段路就累加距离*/
    int totalDistance = 0;
    //3.环路的起跑线设置
    /*3.1.选择图书馆(顶点0)作为起点(也可选择其他顶点)*/
    int current = 0;
    /*3.2.记录起点:环路数组的第一个位置存储起点*/
    tour[0] = current;
    /*3.3.标记起点已访问*/
    visited[current] = 1;
    //4.贪心构建路径
    for (int step = 1; step < graph->vertexCount; step++) 
	{
    	/*外层循环:逐个添加剩余地点,step从1开始,因为起点已经在第0位置*/
    	//5.寻找"最近邻居"
    	/*5.1.定义下一个目标地点next,-1表示尚未找到*/
        int next = -1;
        /*5.2.minDist表示当前最小距离,初始设为无穷大*/
        int minDist = INF;
        //6.寻找最近的未访问顶点
        for (int i = 0; i < graph->vertexCount; i++) 
		{
			/*7.!visited[i]:检查地点i是否未被访问,
			graph->dist[current][i] < minDist:检查是否比当前找到的最近点更近,
			如果两个条件都满足:更新最小距离和目标地点*/
            if (!visited[i] && graph->dist[current][i] < minDist) 
			{
                minDist = graph->dist[current][i];
                next = i;
            }
        }
        /*8.安全防护:如果找不到下一个地点(理论上不应发生),立即终止循环,
		防止无限循环或数组越界*/
        if (next == -1) break;
        //9.更新状态,准备下一轮
        /*9.1.将选中的地点加入环路路径*/
        tour[step] = next;
        /*9.2.累加这段路的距离*/
        totalDistance += minDist;
        /*9.3.标记该地点已访问*/
        visited[next] = 1;
        /*9.4.移动当前位置指针到新地点*/
        current = next;
    }
    //10.完成环路,回到起点,环路的"收尾工作"
	/*10.1.加上返回起点的距离,当前在最后一个访问地点,需要回到起点完成环路*/ 
	/*起点到各个地点的距离在算法前期已经通过 graph->dist 矩阵完整计算了,
	Floyd算法已经计算了所有顶点间的最短距离,
	*/
    totalDistance += graph->dist[current][tour[0]];
    /*10.2.路径数组的闭环,最后一个位置存储起点,形成完整的环形路径*/
    tour[graph->vertexCount] = tour[0];
    //11.输出打印整个环路
    printf("校园跑最佳环路方案(近似解):\n");
    printf("最佳路线:");
    for (int i = 0; i <= graph->vertexCount; i++) //包含了i为graph->vertexCount,因为环路会回到起点 
	{
        printf("%s", graph->vertices[tour[i]].name);
        if (i < graph->vertexCount) //最后一个地点不打印箭头 
		{
            printf(" → "); 
        }
    }
    printf("\n 总距离:%d米\n", totalDistance);
    //12.输出详细路径信息
    printf("\n路径详情:\n");
    for (int i = 0; i < graph->vertexCount; i++) 
	{
        int from = tour[i];
        int to = tour[i + 1];
        printf("从 %s 到 %s:%d米\n", 
               graph->vertices[from].name, 
               graph->vertices[to].name, 
               graph->dist[from][to]);
    }
    printf("============================================ \n");
}
/*------------------------------------------黄黄------------------------------------------*/


/*------------------------------------------吴佳峻------------------------------------------*/
//打印各个顶点之间的距离矩阵的函数 
void printDistanceMatrix(Graph* graph) {
	printf("============================================\n");
    printf("当前各个地点之间的距离如下:\n");
    printf("     ");
    //1.首先遍历输出所有顶点即地点 
    for (int i = 0; i < graph->vertexCount; i++) 
	{
        printf("%-8s", graph->vertices[i].name);
    }
    printf("\n");
    //2.再输出打印各个顶点到各个顶点的距离 
    for (int i = 0; i < graph->vertexCount; i++) 
	{
		/*2.1.首先打印当前地点名称*/
        printf("%-5s", graph->vertices[i].name);
        /*2.2.遍历所有地点,打印当前地点距离各个地点有多远*/
        for (int j = 0; j < graph->vertexCount; j++) 
		{
            if (graph->dist[i][j] == INF)
			{
                printf("INF     ");
            } 
			else 
			{
                printf("%-8d", graph->dist[i][j]);
            }
        }
        printf("\n");
    }
    printf("============================================\n");
}
/*------------------------------------------吴佳峻------------------------------------------*/


int main() {
	//1.创建图(校园跑) 
    Graph campusGraph;
    //2.初始化图
    initGraph(&campusGraph);
    //3.设置校园跑顶点和距离 
    setVertex_Edge(&campusGraph); 
    //4.打印未求最短距离的矩阵
    printDistanceMatrix(&campusGraph);
    //5.使用Floyd算法计算最短路径 
    printf("执行Floyd算法计算最短路径...\n");
    floydAlgorithm(&campusGraph);
    //6.打印求出最短距离的矩阵
    printDistanceMatrix(&campusGraph);
    //7.打印所有最短路径
    printAllShortestPaths(&campusGraph);
    //8.寻找最佳环路
    findShortestTour(&campusGraph);
    return 0;
}
相关推荐
吃好喝好玩好睡好9 小时前
OpenHarmony混合开发实战指南
c语言·python·flutter·vr·visual studio
laocooon5238578869 小时前
C语言,少了&为什么报 SegmentationFault
c语言·开发语言
white-persist9 小时前
【攻防世界】reverse | re1-100 详细题解 WP
c语言·开发语言·网络·汇编·python·算法·网络安全
.YM.Z9 小时前
【数据结构】:排序(二)——归并与计数排序详解
数据结构·算法·排序
武帝为此9 小时前
【数据结构之树状数组】
数据结构·算法
失败才是人生常态9 小时前
算法题归类学习
学习·算法
天赐学c语言9 小时前
12.6 - K个一组翻转链表 && C 编译到执行的4个阶段
数据结构·c++·链表·c编译
leoufung9 小时前
用 DFS 拓扑排序吃透 LeetCode 210:Course Schedule II
算法·leetcode·深度优先
chao1898449 小时前
电容层析成像Tikhonov算法
算法