A*尋路演算法

來源:互聯網
上載者:User

標籤:c語言   else   歐幾裡德   遊戲編程   tmp   include   堆排   路由演算法   code   

A*簡單介紹

圖搜尋技術在遊戲編程中無處不在,不管什麼遊戲類型,圖搜尋方法不可避免成為遊戲AI的基礎。比方以下夢幻西遊自己主動找人的功能



A*搜尋演算法就是圖搜尋演算法的一種。俗稱A星演算法。這是一種在圖形平面上,有多個節點的路徑。求出最低通過成本的演算法。經常使用於遊戲中的NPC的移動計算。或線上遊戲的BOT的移動計算上。


從Dijkstra單源最短路演算法說起

Dijkstra(迪傑斯特拉)演算法是典型的最短路徑路由演算法。用於計算一個節點到其它全部節點的最短路徑。

主要特點是以起始點為中心向外層層擴充。直到擴充到終點為止。Dijkstra演算法能得出最短路徑的最優解,但因為它遍曆計算的節點非常多,所以效率低。
Dijkstra演算法是非常有代表性的最短路演算法。在非常多專業課程中都作為基本內容有具體的介紹。如資料結構,圖論,運籌學等等。


演算法的基本過程例如以下:

假設存在G=<V,E>。源頂點為V0,U={V0},dist[i]記錄V0到i的最短距離,path[i]記錄從V0到i路徑上的i前面的一個頂點。

1.從V-U中選擇使dist[i]值最小的頂點i,將i增加到U中;

2.更新與i直接相鄰頂點的dist值。

(dist[j]=min{dist[j],dist[i]+matrix[i][j]})

3.直到U=V。停止。



c語言實現

//djk.c 2014.10.26 Quan#include <stdio.h>#define INT_MAX 1000000#define MAXN  1000//n - node count;//s - source node//map[i][j] - the distance of i to j. I and j are neighboring.//dist - record distance frome s//pre - record the prenodevoid djk(int n, int s, int map[MAXN][MAXN], int dist[MAXN], int pre[MAXN]){     int i,j,k;     int min;     int p[MAXN];     for(i = 1;i <= n;i++)     {          p[i] = 0;          if(i != s) dist[i] = map[s][i];     }     dist[s] = 0; //array p as the mark for solution-set     p[s] = 1;     for(i = 1;i <= n-1;i++)     {          min = INT_MAX;  //k as the node which is the nearest to source and not marked          k = 0;  //find the node which is the nearest to source and not marked          for(j = 1;j <= n;j++)          {               if(!p[j]&&dist[j] < min)               {                    min = dist[j];                    k = j;               }          }// no node available          if(k == 0) return;  //first node          if(i == 1) pre[k] = s;  //color the node          p[k] = 1;  //update dist[j]          for(j = 1; j <= n-1; j++)          {               if(!p[j]&&map[k][j] != INT_MAX&&dist[j] > dist[k] + map[k][j])               {                    dist[j] = dist[k] + map[k][j];//renew the distance of node j to the solution-set                    pre[j] = k;               }          }     }}int main(){     int i,j,s = 2;     int n = 6;     int dist[MAXN] = {0};     int pre[MAXN] = {0};     int map[MAXN][MAXN];     //Assign every path length to max.     for(i = 1; i <= n; i++)          for(j = 1; j <= n; j++)                {                    map[i][j] = INT_MAX;               }      //Assign the length of i to i to 0.              for(i = 1;i <= n;i++)     {         map[i][i] = 0;      }      map[1][2] = 50;map[1][3] = 10;     map[1][5] = 45;map[2][3] = 15;     map[2][4] = 50;map[2][5] = 10;     map[3][1] = 20;map[3][4] = 15;     map[4][2] = 20;map[4][5] = 35;     map[5][4] = 30;map[6][4] = 3;     djk(n, 2, map, dist, pre);     for(j = 1;j <= n;j++)     {          printf("%d -> %d : %d\n", s, j, dist[j]);     }    return 0;}



編譯執行



A*演算法

A*演算法是一種啟示式搜尋演算法。啟示式搜尋就是在狀態空間中的搜尋對每個搜尋的位置進行評估,得到最好的位置。再從這個位置進行搜尋直到目標。這樣能夠省略大量無謂的搜尋路徑,提高了效率。在啟示式搜尋中。對位置的估價是十分重要的。

採用了不同的估價能夠有不同的效果。


A*演算法的公式為:f(n)=g(n)+h(n)。g(n)表示從起點到隨意頂點n的實際距離。h(n)表示隨意頂點n到目標頂點的估算距離。

