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為數據定義的頭文件
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為函數的頭文件
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為控制流程的頭文件,被主函數調用
1 #include "process_control.h" 2 3 int main() 4 { 5 testStart(); 6 7 return 0; 8 }
part4為主函數
其實,了解數據結構的人會看出來,A*里的開啟列表每次都要找出里面的最小值,本文中逐個搜索取最小值的方法並不是最好的方法,這涉及到查找,二叉排序樹等等知識,在下一篇文章中,我們開始正式分析如何優化這個算法。
作為參考,這篇文章里的程序在VS2015中運行的結果差不多是這樣的:


點擊這里查看文章第三部分的內容。
