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.hdata.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;
data.h

 

  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 }
func.h

  在下一章里,我們將嘗試用其他的方式存儲地圖並且用新的方式寫啟發函數,敬請期待。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM