A*尋路算法的探尋與改良(二)


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中運行的結果差不多是這樣的:

         

 

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


免責聲明!

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



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