一、问题描述
看图说话,问题很简单。
二、迟来的代码
贪婪算法
#include <stdio.h> #include <stdlib.h> #define N 21 // 城市个数 #define MaxInt 32768 // 定义无穷大 // 枚举城市名,给城市名从0开始编码,相当于"#define Arad 0", enum City{Arad = 0, Bucharest, Craiova, Dobreta, Eforie, Fagaras, Giurgiu, Hirsova,Iasi, Lugoj, Mehadia, Neamt, Oradea, Pitesti, Rimnicu_Vilcea, Sibiu, Timisoara,Urziceni, Vaslui, Zerind}; int arcs[N][N] = {0}; // 定义两个城市间实际距离 // 定义两个城市间的直线距离,即最短距离,小于等于实际距离 int line[N] = {366, 0, 160, 242, 161, 176, 77, 151, 226, 244, 241, 234, 380, 10, 193, 253, 329, 80, 199, 374}; // 初始化问题状态 void init() { // 定义两个城市间的距离 { arcs[Zerind][Oradea] = 71; arcs[Zerind][Arad] = 75; arcs[Arad][Sibiu] = 140; arcs[Arad][Timisoara] = 118; arcs[Oradea][Sibiu] = 151; arcs[Timisoara][Lugoj] = 111; arcs[Lugoj][Mehadia] = 70; arcs[Mehadia][Dobreta] = 75; arcs[Dobreta][Craiova] = 120; arcs[Sibiu][Fagaras] = 99; arcs[Sibiu][Rimnicu_Vilcea] = 80; arcs[Rimnicu_Vilcea][Pitesti] = 97; arcs[Rimnicu_Vilcea][Craiova] = 146; arcs[Craiova][Pitesti] = 138; arcs[Pitesti][Bucharest] = 101; arcs[Fagaras][Bucharest] = 211; arcs[Bucharest][Giurgiu] = 90; arcs[Bucharest][Urziceni] = 85; arcs[Urziceni][Hirsova] = 98; arcs[Hirsova][Eforie] = 86; arcs[Urziceni][Vaslui] = 142; arcs[Vaslui][Iasi] = 92; arcs[Iasi][Neamt] = 87; } int i, j; // 初始化剩下城市的距离,相同两个城市间距离定义为0 for(i = 0; i < N; i++) { for(j = 0; j < N; j++) { // 如果两个城市之间可以直达,即图中两个城市节点之间的边 if(arcs[i][j] > 0) { arcs[j][i] = arcs[i][j]; } else if(arcs[j][i] > 0) { arcs[i][j] = arcs[j][i]; } // 无法直接到达的两个城市之间的距离为-1 else if(i == j) { arcs[i][j] = 0; } else { arcs[i][j] = -1; } } } } // 根据城市编码转换成城市名 void transform(int city) { switch(city) { case Arad: printf("%s\n", "Arad"); break; case Bucharest: printf("%s\n", "Bucharest"); break; case Craiova: printf("%s\n", "Craiova"); break; case Dobreta: printf("%s\n", "Dobreta"); break; case Eforie: printf("%s\n", "Eforie"); break; case Fagaras: printf("%s\n", "Fagaras"); break; case Giurgiu: printf("%s\n", "Giurgiu"); break; case Hirsova: printf("%s\n", "Hirsova"); break; case Iasi: printf("%s\n", "Iasi"); break; case Lugoj: printf("%s\n", "Lugoj"); break; case Mehadia: printf("%s\n", "Arad"); break; case Neamt: printf("%s\n", "Neamt"); break; case Oradea: printf("%s\n", "Oradea"); break; case Pitesti: printf("%s\n", "Pitesti"); break; case Rimnicu_Vilcea: printf("%s\n", "Rimnicu Vilcea"); break; case Sibiu: printf("%s\n", "Sibiu"); break; case Timisoara: printf("%s\n", "Timisoara"); break; case Urziceni: printf("%s\n", "Urziceni"); break; case Vaslui: printf("%s\n", "Vaslui"); break; case Zerind: printf("%s\n", "Zerind"); break; } } // 打印解的路径 void show(int *Path) { int i; printf("解的路径如下\n"); for(i = 0; Path[i] != -1; i++) { transform(Path[i]); // 打印当前节点 } } // 贪婪算法 void Greedy(int V0, int VN) { printf("start Greedy\n"); int i, v, w, min, cost; int S[N]; // 记录从源点V0到终点Vi是否已被确定为最短路径长度 int Path[N]; // 记录从源点V0到终点Vi的当前最短路径长度上Vi的直接前驱节点号 // 初始化解的路径,-1表示为节点没在路径,n表示n对应的城市在路径中 for(i = 0; i < N; i++) { S[i] = 0; Path[i] = -1; } cost = 0; S[V0] = 1; // 标记初始节点已经加入解的路径中 Path[0] = V0; // 把初始节点加入解的路径中 // 最多需要搜索N次,如果都没有找到,就直接结束,一般这种情况不会发生 for(i = 1; i < N; i++) { v = V0; min = MaxInt; // 从剩下的未标记的,是当前的节点的后继节点中的节点中 // 选出一个离目标的节点的距离最小的,作为下次拓展的节点 for(w = 0; w < N; w++) { if(!S[w] && arcs[Path[i-1]][w] > 0 && line[w] < min) { v = w; min = line[w]; } } S[v] = 1; // 标记已经选出的最小的节点 Path[i] = v; // 把选出的节点加入到解的路径中 cost += arcs[Path[i-1]][v]; // 累加实际代价 printf("本次拓展节点:"); transform(v); printf("估价函数值:%d\n", line[v]); // 如果选出的节点就是目标节点,结束 if(Path[i] == VN) { printf("Greedy finsih\n\n"); show(Path); printf("实际的总代价:%d\n", cost); break; } } } int main(void) { init(); Greedy(Zerind, Bucharest); return 0; }
启发式算法
#include <stdio.h> #include <stdlib.h> #define N 21 // 城市个数 #define MaxInt 32768 // 定义无穷大 // 枚举城市名,给城市名从0开始编码,相当于"#define Arad 0", enum City{Arad = 0, Bucharest, Craiova, Dobreta, Eforie, Fagaras, Giurgiu, Hirsova,Iasi, Lugoj, Mehadia, Neamt, Oradea, Pitesti, Rimnicu_Vilcea, Sibiu, Timisoara,Urziceni, Vaslui, Zerind}; int arcs[N][N] = {0}; // 定义两个城市间的距离 // 定义两个城市间的直线距离,即最短距离,小于等于实际距离 int line[N] = {366, 0, 160, 242, 161, 176, 77, 151, 226, 244, 241, 234, 380, 10, 193, 253, 329, 80, 199, 374}; int g[N] = {0}; // 从初始节点到当前节点的代价 // 初始化问题状态 void init() { // 定义两个城市间的距离 { arcs[Zerind][Oradea] = 71; arcs[Zerind][Arad] = 75; arcs[Arad][Sibiu] = 140; arcs[Arad][Timisoara] = 118; arcs[Oradea][Sibiu] = 151; arcs[Timisoara][Lugoj] = 111; arcs[Lugoj][Mehadia] = 70; arcs[Mehadia][Dobreta] = 75; arcs[Dobreta][Craiova] = 120; arcs[Sibiu][Fagaras] = 99; arcs[Sibiu][Rimnicu_Vilcea] = 80; arcs[Rimnicu_Vilcea][Pitesti] = 97; arcs[Rimnicu_Vilcea][Craiova] = 146; arcs[Craiova][Pitesti] = 138; arcs[Pitesti][Bucharest] = 101; arcs[Fagaras][Bucharest] = 211; arcs[Bucharest][Giurgiu] = 90; arcs[Bucharest][Urziceni] = 85; arcs[Urziceni][Hirsova] = 98; arcs[Hirsova][Eforie] = 86; arcs[Urziceni][Vaslui] = 142; arcs[Vaslui][Iasi] = 92; arcs[Iasi][Neamt] = 87; } int i, j; for(i = 0; i < N; i++) { for(j = 0; j < N; j++) { if(arcs[i][j] > 0) { arcs[j][i] = arcs[i][j]; } else if(arcs[j][i] > 0) { arcs[i][j] = arcs[j][i]; } else if(i != j) { arcs[i][j] = MaxInt; arcs[j][i] = MaxInt; } } } } // 转换成城市名 void transform(int city) { switch(city) { case Arad: printf("%s\n", "Arad"); break; case Bucharest: printf("%s\n", "Bucharest"); break; case Craiova: printf("%s\n", "Craiova"); break; case Dobreta: printf("%s\n", "Dobreta"); break; case Eforie: printf("%s\n", "Eforie"); break; case Fagaras: printf("%s\n", "Fagaras"); break; case Giurgiu: printf("%s\n", "Giurgiu"); break; case Hirsova: printf("%s\n", "Hirsova"); break; case Iasi: printf("%s\n", "Iasi"); break; case Lugoj: printf("%s\n", "Lugoj"); break; case Mehadia: printf("%s\n", "Arad"); break; case Neamt: printf("%s\n", "Neamt"); break; case Oradea: printf("%s\n", "Oradea"); break; case Pitesti: printf("%s\n", "Pitesti"); break; case Rimnicu_Vilcea: printf("%s\n", "Rimnicu Vilcea"); break; case Sibiu: printf("%s\n", "Sibiu"); break; case Timisoara: printf("%s\n", "Timisoara"); break; case Urziceni: printf("%s\n", "Urziceni"); break; case Vaslui: printf("%s\n", "Vaslui"); break; case Zerind: printf("%s\n", "Zerind"); break; } } // 打印解的路径 void show(int *Path) { int i; printf("解的路径如下\n"); for(i = 0; Path[i] != -1; i++) { transform(Path[i]); // 打印当前节点 } } // 启发式算法 void Astar(int V0, int VN) { printf("Astar start\n"); int i, v, w, min, cost; int S[N]; // 记录从源点V0到终点Vi是否已被确定为最短路径长度 int Path[N]; // 记录从源点V0到终点Vi的当前最短路径长度上Vi的直接前驱节点号 for(i = 0; i < N; i++) { S[i] = 0; // 初始时所有节点都没有被加入解路径 Path[i] = -1; // 初始化解的路径 g[i] = arcs[V0][i]; // 初始化节点到当前节点的代价 } cost = 0; S[V0] = 1; // 标记已经选出的最小的节点 Path[0] = V0; // 把选出的节点加入到解的路径中 for(i = 1; i < N; i++) { v = V0; min = MaxInt; // 从不在解的路径中的节点组成的节点集和当前节点的后继节点的组成的节点集的交集中 // 选择出估计函数最小的节点,作为下一个要拓展的节点 for(w = 0; w < N; w++) { if(!S[w] && arcs[Path[i-1]][w] > 0 && arcs[Path[i-1]][w] < MaxInt) { if(g[w]+ line[w] < min) { v = w; min = g[w]+ line[w]; } } } S[v] = 1; // 标记已经选出的最小的节点 Path[i] = v; // 把选出的节点加入到解的路径中 printf("本次拓展节点:"); transform(v); printf("估计函数值:%d\n", min); cost += arcs[Path[i-1]][v]; // 累加实际代价 // 更改当前节点到剩下不在解的路径中的代价 // 这里直接修改,不需要比较g[w] 与 g[v] + arcs[v][w]的大小 // 这里这里没有采用open表和close表,所以为了避免回溯的问题,直接修改 for(w = 0; w < N; w++) { if(!S[w]) { g[w] = g[v] + arcs[v][w]; } } // 如果选出的节点就是目标节点,结束 if(Path[i] == VN) { printf("Astar finish\n\n"); show(Path); printf("总共的代价:%d\n", cost); break; } } } int main(void) { init(); Astar(Zerind, Bucharest); return 0; }
三、简单分析
相信认真看完代码注释的程序猿同学,应该能看懂了,这里的采用的启发式搜索进行了一点点改进,是更加实际情况而定,注释也有说明。
此外特别想说明一下,这个题,最复杂的就是如何表示城市名了,因为要打印解的路径码,所以要对城市名进行编码,然后输出时,又进行译码,这种方法对解决某些类似的问题特别有效!这里采用了枚举的方法来进行编码,当然也可以采用#define 宏定义的方法,不过不建议采用,因为特别长。
至于具体的流程图,这里不给出,因为太难画了,而且也相信别的博主的博客上也有,这里主要讲解代码的写法。