A*寻路算法的探寻与改良(三)
A*寻路算法的探寻与改良(三)
by:田宇轩
第三分:这部分内容基于树、查找算法等对A*算法的执行效率进行了改良,想了解细化后的A*算法和变种A*算法内容的朋友们可以跳过这部分并阅读稍后更新的其他内容
3.1 回顾
你可以点击这里回顾文章的第二部分。
在我的上一篇文章中,我们探讨了如何用编程实现A*算法,并给出了C语言的算法实现,这一章内容中我们主要研究如何提高A*算法的执行效率。抛开时间复杂度的复杂计算,我们大概可以知道,函数对数据的操作次数越少,达成目的越快,算法的效率也就越高。沿着这个思路,我们可以先从研究函数对存储结构做的操作频率和具体内容改造它们,使A*更高效。如果前两部分只是初学者的新手教程,那么从本章开始,我们进入了更难的部分。
3.2 分析A*函数的中代价高的操作
在A*中,我们把起点放入iter,然后就重复for循环,可见for循环中重复执行的函数对于A*算法性能的影响是非常重要的,我们就从这个Astar里的最外层for循环开始看,一个函数一个函数地分析出哪个部分可以如何提高A*算法的效率。
(一)首先我们分析的就是这个for循环的循环条件,这个循环的条件在每一次循环发生的时候都会进行判定。最外层的for循环里,我们只判定这么两项内容:
(1)开启列表是否为空。开启列表如果是空的,这就代表我们没有待处理的格子了,已经无路可走,因此开启列表一旦为空,A*算法也就不用执行了。但是看过上一篇文章的朋友应该记得,我们用一个有全局作用域的变量openListCount(引用代码内容用基佬紫色,下同)来记录开启列表里格子的个数,因此,这个判定就只是判定openListCount这个参数是否为0,这种判定很简单,因此不用优化。
(2)终点是否在关闭列表中。一旦终点在关闭列表之中,就说明我们已经找到了最优路径,并且把路径的最有一个格子——终点也进行了父节点标记的操作,这一操作之后,A*也不必继续下去了。回顾上一篇文章,我们是在关闭列表中逐一匹配里面格子的坐标是否和终点坐标相等,这其实是很麻烦的方法。那么,怎么检测终点是否在关闭列表中呢,这里有一个更好的思路:我们都知道起点是唯一一个G值为0的格子(起点距离自己的距离为0),相应的,终点也是唯一一个H值为0的格子。在求曼哈顿距离时,如果发现一个格子的H值为0,那么毫无疑问,这个格子就是终点。我们可以用一个标记(类似process_control.h里面的参数flag一样)来使主循环停止而不用一一匹配新格子与关闭列表。
(二)主循环分析完了,我们来看看迭代器进入循环体内部发生的事情。之后首先发生的事情,是一个查找操作,开启列表中F值最小的(之一)会被找出来并设为当前格。然后是一个把当前格放入关闭列表的操作。先分析这两步操作:
(1)查找开启列表中的最小值。之前这一步操作中我们用的是冒泡排序的一层循环,把它们中最小的数(之一)沉到底,但是这种查找方法显然并不快,开启列表是在稍后的迭代操作中一步一步建立起来的,因此我们每次把东西加入开启列表可以按照最小二叉堆的添加操作做,这样数组的最后一个元素自然就是开启列表中最大的元素(之一)了。
(2)往开启列表中插入新的元素。开启列表是最小二叉堆,因此插入元素要根据最小二叉堆的性质。
(3)删除开启列表中最小的元素。开启列表是最小二叉堆,因此删除元素要根据最小二叉堆的性质。
(4)删除开启列表中指定的元素。开启列表中元素的f值可能会随着重新计算NewG的值而改变,因此我们会先删除指定的元素。这个过程可以有很多种方式实现,这里,我的例子选择完全重新排序开启列表,其实不是很有效率。
(5)把当前元素加入关闭列表,用方便查找的方式构建关闭列表使其易于查找。
(6)搜索关闭列表,配合关闭列表结构提高效率。
因此,我们可以总结出7条可以优化的操作(函数):
(1)void putInOpenList(struct baseNode inList),把一个元素插入开启列表中。开启列表主要是插入删除,因此用最小二叉堆就好了,每次都出队列最小的数字,改进后要求格子按照最小二叉堆的性质进行插入操作。
(2)struct baseNode readTopOpenList(),取出开启列表中最小的数字。改进后这个函数根据最小二叉堆的性质直接获取列表顶部格子即可。
(3)void putInCloseList(struct baseNode temp),当前点加入到关闭列表中。关闭列表只有插入和搜索操作,为了方便检索,可以按照二叉排序树、AVL树或者红黑树的性质做一个关闭列表的操作(然而我并不会红黑树,(逃(哔站粉用于标记吐槽和没什么卵用的内心独白,下同),等我学完红黑树就回来补完这个~QwQ),这里,为了简单(其实只是自己水平有限),做一个方便查找的普通排序列表配合折半查找好了。
(4)void outOpenList(struct baseNode iter),把开启列表中的当前节点删除。既然开启列表是最小二叉堆,删除也要按照这个性质来。
(5)void outopenlist2(struct baseNode iter), 重新排序最小二叉堆,并删除指定的点。
(6)int isInCloseList(struct baseNode temp),查看指定的temp在不在关闭列表中的函数,用折半查找提高效率就好了。
(7)int manhatten(int i, int j),求曼哈顿函数的值,当值为零,赶紧立一个flag,让主循环停止。
OpenList和CloseList的存储结构也随着新设计的数据结构改变就好,明确了这些重点,剩下的活只要实现这些函数,然后把函数覆盖原来的函数(位于func.h中)就好。
3.3 实现7条优化
下面的内容就是这些函数的具体实现。
3.3.1 优化终点在不在关闭列表的判定
首先,我们对比较简单的(7)int manhatten(int i, int j),进行改造如下:
1 //A*中的启发式函数,用于求指定位置和终点之间的曼哈顿距离 2 int manhatten(int i, int j) 3 { 4 int a= (abs(AsceneryList[1].node.i - i) + abs(AsceneryList[1].node.j - j)); 5 if (a==0) 6 { 7 FLAG = 1; 8 } 9 10 return a*10; 11 }
代码3.3.1.1——改造后的manhatten
这里,我们在data.h里定义一个全局变量,int FLAG=0;,这个FLAG初值为0,每当Astar执行中发现某个格子曼哈顿距离为0,相应地,就把这个FLAG的值改为1,然后Astar内最外层for循环判定条件发现FLAG等于1,就意味着终点已经在关闭列表内,可以松口气了(以前是遍历关闭列表找终点在不在里面,这显然是一步优化)。但是相应地,改变Astar最外层for语句为for (; openListCount != 0 && FLAG==0;)。
同时别忘了每次执行Astar前都重置FLAG值为1,只需在Astar函数内开始加上一句FLAG=0;。
3.3.2 优化开启列表
函数(1)(2)(4)(5)都和开启列表有关,分别对应着开启列表的添加元素,最小值出队列,删除元素和搜索,我们这里再处理他们,改为下面这样的函数:
1 //把一个元素插入开启列表中///////// 2 void putInOpenList(struct baseNode inList) 3 { 4 ++openListCount; 5 6 OpenList[openListCount - 1] = inList; 7 for (int i = openListCount - 1; i >0; i=(i-1)/2) 8 { 9 if (OpenList[i].f<OpenList[(i-1)/2].f) 10 { 11 OpenList[i]=OpenList[(i - 1) / 2]; 12 OpenList[(i - 1) / 2] = inList; 13 } 14 else 15 { 16 break; 17 } 18 } 19 } 20 21 //取出开启列表中最小的数 22 struct baseNode readTopOpenList() 23 { 24 return OpenList[0]; 25 } 26 27 //把开启列表中的当前节点删除 28 void outOpenList() 29 { 30 --openListCount; 31 32 OpenList[0]=OpenList[openListCount]; 33 struct baseNode temp; 34 35 for (int i = 0,p=0; 2*i+1<openListCount;i=p) 36 { 37 if (2*i+2>openListCount-1) 38 { 39 p = 2 * i + 1; 40 } 41 else 42 { 43 if (OpenList[2 * i + 1].f<OpenList[2 * i + 2].f) 44 { 45 p = 2 * i + 1; 46 } 47 else 48 { 49 p = 2 * i + 2; 50 } 51 } 52 53 if (OpenList[p].f<OpenList[i].f) 54 { 55 temp=OpenList[p]; 56 OpenList[p]=OpenList[i]; 57 OpenList[i] = temp; 58 } 59 else 60 { 61 break; 62 } 63 } 64 } 65 66 //删除开启列表中指定的元素,重构最小二叉堆 67 //(注:这个可以用很多方法实现,示例并非最优) 68 void outOpenList2(struct baseNode iter) 69 { 70 int number=openListCount-1; 71 struct baseNode openlist2[MAX_number*MAX_number]; 72 for (int i = 0; i < openListCount; ++i) 73 { 74 if (OpenList[i].i==iter.i&&OpenList[i].j==iter.j) 75 { 76 continue; 77 } 78 openlist2[i]=OpenList[i]; 79 } 80 81 openListCount = 0; 82 for (int i = 0; i < number; ++i) 83 { 84 putInOpenList(openlist2[i]); 85 } 86 }
代码3.2.2.1——优化开启列表
3.3.3 优化关闭列表
关闭列表中的元素是只进不出的,因此这里用简单的排序+折半查找优化效率,注意:对于关闭列表中的排序,应该用唯一值做key方便折半查找,这里,唯一key被设置成为格子的i坐标乘地图边的最大值加上格子的j坐标:
1 //把一个元素加入关闭列表中 2 void putInCloseList(struct baseNode temp) 3 { 4 CloseList[closeListCount] = temp; 5 6 struct baseNode iter; 7 for (int i = closeListCount; i>0; --i) 8 { 9 if ((CloseList[i].i*MAX_number+CloseList[i].j+1)<(CloseList[i-1].i*MAX_number + CloseList[i-1].j + 1)) 10 { 11 iter = CloseList[i]; 12 CloseList[i]=CloseList[i - 1]; 13 CloseList[i - 1] = iter; 14 } 15 else 16 { 17 break; 18 } 19 } 20 ++closeListCount; 21 } 22 23 //查看指定的temp在不在关闭列表中的函数 24 int isInCloseList(struct baseNode temp) 25 { 26 int low = 0, high = closeListCount - 1, mid = (low + high) / 2; 27 28 for (; low<=high; mid = (low + high) / 2) 29 { 30 if ((CloseList[mid].i*MAX_number + CloseList[mid].j + 1)==(temp.i*MAX_number + temp.j + 1)) 31 { 32 return 1; 33 } 34 else if((CloseList[mid].i*MAX_number + CloseList[mid].j + 1) > (temp.i*MAX_number + temp.j + 1)) 35 { 36 high = mid - 1; 37 } 38 else 39 { 40 low = mid + 1; 41 } 42 } 43 return 0; 44 }
代码3.3.3.1——优化关闭列表
这里要说明一下思路,我们为了使关闭列表方便查找,使用了插入新元素时排序关闭列表的方式,排序依赖的值应该类似主码,是格子唯一的标记,格子存储在二维数组中,唯一标记显然是格子的坐标,不过坐标是两个值,我们不想弄一个结构体储存格子的key,所以就采用哈希表的思路把格子的横纵坐标转换为一个唯一值,那就是一个格子在二维列表的次序(这个格子在二维列表中是第几个格子)。
3.3 回顾
这样所有的函数就优化完了,这组优化只改动了func.h和data.h,现在我们把改进后的这两个文件重新贴在下方(默认折叠),如果大家在自己按照这种思路写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; 58 59 //用于记录关闭列表里是否有终点 60 int FLAG = 0;
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.i = AsceneryList[i].node.i; 87 AsceneryList[0].node.j = AsceneryList[i].node.j; 88 89 i = 0; 90 printf("成功把%s设置为起点\n",inputS); 91 break; 92 } 93 } 94 if (i != 0) 95 { 96 printf("找不到这个景点\n"); 97 } 98 99 int j = 2; 100 101 char inputM[20]; 102 printf("请输入想查找的景点的名称:\n这个景点将作为终点\n"); 103 scanf("%s", inputM); 104 105 for (; j < sceneryCount; ++j) 106 { 107 if (strcmp(AsceneryList[j].placeName, inputM) == 0) 108 { 109 AsceneryList[1].node.i = AsceneryList[j].node.i; 110 AsceneryList[1].node.j = AsceneryList[j].node.j; 111 112 j = 0; 113 printf("成功把%s设置为终点\n",inputM); 114 break; 115 } 116 } 117 if (j!=0) 118 { 119 printf("找不到这个景点\n"); 120 } 121 } 122 else if(user_input==2) 123 { 124 int i = 0, j = 0, p = 0, q = 0; 125 126 printf("地图坐标从【1,1】开始\n"); 127 printf("请输入起点的横坐标:\n"); 128 scanf("%d", &i); 129 printf("请输入起点的纵坐标:\n"); 130 scanf("%d", &j); 131 132 AsceneryList[0].node.i = i - 1; 133 AsceneryList[0].node.j = j - 1; 134 135 printf("请输入终点的横坐标:\n"); 136 scanf("%d", &p); 137 printf("请输入终点的纵坐标:\n"); 138 scanf("%d", &q); 139 140 AsceneryList[1].node.i = p - 1; 141 AsceneryList[1].node.j = q - 1; 142 } 143 else 144 { 145 printf("输入错误\n"); 146 } 147 } 148 149 //以下函数都是A*算法的一部分/////////////////////////// 150 151 //把一个元素插入开启列表中,使开启列表符合最小二叉堆的性质///////// 152 void putInOpenList(struct baseNode inList) 153 { 154 ++openListCount; 155 156 OpenList[openListCount - 1] = inList; 157 for (int i = openListCount - 1; i >0; i=(i-1)/2) 158 { 159 if (OpenList[i].f<OpenList[(i-1)/2].f) 160 { 161 OpenList[i]=OpenList[(i - 1) / 2]; 162 OpenList[(i - 1) / 2] = inList; 163 } 164 else 165 { 166 break; 167 } 168 } 169 } 170 171 //取出开启列表(最小二叉堆)中最小的数 172 struct baseNode readTopOpenList() 173 { 174 return OpenList[0]; 175 } 176 177 //把一个元素加入关闭列表中,使关闭列表有序 178 void putInCloseList(struct baseNode temp) 179 { 180 CloseList[closeListCount] = temp; 181 182 struct baseNode iter; 183 for (int i = closeListCount; i>0; --i) 184 { 185 if ((CloseList[i].i*MAX_number+CloseList[i].j+1)<(CloseList[i-1].i*MAX_number + CloseList[i-1].j + 1)) 186 { 187 iter = CloseList[i]; 188 CloseList[i]=CloseList[i - 1]; 189 CloseList[i - 1] = iter; 190 } 191 else 192 { 193 break; 194 } 195 } 196 ++closeListCount; 197 } 198 199 //把开启列表中的当前节点删除,使其符合最小二叉堆的性质 200 void outOpenList() 201 { 202 --openListCount; 203 204 OpenList[0]=OpenList[openListCount]; 205 struct baseNode temp; 206 207 for (int i = 0,p=0; 2*i+1<openListCount;i=p) 208 { 209 if (2*i+2>openListCount-1) 210 { 211 p = 2 * i + 1; 212 } 213 else 214 { 215 if (OpenList[2 * i + 1].f<OpenList[2 * i + 2].f) 216 { 217 p = 2 * i + 1; 218 } 219 else 220 { 221 p = 2 * i + 2; 222 } 223 } 224 225 if (OpenList[p].f<OpenList[i].f) 226 { 227 temp=OpenList[p]; 228 OpenList[p]=OpenList[i]; 229 OpenList[i] = temp; 230 } 231 else 232 { 233 break; 234 } 235 } 236 } 237 238 //删除开启列表中指定的元素,重构最小二叉堆 239 //(注:这个可以用很多方法实现,示例并非最优) 240 void outOpenList2(struct baseNode iter) 241 { 242 int number=openListCount-1; 243 struct baseNode openlist2[MAX_number*MAX_number]; 244 for (int i = 0; i < openListCount; ++i) 245 { 246 if (OpenList[i].i==iter.i&&OpenList[i].j==iter.j) 247 { 248 continue; 249 } 250 openlist2[i]=OpenList[i]; 251 } 252 253 openListCount = 0; 254 for (int i = 0; i < number; ++i) 255 { 256 putInOpenList(openlist2[i]); 257 } 258 } 259 260 //对于一路上的每个点,分析它的最多八个邻居,并加入邻居列表 261 void addNeibo(struct baseNode iter) 262 { 263 neibNum = 0; 264 265 for (int i = iter.i - 1; i <= iter.i + 1; ++i) 266 { 267 for (int j = iter.j - 1; j <= iter.j + 1; ++j) 268 { 269 if (i >= 0 && i <= MAX_number - 1 && j >= 0 && j <= MAX_number - 1) 270 { 271 if (i == iter.i&&j == iter.j) 272 { 273 } 274 else 275 { 276 map[i][j].h = manhatten(i, j); 277 278 Neibo[neibNum] = map[i][j]; 279 ++neibNum; 280 } 281 } 282 } 283 } 284 } 285 286 //查看临近格是否在开启列表中的函数 287 int isInOpenList(struct baseNode neibo) 288 { 289 for (int i = 0; i < openListCount - 1; ++i) 290 { 291 if (OpenList[i].i == neibo.i&&OpenList[i].j == neibo.j) 292 { 293 return 1; 294 } 295 } 296 return 0; 297 } 298 299 //查看指定的temp在不在关闭列表中的函数,使用了折半查找 300 int isInCloseList(struct baseNode temp) 301 { 302 int low = 0, high = closeListCount - 1, mid = (low + high) / 2; 303 304 for (; low<=high; mid = (low + high) / 2) 305 { 306 if ((CloseList[mid].i*MAX_number + CloseList[mid].j + 1)==(temp.i*MAX_number + temp.j + 1)) 307 { 308 return 1; 309 } 310 else if((CloseList[mid].i*MAX_number + CloseList[mid].j + 1) > (temp.i*MAX_number + temp.j + 1)) 311 { 312 high = mid - 1; 313 } 314 else 315 { 316 low = mid + 1; 317 } 318 } 319 return 0; 320 } 321 322 //A*中的启发式函数,用于求指定位置和终点之间的曼哈顿距离 323 int manhatten(int i, int j) 324 { 325 int a= (abs(AsceneryList[1].node.i - i) + abs(AsceneryList[1].node.j - j)); 326 if (a==0) 327 { 328 FLAG = 1; 329 } 330 331 return a*10; 332 } 333 334 //求当前点与父亲节点的距离 335 int increment(struct baseNode this) 336 { 337 if ((abs(this.father->i-this.i)==1) && (abs(this.father->j - this.j) == 1)) 338 { 339 return 14*this.weigt; 340 } 341 else if ((this.father->i - this.i) == 0 && (this.father->j - this.j) == 0) 342 { 343 return 0; 344 } 345 else 346 { 347 return 10*this.weigt; 348 } 349 } 350 351 //求出用当前点作为父节点时这个点的G值 352 int NewG(struct baseNode this,struct baseNode father) 353 { 354 if (abs(father.i - this.i) == 1 && abs(father.j - this.j) == 1) 355 { 356 return father.g+14; 357 } 358 else if (abs(father.i - this.i) == 0 && abs(father.j - this.j) == 0) 359 { 360 return father.g; 361 } 362 else 363 { 364 return father.g+10; 365 } 366 } 367 368 //把A*算法的节点按倒序整理到Astack里面 369 void arrange(struct baseNode iter) 370 { 371 AstackCount = 0; 372 for (; ; iter=map[iter.father->i][iter.father->j]) 373 { 374 Astack[AstackCount] = iter; 375 ++AstackCount; 376 if (iter.i == AsceneryList[0].node.i&&iter.j == AsceneryList[0].node.j) 377 { 378 break; 379 } 380 } 381 } 382 383 //打印出A*算法的路径矩阵 384 printAstar() 385 { 386 printf("A为最佳路径,Q为不经过区域\n\n"); 387 int boole = 0; 388 389 for (int i = 0; i < MAX_number; ++i) 390 { 391 for (int j = 0; j < MAX_number; ++j) 392 { 393 for (int w=0; w<AstackCount; ++w) 394 { 395 if (Astack[w].i==i&&Astack[w].j==j) 396 { 397 boole = 1; 398 break; 399 } 400 } 401 402 if (boole==1) 403 { 404 printf("A "); 405 boole = 0; 406 } 407 else 408 { 409 printf("Q "); 410 } 411 } 412 printf("\n"); 413 } 414 } 415 416 //Astar的本体 417 void Astar() 418 { 419 //FLAG用于判断终点是否在关闭列表 420 FLAG=0; 421 //每次执行A*算法,都初始化开启/关闭列表 422 openListCount = 0; 423 closeListCount = 0; 424 425 //创建一个迭代器,每次都等于f值最小的节点 426 struct baseNode iter; 427 428 //让这个迭代器的初值为起点 429 iter.i = AsceneryList[0].node.i; 430 iter.j = AsceneryList[0].node.j; 431 iter.weigt = map[AsceneryList[0].node.i][AsceneryList[0].node.j].weigt; 432 433 //起点的没有父节点,且为唯一G值为0的点 434 iter.g = 0; 435 iter.h = manhatten(iter.i,iter.j); 436 iter.f = iter.g + iter.h; 437 438 //创建终点 439 struct baseNode ender; 440 441 ender.i = AsceneryList[1].node.i; 442 ender.j = AsceneryList[1].node.j; 443 444 //把起点放入开启列表 445 putInOpenList(iter); 446 447 //当开启列表为空或者终点在关闭列表中,结束寻径 448 for (; openListCount != 0 && FLAG==0;) 449 { 450 //取出开启列表中f值最小的节点(之一),并设为iter(当前点) 451 iter = readTopOpenList(); 452 453 //把最小点从开启列表中删除 454 outOpenList(); 455 456 //把当前点记录在关闭列表中 457 putInCloseList(iter); 458 459 //把当前点的邻居加入邻居列表 460 addNeibo(iter); 461 462 //对于每个邻居,分三种情况进行操作 463 for (int i = 0; i < neibNum; ++i) 464 { 465 //如果这个邻居节点不可通过,或者这个邻居节点在关闭列表中,略过它 466 if (Neibo[i].weigt==0 || isInCloseList(Neibo[i])) 467 { 468 } 469 //如果这个邻居节点已经在开启列表中 470 else if(isInOpenList(Neibo[i])) 471 { 472 if (NewG(Neibo[i],iter)<Neibo[i].g) 473 { 474 map[Neibo[i].i][Neibo[i].j].father = &map[iter.i][iter.j]; 475 map[Neibo[i].i][Neibo[i].j].g = map[iter.i][iter.j].g + increment(Neibo[i]); 476 map[Neibo[i].i][Neibo[i].j].f = map[Neibo[i].i][Neibo[i].j].g + map[Neibo[i].i][Neibo[i].j].h; 477 478 outOpenList2(Neibo[i]); 479 putInOpenList(Neibo[i]); 480 } 481 } 482 //如果这个邻居节点不在开启列表中 483 else 484 { 485 map[Neibo[i].i][Neibo[i].j].father= Neibo[i].father = &map[iter.i][iter.j]; 486 map[Neibo[i].i][Neibo[i].j].g = map[iter.i][iter.j].g + increment(Neibo[i]); 487 map[Neibo[i].i][Neibo[i].j].f = map[Neibo[i].i][Neibo[i].j].g + map[Neibo[i].i][Neibo[i].j].h; 488 489 Neibo[i] = map[Neibo[i].i][Neibo[i].j]; 490 putInOpenList(Neibo[i]); 491 } 492 } 493 } 494 495 arrange(map[ender.i][ender.j]); 496 printAstar(); 497 }
在下一章里,我们将尝试用其他的方式存储地图并且用新的方式写启发函数,敬请期待。