這個公式遵循以下特性:

● 假設h(n)為0。僅僅需求出g(n),即求出起點到隨意頂點n的最短路徑。則轉化為單源最短路徑問題。即Dijkstra演算法
● 假設h(n)<=“n到目標的實際距離”,則一定能夠求出最優解。

並且h(n)越小,須要計算的節點越多。演算法效率越低。


對於函數h(n),估算距離經常使用的方法有:

● 曼哈頓距離:定義曼哈頓距離的正式意義為L1-距離或城市區塊距離。也就是在歐幾裡德空間的固定直角座標繫上兩點所形成的線段對軸產生的投影的距離總和。比如在平面上。座標(x1,y1)的點P1與座標(x2, y2)的點P2的曼哈頓距離為:|x1 - x2| + |y1 - y2|。
● 歐氏距離:是一個通常採用的距離定義。它是在m維空間中兩個點之間的真實距離。在二維和三維空間中的歐氏距離的就是兩點之間的距離。比如在平面上,座標(x1,y1)的點P1與座標(x2, y2)的點P2的歐氏距離為: sqrt((x1-x2)^2+(y1-y2)^2 )。


● 切比雪夫距離:是兩個向量之間各分量差值的最大值。比如在平面上,座標(x1, y1)的點P1與座標(x2, y2)的點P2的切比雪夫距離為:max(|x1 - x2| , |y1 - y2|)。


A*演算法最重要的就是維護兩個列表,一個開啟列表,一個關閉列表。演算法描寫敘述例如以下

1,從點A開始。並且把它作為待處理點存入一個“開啟列表”。開啟列表就像一張購物清單。雖然如今列表裡僅僅有一個元素。但以後就會多起來。你的路徑可能會通過它包括的方格,也可能不會。基本上。這是一個待檢查方格的列表。
2,尋找起點周圍全部可到達或者可通過的方格,跳過有牆,水,或其它無法通過地形的方格。

計算出f,g,h。把他們增加開啟列表。為全部這些方格儲存點A作為“父方格”。當我們想描寫敘述路徑的時候,父方格的資料是十分重要的。後面會解釋它的具體用途。


3。從開啟列表中刪除點A。把它增加到一個“關閉列表”,列表中儲存全部不須要再次檢查的方格。

4,把它從開啟列表中刪除 f 值最小的節點,然後增加到關閉列表中。


5。檢查全部相鄰格子。跳過那些已經在關閉列表中的或者不可通過的(有牆。水的地形。或者其它無法通過的地形),計算出f,g,h。把他們增加進開啟列表,假設他們還不在裡面的話。

把選中的方格作為新的方格的父節點。
6。假設某個相鄰格已經在開啟列表裡了,檢查如今的這條路徑是否更好。換句話說。檢查假設我們用新的路徑到達它的話。G值是否會更低一些。假設不是。那就什麼都不做。


舉個栗子,例如以



如今想要從A走到B,中間黑色是一堵牆,無法穿過。設C為n點。考慮下C點的f(n)




這裡用曼哈頓距離來計算估值,方格上下左右相鄰距離為10。對角線相鄰為14。曼哈頓距離以10為單位,對於C。g(n) = 15, h(n) = 40.


接下來的步驟能夠看以下的動畫示範。


接下來是演算法的c語言實現。

