A*寻路算法的探寻与改良(二)
by:田宇轩
第二部分:这部分内容主要是使用C语言编程实现A*,想了解A*算法的优化内容的朋友们可以跳过这部分并阅读稍后更新的其他内容
2.1 回顾
在我的上一篇文章中,我们通过抽象的思维方式得出了A*算法的概念和原理,这一章内容中主要探讨如何用编程实现A*算法。
在数据结构与算法的学习中,每个算法都应该结合一定的数据结构在计算机中存储,然后用对应的函数操控这些数据结构,A*算法也不例外,从上一篇文章中,我们知道,A*算法需要:
(1)地图,这是一个存储静态路网的结构,由格子组成
(2)格子,格子是组成地图的基本单位,每个格子都有坐标,F,G,H,父节点这五种属性;
(3)开启列表,用于记录等待处理的格子;
(4)关闭列表,用于记录已经处理的格子;
(5)起点和终点,用于接受用户输入指定哪个点为起点,哪个点为终点;
这些存储结构都是A*算法需要的,其实为了实现A*算法,我们还需要更多的存储结构,这些结构我们将会在用到的时候抽象出来的。弄清思路之后,我们先用C语言定义一下这些结构,如果您是其他语言的使用者,也可以按照这些结构的描述用其他语言的定义和实现。下面就是C语言对A*所需结构的实现,下面这段代码可以单独定义在一个头文件中,拥有全局作用域,其实让这些代码拥有全局作用域的方式我们并不提倡,这里只是方便教学和理解用。
1 #define MAX_number 5 //这是地图的最大值,可以自己修改以符合实际需求 2 3 //一个比较基础的结构体,用于和地图联动 4 struct baseNode 5 { 6 int i; 7 int j; 8 int weigt; 9 10 int f; 11 int g; 12 int h; 13 14 struct baseNode *father; 15 }; 16 17 //定义了一个全局变量用来存储二维矩阵地图 18 struct baseNode map[MAX_number][MAX_number]; 19 20 //记录起点和终点的数组,起点为Ascenery[0],终点为Ascenery[1] 21 struct baseNode AsceneryList[2]; 22 23 //开启列表,用于A*算法 24 struct baseNode OpenList[MAX_number*MAX_number]; 25 26 //关闭列表,用于A*算法 27 struct baseNode CloseList[MAX_number*MAX_number]; 28 29 //用于记录开启列表中元素的个数 30 int openListCount = 0; 31 32 //用于记录关闭列表中元素的个数 33 int closeListCount = 0;
代码2.1.1—A*的基本存储结构
2.2 A*算法的流程
2.2.1 设计代码体系结构
为了方便用户输入和输出,也方便我们直观地看到A*的结果,在代码结构上,我们准备设计三个头文件:data.h,用于存储A*依赖的结构,func.h,用于写一些功能,process_control.h,用于显示一个简单的用户界面,控制用户流程,识别用户的输入是否在合法范围内,最后,我们用main.c调用所有这些内容。主函数很简单,这里先写出来:
1 #include "process_control.h" 2 3 int main() 4 { 5 testStart(); 6 7 return 0; 8 }
代码2.2.1.1—主函数
2.2.2 流程控制函数
我们把前面定义的存储结构写在了data.h里面。然后我们稍微设计一下控制流程的process.h。这是main.c唯一引用的头文件,里面包含一个testStart函数。
1 #pragma once 2 3 #include "funcs.h" 4 5 //用于控制整个校园导航系统流程 6 void testStart() 7 { 8 //flag用于辅助程序判断有没有足够条件执行各功能 9 int flag1 = 0, flag2 = 0; 10 11 //不断的让用户选择菜单 12 for (;;) 13 { 14 printf("基于A*算法的校园导航系统程序\n\n"); 15 16 printf("你可以进行以下操作:\n"); 17 printf("1.设定校园地图地形\n"); 18 printf("2.设定寻径的起点和终点\n"); 19 printf("3.找出最佳路径\n"); 20 printf("0.退出系统\n\n"); 21 22 //让用户输入自己的选择 23 int userInput = 0; 24 scanf("%d", &userInput); 25 26 //根据自己的选择分别执行inputmap,setroad,Astar三个函数 27 switch (userInput) 28 { 29 case 1: 30 inputmap(); 31 flag1 = 1; 32 printf("设定校园地图地形成功\n\n"); 33 break; 34 35 case 2: 36 if (flag1 == 1) 37 { 38 setRoad(); 39 flag2 = 1; 40 printf("起点终点设定完毕\n\n"); 41 } 42 else 43 { 44 printf("请先完成地图设定\n"); 45 } 46 break; 47 48 case 3: 49 if (flag1 == 1&&flag2==1) 50 { 51 Astar(); 52 printf("寻径完毕\n\n"); 53 } 54 else 55 { 56 printf("请先完成地图、起点终点设定\n"); 57 } 58 break; 59 60 case 0: 61 exit(0); 62 break; 63 64 default: 65 printf("输入不在指定范围内,请重新输入\n\n"); 66 break; 67 } 68 } 69 }
代码2.2.2.1—流程控制函数
2.2.3 设定地图样式的函数inputmap和设定起点终点的函数setroad
让我们先设定好地图再进行A*算法本体的编写,这部分没有什么难度,因此也不先写伪代码和分析逻辑了,对此不感兴趣的朋友可以直接往后看Astar函数的实现。
1 #pragma once 2 3 #include <stdio.h> 4 #include <stdlib.h> 5 #include <string.h> 6 #include <math.h> 7 #include "data_define.h" 8 9 //设定地图的函数 10 void inputmap() 11 { 12 printf("目前地图大小为%d * %d。\n",MAX_number,MAX_number); 13 printf("请通过输入整数的形式填充地图,0代表地形不可通过,\n其他正整数代表权值,权值越大,地形越不方便通过\n\n"); 14 15 for (int i = 0; i <MAX_number; ++i) 16 { 17 for (int j = 0; j < MAX_number; ++j) 18 { 19 scanf("%d", &map[i][j].weigt); 20 21 map[i][j].i = i; 22 map[i][j].j = j; 23 } 24 printf("第%d行输入完毕,共%d行\n\n",i+1,MAX_number); 25 } 26 } 27 28 //设定起点和终点的函数 29 void setRoad() 30 { 31 int i = 0, j = 0, p = 0, q = 0; 32 33 printf("地图坐标从【1,1】开始\n"); 34 printf("请输入起点的横坐标:\n"); 35 scanf("%d", &i); 36 printf("请输入起点的纵坐标:\n"); 37 scanf("%d", &j); 38 39 AsceneryList[0].i = i - 1; 40 AsceneryList[0].j = j - 1; 41 42 printf("请输入终点的横坐标:\n"); 43 scanf("%d", &p); 44 printf("请输入终点的纵坐标:\n"); 45 scanf("%d", &q); 46 47 AsceneryList[1].i = p - 1; 48 AsceneryList[1].j = q - 1; 49 50 }
代码2.2.3.1—设定地图样式和起点终点的函数
2.2.4 Astar函数的设计
由于Astar算法需要对每个点的邻居都分析其F值,而且这里我们用二维矩阵来设定地图,因此一个格子最多有8个邻居格子,为了一一处理这些邻居格子,我们设计一种大小为8的邻居数组,用循环从邻居数组的第一个元素处理到邻居数组的最大第八个元素。邻居数组定义如下:
1 //邻居列表,用于记录每个当前点的所有邻居 2 struct baseNode Neibo[8]; 3 4 //用于记录邻居的个数 5 int neibNum = 0;
代码2.2.4.1—邻居列表的定义
接下来我们按照上前文章总结的流程编写一个代码框架,先不实现各个函数的具体功能。先回顾一下上一篇文章的A*寻路流程:
2.2.4.1—A*寻路流程
复习了流程之后,我们就可以按照流程上面的大致打一个框架:
1 //假设我们预先把起点放入迭代器iter,终点设为ender 2 //把起点放入开启列表 3 putInOpenList(iter); 4 5 //当开启列表为空或者终点在关闭列表中,结束寻径 6 for (; openListCount != 0 && isInCloseList(ender)==0;) 7 { 8 //取出开启列表中f值最小的节点(之一),并设为iter(当前点) 9 iter = readTopOpenList(); 10 11 //把当前点从开启列表中删除 12 outOpenList(iter); 13 14 //把当前点记录在关闭列表中 15 putInCloseList(iter); 16 17 //把当前点的邻居加入邻居列表 18 addNeibo(iter); 19 20 //对于每个邻居,分三种情况进行操作 21 for (int i = 0; i < neibNum; ++i) 22 { 23 //如果这个邻居节点不可通过,或者这个邻居节点在关闭列表中,略过它 24 if (Neibo[i].weigt==0 || isInCloseList(Neibo[i])) 25 { 26 } 27 //如果这个邻居节点已经在开启列表中 28 else if(isInOpenList(Neibo[i])) 29 { //看看以当前格子为父节点,算出来的新G值是不是比原来的G值小,如果更小,就改变这一格的父节点,G值,重新计算F值 30 if (NewG(Neibo[i],iter)<Neibo[i].g) 31 { 32 map[Neibo[i].i][Neibo[i].j].father = &map[iter.i][iter.j]; 33 map[Neibo[i].i][Neibo[i].j].g = map[iter.i][iter.j].g + increment(Neibo[i]); 34 map[Neibo[i].i][Neibo[i].j].f = map[Neibo[i].i][Neibo[i].j].g + map[Neibo[i].i][Neibo[i].j].h; 35 //把这一格的旧记录从开启列表删除,把更新后的这一格的值加入开启列表等待处理 36 outOpenList(Neibo[i]); 37 putInOpenList(Neibo[i]); 38 } 39 } 40 //如果这个邻居节点不在开启列表中 41 else 42 { 43 map[Neibo[i].i][Neibo[i].j].father= Neibo[i].father = &map[iter.i][iter.j]; 44 map[Neibo[i].i][Neibo[i].j].g = map[iter.i][iter.j].g + increment(Neibo[i]); 45 map[Neibo[i].i][Neibo[i].j].f = map[Neibo[i].i][Neibo[i].j].g + map[Neibo[i].i][Neibo[i].j].h; 46 47 Neibo[i] = map[Neibo[i].i][Neibo[i].j]; 48 putInOpenList(Neibo[i]); 49 } 50 } 51 }
代码2.2.4.2—A*寻路流程的代码
然后,只要分别实现上面代码逻辑中的函数就好了:
1 //以下函数都是A*算法的一部分/////////////////////////// 2 3 //把一个元素插入开启列表中///////// 4 void putInOpenList(struct baseNode inList) 5 { 6 OpenList[openListCount] = inList; 7 ++openListCount; 8 } 9 10 //取出开启列表中最小的数 11 struct baseNode readTopOpenList() 12 { 13 struct baseNode temp; 14 15 for (int i = 0; i < openListCount-1; ++i) 16 { 17 if (OpenList[i].f<OpenList[i+1].f) 18 { 19 temp=OpenList[i]; 20 OpenList[i]=OpenList[i + 1]; 21 OpenList[i + 1] = temp; 22 } 23 } 24 25 return OpenList[openListCount-1]; 26 } 27 28 //把一个元素加入关闭列表中 29 void putInCloseList(struct baseNode temp) 30 { 31 CloseList[closeListCount] = temp; 32 33 ++closeListCount; 34 } 35 36 //把开启列表中的当前节点删除 37 void outOpenList(struct baseNode iter) 38 { 39 int i = openListCount - 1; 40 for (; i >= 0;--i) 41 { 42 if (OpenList[i].i==iter.i&&OpenList[i].j==iter.j) 43 { 44 break; 45 } 46 } 47 48 for (int j = i; j < openListCount-1; ++j) 49 { 50 OpenList[j] = OpenList[j+1]; 51 } 52 --openListCount; 53 } 54 55 //对于一路上的每个点,分析它的最多八个邻居,并加入邻居列表 56 void addNeibo(struct baseNode iter) 57 { 58 neibNum = 0; 59 60 for (int i = iter.i - 1; i <= iter.i + 1; ++i) 61 { 62 for (int j = iter.j - 1; j <= iter.j + 1; ++j) 63 { 64 if (i >= 0 && i <= MAX_number - 1 && j >= 0 && j <= MAX_number - 1) 65 { 66 if (i == iter.i&&j == iter.j) 67 { 68 } 69 else 70 { 71 map[i][j].h = manhatten(i, j); 72 73 Neibo[neibNum] = map[i][j]; 74 ++neibNum; 75 } 76 } 77 } 78 } 79 } 80 81 //查看临近格在不在开启列表中的函数 82 int isInOpenList(struct baseNode neibo) 83 { 84 for (int i = 0; i < openListCount - 1; ++i) 85 { 86 if (OpenList[i].i == neibo.i&&OpenList[i].j == neibo.j) 87 { 88 return 1; 89 } 90 } 91 return 0; 92 } 93 94 //查看指定的temp在不在关闭列表中的函数 95 int isInCloseList(struct baseNode temp) 96 { 97 for (int i = 0; i < closeListCount-1; ++i) 98 { 99 if (CloseList[i].i == temp.i&&CloseList[i].j == temp.j) 100 { 101 return 1; 102 } 103 } 104 return 0; 105 } 106 107 //A*中的启发式函数,用于求指定位置和终点之间的曼哈顿距离 108 int manhatten(int i, int j) 109 { 110 return (abs(AsceneryList[1].node.i - i) + abs(AsceneryList[1].node.j - j))*10; 111 } 112 113 //求当前点与父亲节点的距离 114 int increment(struct baseNode this) 115 { 116 if ((abs(this.father->i-this.i)==1) && (abs(this.father->j - this.j) == 1)) 117 { 118 return 14*this.weigt; 119 } 120 else if ((this.father->i - this.i) == 0 && (this.father->j - this.j) == 0) 121 { 122 return 0; 123 } 124 else 125 { 126 return 10*this.weigt; 127 } 128 } 129 130 //求出用当前点作为父节点时这个点的G值 131 int NewG(struct baseNode this,struct baseNode father) 132 { 133 if (abs(father.i - this.i) == 1 && abs(father.j - this.j) == 1) 134 { 135 return father.g+14; 136 } 137 else if (abs(father.i - this.i) == 0 && abs(father.j - this.j) == 0) 138 { 139 return father.g; 140 } 141 else 142 { 143 return father.g+10; 144 } 145 }
代码2.2.4.3—刚才代码中具体函数的分别实现
经过函数实现之后,我们就得出来了一条最短的从起点A到终点B的路径,这条路径上(包含A和B),每一个格子都指向它的父节点,因此我们可以从终点开始,一直遍历父节点,并设置一个迭代器,把每个格子的父节点赋值给迭代器,再储存入一个存储路径的数组里,我们就得到了这条路径:
1 //用来记录路径经过的点的个数 2 int AstackCount = 0; 3 4 //用来储存整理后的路径 5 struct baseNode Astack[MAX_number*MAX_number];
代码2.2.4.4—存储最佳路线的数组
1 //把A*算法的节点按倒序整理到Astack里面 2 void arrange(struct baseNode iter) 3 { 4 AstackCount = 0; 5 for (; ; iter=map[iter.father->i][iter.father->j]) 6 { 7 Astack[AstackCount] = iter; 8 ++AstackCount; 9 if (iter.i == AsceneryList[0].node.i&&iter.j == AsceneryList[0].node.j) 10 { 11 break; 12 } 13 } 14 } 15 16 //打印出A*算法的路径矩阵 17 printAstar() 18 { 19 printf("A为最佳路径,Q为不经过区域\n\n"); 20 int boole = 0; 21 22 for (int i = 0; i < MAX_number; ++i) 23 { 24 for (int j = 0; j < MAX_number; ++j) 25 { 26 for (int w=0; w<AstackCount; ++w) 27 { 28 if (Astack[w].i==i&&Astack[w].j==j) 29 { 30 boole = 1; 31 break; 32 } 33 } 34 35 if (boole==1) 36 { 37 printf("A "); 38 boole = 0; 39 } 40 else 41 { 42 printf("Q "); 43 } 44 } 45 printf("\n"); 46 } 47 }
代码2.2.4.5—迭代整理和输出
大功告成......等等,还差一步,我们在做A*操作时曾经假设iter初始化为起点,ender为终点,记得吗?因此,我们在做A*算法之前还要做这种类似初始化的操作:
//每次执行A*算法,都初始化开启/关闭列表 openListCount = 0; closeListCount = 0; //创建一个迭代器,每次都等于f值最小的节点 struct baseNode iter; //让这个迭代器的初值为起点 iter.i = AsceneryList[0].i; iter.j = AsceneryList[0].j; iter.weigt = map[AsceneryList[0].i][AsceneryList[0].j].weigt; //起点的没有父节点,且为唯一G值为0的点 iter.g = 0; iter.h = manhatten(iter.i,iter.j); iter.f = iter.g + iter.h; //创建终点 struct baseNode ender; ender.i = AsceneryList[1].i; ender.j = AsceneryList[1].j; //把起点放入开启列表 putInOpenList(iter);
代码2.2.4.6—初始化A*
2.3 A*算法总结
这里我们按顺序写一遍A*算法的完整代码,默认是折叠的。看了上文自己做代码的朋友如果实际操作遇到问题,就可以参考以下代码:(下面的代码因为课设加了一些无关紧要的功能)
1 #pragma once 2 3 #define MAX_number 5 4 5 //一个比较基础的结构体,用于和地图联动 6 struct baseNode 7 { 8 int i; 9 int j; 10 int weigt; 11 12 int f; 13 int g; 14 int h; 15 16 struct baseNode *father; 17 }; 18 19 //定义了一个全局变量用来存储二维矩阵地图 20 struct baseNode map[MAX_number][MAX_number]; 21 22 //用于记录景点的数组元素 23 struct scenerySpotsList 24 { 25 struct baseNode node; 26 char placeName[20]; 27 }; 28 29 //邻居列表,用于记录每个当前点的所有邻居 30 struct baseNode Neibo[8]; 31 32 //记录景点,起点和终点的数组 33 struct scenerySpotsList AsceneryList[MAX_number*MAX_number]; 34 35 //开启列表,用于A*算法 36 struct baseNode OpenList[MAX_number*MAX_number]; 37 38 //关闭列表,用于A*算法 39 struct baseNode CloseList[MAX_number*MAX_number]; 40 41 //用于记录现在的景点个数,第1个景点记录在AsceneryList【2】里,AsceneryList【0】和AsceneryList【1】分别用来记录起点和终点 42 int sceneryCount = 2; 43 44 //用于记录开启列表中元素的个数 45 int openListCount = 0; 46 47 //用于记录关闭列表中元素的个数 48 int closeListCount = 0; 49 50 //用于记录邻居的个数 51 int neibNum = 0; 52 53 //用来储存整理后的路径 54 struct baseNode Astack[MAX_number*MAX_number]; 55 56 //用来记录路径经过的点的个数 57 int AstackCount = 0;
part1
part1为数据定义的头文件
1 #pragma once 2 3 #include <stdio.h> 4 #include <stdlib.h> 5 #include <string.h> 6 #include <math.h> 7 #include "data_define.h" 8 9 //设定地图的函数 10 void inputmap() 11 { 12 printf("目前地图大小为%d * %d。\n",MAX_number,MAX_number); 13 printf("请通过输入整数的形式填充地图,0代表地形不可通过,\n其他正整数代表权值,权值越大,地形越不方便通过\n\n"); 14 15 for (int i = 0; i <MAX_number; ++i) 16 { 17 for (int j = 0; j < MAX_number; ++j) 18 { 19 scanf("%d", &map[i][j].weigt); 20 21 map[i][j].i = i; 22 map[i][j].j = j; 23 } 24 printf("第%d行输入完毕,共%d行\n\n",i+1,MAX_number); 25 } 26 } 27 28 //设定校园景点的函数 29 void setView() 30 { 31 for (; ; ) 32 { 33 int inputI = 0; 34 int inputJ = 0; 35 char inputS[20]; 36 37 printf("请输入景点的名称,输入done结束景点录入\n"); 38 scanf("%s", inputS); 39 40 if (strcmp(inputS,"done")==0) 41 { 42 printf("结束输入:\n"); 43 break; 44 } 45 else 46 { 47 strcpy(AsceneryList[sceneryCount].placeName,inputS); 48 49 printf("地图坐标从【1,1】开始\n"); 50 printf("请输入景点的横坐标:\n"); 51 scanf("%d", &inputI); 52 AsceneryList[sceneryCount].node.i = inputI-1; 53 54 printf("请输入景点的纵坐标:\n"); 55 scanf("%d", &inputJ); 56 AsceneryList[sceneryCount].node.j = inputJ-1; 57 58 printf("成功输入一个景点:\n"); 59 ++sceneryCount; 60 } 61 } 62 } 63 64 //设定起点和终点的函数 65 void setRoad() 66 { 67 printf("你可以进行以下操作\n"); 68 printf("1.寻找一个景点\n"); 69 printf("2.手动设定起点终点坐标\n"); 70 71 int user_input = 0; 72 scanf("%d",&user_input); 73 74 if (user_input==1) 75 { 76 int i = 2; 77 78 char inputS[20]; 79 printf("请输入想查找的景点的名称:\n这个景点将作为起点\n"); 80 scanf("%s", inputS); 81 82 for (; i < sceneryCount; ++i) 83 { 84 if (strcmp(AsceneryList[i].placeName, inputS) == 0) 85 { 86 AsceneryList[0].node = AsceneryList[i].node; 87 88 i = 0; 89 printf("成功把%s设置为起点\n",inputS); 90 break; 91 } 92 } 93 if (i != 0) 94 { 95 printf("找不到这个景点\n"); 96 } 97 98 int j = 2; 99 100 char inputM[20]; 101 printf("请输入想查找的景点的名称:\n这个景点将作为终点\n"); 102 scanf("%s", inputM); 103 104 for (; j < sceneryCount; ++j) 105 { 106 if (strcmp(AsceneryList[j].placeName, inputM) == 0) 107 { 108 AsceneryList[1].node = AsceneryList[j].node; 109 110 j = 0; 111 printf("成功把%s设置为终点\n",inputM); 112 break; 113 } 114 } 115 if (j!=0) 116 { 117 printf("找不到这个景点\n"); 118 } 119 } 120 else if(user_input==2) 121 { 122 int i = 0, j = 0, p = 0, q = 0; 123 124 printf("地图坐标从【1,1】开始\n"); 125 printf("请输入起点的横坐标:\n"); 126 scanf("%d", &i); 127 printf("请输入起点的纵坐标:\n"); 128 scanf("%d", &j); 129 130 AsceneryList[0].node.i = i - 1; 131 AsceneryList[0].node.j = j - 1; 132 133 printf("请输入终点的横坐标:\n"); 134 scanf("%d", &p); 135 printf("请输入终点的纵坐标:\n"); 136 scanf("%d", &q); 137 138 AsceneryList[1].node.i = p - 1; 139 AsceneryList[1].node.j = q - 1; 140 } 141 else 142 { 143 printf("输入错误\n"); 144 } 145 } 146 147 //以下函数都是A*算法的一部分/////////////////////////// 148 149 //把一个元素插入开启列表中///////// 150 void putInOpenList(struct baseNode inList) 151 { 152 OpenList[openListCount] = inList; 153 ++openListCount; 154 } 155 156 //取出开启列表中最小的数 157 struct baseNode readTopOpenList() 158 { 159 struct baseNode temp; 160 161 for (int i = 0; i < openListCount-1; ++i) 162 { 163 if (OpenList[i].f<OpenList[i+1].f) 164 { 165 temp=OpenList[i]; 166 OpenList[i]=OpenList[i + 1]; 167 OpenList[i + 1] = temp; 168 } 169 } 170 171 return OpenList[openListCount-1]; 172 } 173 174 //把一个元素加入关闭列表中 175 void putInCloseList(struct baseNode temp) 176 { 177 CloseList[closeListCount] = temp; 178 179 ++closeListCount; 180 } 181 182 //把开启列表中的当前节点删除 183 void outOpenList(struct baseNode iter) 184 { 185 int i = openListCount - 1; 186 for (; i >= 0;--i) 187 { 188 if (OpenList[i].i==iter.i&&OpenList[i].j==iter.j) 189 { 190 break; 191 } 192 } 193 194 for (int j = i; j < openListCount-1; ++j) 195 { 196 OpenList[j] = OpenList[j+1]; 197 } 198 --openListCount; 199 } 200 201 //对于一路上的每个点,分析它的最多八个邻居,并加入邻居列表 202 void addNeibo(struct baseNode iter) 203 { 204 neibNum = 0; 205 206 for (int i = iter.i - 1; i <= iter.i + 1; ++i) 207 { 208 for (int j = iter.j - 1; j <= iter.j + 1; ++j) 209 { 210 if (i >= 0 && i <= MAX_number - 1 && j >= 0 && j <= MAX_number - 1) 211 { 212 if (i == iter.i&&j == iter.j) 213 { 214 } 215 else 216 { 217 map[i][j].h = manhatten(i, j); 218 219 Neibo[neibNum] = map[i][j]; 220 ++neibNum; 221 } 222 } 223 } 224 } 225 } 226 227 //查看临近格在不在开启列表中的函数 228 int isInOpenList(struct baseNode neibo) 229 { 230 for (int i = 0; i < openListCount - 1; ++i) 231 { 232 if (OpenList[i].i == neibo.i&&OpenList[i].j == neibo.j) 233 { 234 return 1; 235 } 236 } 237 return 0; 238 } 239 240 //查看指定的temp在不在关闭列表中的函数 241 int isInCloseList(struct baseNode temp) 242 { 243 for (int i = 0; i < closeListCount-1; ++i) 244 { 245 if (CloseList[i].i == temp.i&&CloseList[i].j == temp.j) 246 { 247 return 1; 248 } 249 } 250 return 0; 251 } 252 253 //A*中的启发式函数,用于求指定位置和终点之间的曼哈顿距离 254 int manhatten(int i, int j) 255 { 256 return (abs(AsceneryList[1].node.i - i) + abs(AsceneryList[1].node.j - j))*10; 257 } 258 259 //求当前点与父亲节点的距离 260 int increment(struct baseNode this) 261 { 262 if ((abs(this.father->i-this.i)==1) && (abs(this.father->j - this.j) == 1)) 263 { 264 return 14*this.weigt; 265 } 266 else if ((this.father->i - this.i) == 0 && (this.father->j - this.j) == 0) 267 { 268 return 0; 269 } 270 else 271 { 272 return 10*this.weigt; 273 } 274 } 275 276 //求出用当前点作为父节点时这个点的G值 277 int NewG(struct baseNode this,struct baseNode father) 278 { 279 if (abs(father.i - this.i) == 1 && abs(father.j - this.j) == 1) 280 { 281 return father.g+14; 282 } 283 else if (abs(father.i - this.i) == 0 && abs(father.j - this.j) == 0) 284 { 285 return father.g; 286 } 287 else 288 { 289 return father.g+10; 290 } 291 } 292 293 //把A*算法的节点按倒序整理到Astack里面 294 void arrange(struct baseNode iter) 295 { 296 AstackCount = 0; 297 for (; ; iter=map[iter.father->i][iter.father->j]) 298 { 299 Astack[AstackCount] = iter; 300 ++AstackCount; 301 if (iter.i == AsceneryList[0].node.i&&iter.j == AsceneryList[0].node.j) 302 { 303 break; 304 } 305 } 306 } 307 308 //打印出A*算法的路径矩阵 309 printAstar() 310 { 311 printf("A为最佳路径,Q为不经过区域\n\n"); 312 int boole = 0; 313 314 for (int i = 0; i < MAX_number; ++i) 315 { 316 for (int j = 0; j < MAX_number; ++j) 317 { 318 for (int w=0; w<AstackCount; ++w) 319 { 320 if (Astack[w].i==i&&Astack[w].j==j) 321 { 322 boole = 1; 323 break; 324 } 325 } 326 327 if (boole==1) 328 { 329 printf("A "); 330 boole = 0; 331 } 332 else 333 { 334 printf("Q "); 335 } 336 } 337 printf("\n"); 338 } 339 } 340 341 //Astar的本体 342 void Astar() 343 { 344 //每次执行A*算法,都初始化开启/关闭列表 345 openListCount = 0; 346 closeListCount = 0; 347 348 //创建一个迭代器,每次都等于f值最小的节点 349 struct baseNode iter; 350 351 //让这个迭代器的初值为起点 352 iter.i = AsceneryList[0].node.i; 353 iter.j = AsceneryList[0].node.j; 354 iter.weigt = map[AsceneryList[0].node.i][AsceneryList[0].node.j].weigt; 355 356 //起点的没有父节点,且为唯一G值为0的点 357 iter.g = 0; 358 iter.h = manhatten(iter.i,iter.j); 359 iter.f = iter.g + iter.h; 360 361 //创建终点 362 struct baseNode ender; 363 364 ender.i = AsceneryList[1].node.i; 365 ender.j = AsceneryList[1].node.j; 366 367 //把起点放入开启列表 368 putInOpenList(iter); 369 370 //当开启列表为空或者终点在关闭列表中,结束寻径 371 for (; openListCount != 0 && isInCloseList(ender)==0;) 372 { 373 //取出开启列表中f值最小的节点(之一),并设为iter(当前点) 374 iter = readTopOpenList(); 375 376 //把当前点从开启列表中删除 377 outOpenList(iter); 378 379 //把当前点记录在关闭列表中 380 putInCloseList(iter); 381 382 //把当前点的邻居加入邻居列表 383 addNeibo(iter); 384 385 //对于每个邻居,分三种情况进行操作 386 for (int i = 0; i < neibNum; ++i) 387 { 388 //如果这个邻居节点不可通过,或者这个邻居节点在关闭列表中,略过它 389 if (Neibo[i].weigt==0 || isInCloseList(Neibo[i])) 390 { 391 } 392 //如果这个邻居节点已经在开启列表中 393 else if(isInOpenList(Neibo[i])) 394 { 395 if (NewG(Neibo[i],iter)<Neibo[i].g) 396 { 397 map[Neibo[i].i][Neibo[i].j].father = &map[iter.i][iter.j]; 398 map[Neibo[i].i][Neibo[i].j].g = map[iter.i][iter.j].g + increment(Neibo[i]); 399 map[Neibo[i].i][Neibo[i].j].f = map[Neibo[i].i][Neibo[i].j].g + map[Neibo[i].i][Neibo[i].j].h; 400 401 outOpenList(Neibo[i]); 402 putInOpenList(Neibo[i]); 403 } 404 } 405 //如果这个邻居节点不在开启列表中 406 else 407 { 408 map[Neibo[i].i][Neibo[i].j].father= Neibo[i].father = &map[iter.i][iter.j]; 409 map[Neibo[i].i][Neibo[i].j].g = map[iter.i][iter.j].g + increment(Neibo[i]); 410 map[Neibo[i].i][Neibo[i].j].f = map[Neibo[i].i][Neibo[i].j].g + map[Neibo[i].i][Neibo[i].j].h; 411 412 Neibo[i] = map[Neibo[i].i][Neibo[i].j]; 413 putInOpenList(Neibo[i]); 414 } 415 } 416 } 417 418 arrange(map[ender.i][ender.j]); 419 printAstar(); 420 }
part2
part2为函数的头文件
1 #pragma once 2 3 #include "funcs.h" 4 5 //用于控制整个校园导航系统流程 6 void testStart() 7 { 8 int flag1 = 0, flag3 = 0; 9 10 for (;;) 11 { 12 printf("基于A*算法的校园导航系统程序\n\n"); 13 14 printf("你可以进行以下操作:\n"); 15 printf("1.设定校园地图地形\n"); 16 printf("2.设定校园景点\n"); 17 printf("3.设定寻径的起点和终点\n"); 18 printf("4.找出最佳路径\n"); 19 printf("0.退出系统\n\n"); 20 21 int userInput = 0; 22 scanf("%d", &userInput); 23 24 switch (userInput) 25 { 26 case 1: 27 inputmap(); 28 flag1 = 1; 29 printf("设定校园地图地形成功\n\n"); 30 break; 31 32 case 2: 33 if (flag1==1) 34 { 35 setView(); 36 printf("校园景点设定完毕\n\n"); 37 } 38 else 39 { 40 printf("请先完成地图设定\n"); 41 } 42 break; 43 44 case 3: 45 if (flag1 == 1) 46 { 47 setRoad(); 48 flag3 = 1; 49 printf("起点终点设定完毕\n\n"); 50 } 51 else 52 { 53 printf("请先完成地图设定\n"); 54 } 55 break; 56 57 case 4: 58 if (flag1 == 1&&flag3==1) 59 { 60 Astar(); 61 printf("寻径完毕\n\n"); 62 } 63 else 64 { 65 printf("请先完成地图、起点终点设定\n"); 66 } 67 break; 68 69 case 0: 70 exit(0); 71 break; 72 73 default: 74 printf("输入不在指定范围内,请重新输入\n\n"); 75 break; 76 } 77 } 78 }
part3
part3为控制流程的头文件,被主函数调用
1 #include "process_control.h" 2 3 int main() 4 { 5 testStart(); 6 7 return 0; 8 }
part4
part4为主函数
其实,了解数据结构的人会看出来,A*里的开启列表每次都要找出里面的最小值,本文中逐个搜索取最小值的方法并不是最好的方法,这涉及到查找,二叉排序树等等知识,在下一篇文章中,我们开始正式分析如何优化这个算法。
作为参考,这篇文章里的程序在VS2015中运行的结果差不多是这样的: