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 }
在下一章里,我們將嘗試用其他的方式存儲地圖並且用新的方式寫啟發函數,敬請期待。