#include <stdio.h>#include <stdlib.h>#define STARTNODE1#define ENDNODE2#define BARRIER3typedef struct AStarNode{int s_x;// 座標(終於輸出路徑須要)int s_y;int s_g;// 起點到此點的距離( 由g和h能夠得到f。此處f省略。f=g+h )ints_h;// 啟示函數預測的此點到終點的距離int s_style;// 結點類型:起始點。終點,障礙物struct AStarNode * s_parent;// 父節點int s_is_in_closetable;// 是否在close表中int s_is_in_opentable;// 是否在open表中}AStarNode, *pAStarNode;AStarNode  map_maze[10][10];// 結點數組pAStarNode open_table[100];// open表pAStarNode close_table[100];// close表int  open_node_count;// open表中節點數量int   close_node_count; // close表中結點數量pAStarNode path_stack[100];// 儲存路徑的棧int        top = -1;// 棧頂// 交換兩個元素// void swap( int idx1, int idx2 )  {  pAStarNode tmp = open_table[idx1];open_table[idx1] = open_table[idx2];open_table[idx2] = tmp;}  // 堆調整// void adjust_heap( int /*i*/nIndex )    { int curr = nIndex;int child = curr * 2 + 1;// 得到左孩子idx( 下標從0開始。全部做孩子是curr*2+1 )int parent = ( curr - 1 ) / 2;// 得到雙親idxif (nIndex < 0 || nIndex >= open_node_count){return;}// 往下調整( 要比較左右孩子和cuur parent )// while ( child < open_node_count ){// 小根堆是雙親值小於孩子值// if ( child + 1 < open_node_count && open_table[child]->s_g + open_table[child]->s_h  > open_table[child+1]->s_g + open_table[child+1]->s_h ){++child;// 推斷左右孩子大小}if (open_table[curr]->s_g + open_table[curr]->s_h <= open_table[child]->s_g + open_table[child]->s_h){break;}else{swap( child, curr );// 交換節點curr = child;// 再推斷當前孩子節點child = curr * 2 + 1;// 再推斷左孩子}}if (curr != nIndex){return;}// 往上調整( 僅僅須要比較cuur child和parent )// while (curr != 0){if (open_table[curr]->s_g + open_table[curr]->s_h >= open_table[parent]->s_g + open_table[parent]->s_h){break;}else{swap( curr, parent );curr = parent;parent = (curr-1)/2;}}}  // 推斷鄰居點能否夠進入open表// void insert_to_opentable( int x, int y, pAStarNode curr_node, pAStarNode end_node, int w ){int i;if ( map_maze[x][y].s_style != BARRIER )// 不是障礙物{if ( !map_maze[x][y].s_is_in_closetable )// 不在閉表中{if ( map_maze[x][y].s_is_in_opentable )// 在open表中{// 須要推斷是否是一條更最佳化的路徑// if ( map_maze[x][y].s_g > curr_node->s_g + w )// 假設更最佳化{map_maze[x][y].s_g = curr_node->s_g + w;map_maze[x][y].s_parent = curr_node;for ( i = 0; i < open_node_count; ++i ){if ( open_table[i]->s_x == map_maze[x][y].s_x && open_table[i]->s_y == map_maze[x][y].s_y ){break;}}adjust_heap( i );// 以下調整點}}else// 不在open中{map_maze[x][y].s_g = curr_node->s_g + w;map_maze[x][y].s_h = abs(end_node->s_x - x ) + abs(end_node->s_y - y);map_maze[x][y].s_parent = curr_node;map_maze[x][y].s_is_in_opentable = 1;open_table[open_node_count++] = &(map_maze[x][y]);}}}}// 尋找鄰居// 對上下左右8個鄰居進行尋找//  void get_neighbors( pAStarNode curr_node, pAStarNode end_node ){int x = curr_node->s_x;int y = curr_node->s_y;// 以下對於8個鄰居進行處理!// if ( ( x + 1 ) >= 0 && ( x + 1 ) < 10 && y >= 0 && y < 10 ){insert_to_opentable( x+1, y, curr_node, end_node, 10 );}if ( ( x - 1 ) >= 0 && ( x - 1 ) < 10 && y >= 0 && y < 10 ){insert_to_opentable( x-1, y, curr_node, end_node, 10 );}if ( x >= 0 && x < 10 && ( y + 1 ) >= 0 && ( y + 1 ) < 10 ){insert_to_opentable( x, y+1, curr_node, end_node, 10 );}if ( x >= 0 && x < 10 && ( y - 1 ) >= 0 && ( y - 1 ) < 10 ){insert_to_opentable( x, y-1, curr_node, end_node, 10 );}if ( ( x + 1 ) >= 0 && ( x + 1 ) < 10 && ( y + 1 ) >= 0 && ( y + 1 ) < 10 ){insert_to_opentable( x+1, y+1, curr_node, end_node, 14 );}if ( ( x + 1 ) >= 0 && ( x + 1 ) < 10 && ( y - 1 ) >= 0 && ( y - 1 ) < 10 ){insert_to_opentable( x+1, y-1, curr_node, end_node, 14 );}if ( ( x - 1 ) >= 0 && ( x - 1 ) < 10 && ( y + 1 ) >= 0 && ( y + 1 ) < 10 ){insert_to_opentable( x-1, y+1, curr_node, end_node, 14 );}if ( ( x - 1 ) >= 0 && ( x - 1 ) < 10 && ( y - 1 ) >= 0 && ( y - 1 ) < 10 ){insert_to_opentable( x-1, y-1, curr_node, end_node, 14 );}}int main(){ // 地圖數組的定義// AStarNode *start_node;// 起始點AStarNode *end_node;// 結束點AStarNode *curr_node;// 當前點int       is_found;// 是否找到路徑int maze[][10] ={// 僅僅為了好賦值給map_maze{ 1,0,0,3,0,3,0,0,0,0 },{ 0,0,3,0,0,3,0,3,0,3 },{ 3,0,0,0,0,3,3,3,0,3 },{ 3,0,3,0,0,0,0,0,0,3 },{ 3,0,0,0,0,3,0,0,0,3 },{ 3,0,0,3,0,0,0,3,2,3 },{ 3,0,0,0,0,3,3,0,0,0 },{ 0,0,0,0,0,0,0,0,0,0 },{ 3,3,3,0,0,3,0,3,0,3 },{ 3,0,0,0,0,3,3,3,0,3 },};inti,j,x;// 以下準備點// for( i = 0; i < 10; ++i ){for ( j = 0; j < 10; ++j ){map_maze[i][j].s_g = 0;map_maze[i][j].s_h = 0;map_maze[i][j].s_is_in_closetable = 0;map_maze[i][j].s_is_in_opentable = 0;map_maze[i][j].s_style = maze[i][j];map_maze[i][j].s_x = i;map_maze[i][j].s_y = j;map_maze[i][j].s_parent = NULL;if ( map_maze[i][j].s_style == STARTNODE )// 起點{start_node = &(map_maze[i][j]);}else if( map_maze[i][j].s_style == ENDNODE )// 終點{end_node = &(map_maze[i][j]);}printf("%d ", maze[i][j]);}printf("\n");}// 以下使用A*演算法得到路徑// open_table[open_node_count++] = start_node;// 起始點增加open表start_node->s_is_in_opentable = 1;// 增加open表start_node->s_g = 0;start_node->s_h = abs(end_node->s_x - start_node->s_x) + abs(end_node->s_y - start_node->s_y);start_node->s_parent = NULL;if ( start_node->s_x == end_node->s_x && start_node->s_y == end_node->s_y ){printf("起點==終點!\n");return 0;}is_found = 0;while( 1 ){curr_node = open_table[0];// open表的第一個點一定是f值最小的點(通過堆排序得到的)open_table[0] = open_table[--open_node_count];// 最後一個點放到第一個點。然後進行堆調整adjust_heap( 0 );// 調整堆close_table[close_node_count++] = curr_node;// 當前點增加close表curr_node->s_is_in_closetable = 1;// 已經在close表中了if ( curr_node->s_x == end_node->s_x && curr_node->s_y == end_node->s_y )// 終點在close中。結束{is_found = 1;break;}get_neighbors( curr_node, end_node );// 對鄰居的處理if ( open_node_count == 0 )// 沒有路徑到達{is_found = 0;break;}}if ( is_found ){printf("Road founded!\n");printf("Start node: (%d,%d)\n", start_node->s_x, start_node->s_y);printf("End node: (%d,%d)\n", end_node->s_x, end_node->s_y);curr_node = end_node;while( curr_node ){path_stack[++top] = curr_node;curr_node = curr_node->s_parent;}while( top > 0 )// 以下是輸出路徑看看~{if ( top > 0 ){printf("(%d,%d)-->", path_stack[top]->s_x, path_stack[top--]->s_y);}else{printf("(%d,%d)", path_stack[top]->s_x, path_stack[top--]->s_y);}}printf("(%d,%d)", path_stack[top]->s_x, path_stack[top]->s_y);}else{printf("麼有找到路徑");}puts("");return 0;}

執行結果:



和其它演算法的關係


A*演算法,每次從OPENSET中選擇 f(n) 最小的節點將其增加CLOESEDSET中。同一時候擴充相鄰節點。可把OPENSET看成一個優先隊列。key值為 f(n),優先順序最高的先出。
Dijkstra演算法,每次從OPENSET中選擇 g(n) 最小的節點將其增加CLOSEDSET中,同一時候擴充相鄰節點。可把OPENSET看成一個優先隊列,key值為 g(n),優先順序最高的先出。


DFS演算法。每次從OPENSET中選擇最晚被增加的節點將其增加CLOSEDSET中,同一時候擴充相鄰節點,可把OPENSET看成一個棧,後進先出。


BFS演算法,每次從OPENGSET中選擇最早被增加的節點將其增加CLOSEDSET中,同一時候擴充相鄰節點。可把OPENSET看成一個隊列,先進先出。


參考

A* Pathfinding for Beginners - http://www.gamedev.net/page/resources/_/technical/artificial-intelligence/a-pathfinding-for-beginners-r2003

A*路徑搜尋演算法 - http://blog.csdn.net/luckyxiaoqiang/article/details/6996963


資源

A*,Dijkstra,BFS路徑搜尋演算法示範程式 - http://download.csdn.net/detail/walkinginthewind/3822153

A*尋路演算法

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.