啟發式搜索(heuristic search)———A*算法


 

 

 在寬度優先和深度優先搜索里面,我們都是根據搜索的順序依次進行搜索,可以稱為盲目搜索,搜索效率非常低。

而啟發式搜索則大大提高了搜索效率,由這兩張圖可以看出它們的差別:

(左圖類似與盲搜,右圖為啟發式搜索)(圖片來源)  

 

 

很明顯啟發式的搜索效率遠遠大於盲搜。

什么是啟發式搜索(heuristic  search)

  利用當前與問題有關的信息作為啟發式信息,這些信息是能夠提升查找效率以及減少查找次數的。

如何使用這些信息,我們定義了一個估價函數 h(x) 。h(x)是對當前狀態x的一個估計,表示 x狀態到目標狀態的距離。

有:1、h(x) >= 0 ;  2、h(x)越小表示 x 越接近目標狀態; 3、如果 h(x) ==0 ,說明達到目標狀態。

與問題相關的啟發式信息都被計算為一定的 h(x) 的值,引入到搜索過程中。

  然而,有了啟發式信息還不行,還需要起始狀態到 x 狀態所花的代價,我們稱為 g(x) 。比如在走迷宮問題、八數碼問題,我們的 g(x) 就是從起點到 x 位置花的步數 ,h(x) 就是與目標狀態的曼哈頓距離或者相差的數目;在最短路徑中,我們的 g(x) 就是到 x 點的權值,h(x)  就是 x 點到目標結點的最短路或直線距離。

  現在,從 h(x) 和 g(x) 的定義中不能看出,假如我們搜索依據為 F(x) 函數。

  當 F(x) = g(x) 的時候就是一個等代價搜索,完全是按照花了多少代價去搜索。比如 bfs,我們每次都是從離得近的層開始搜索,一層一層搜 ;以及dijkstra算法,也是依據每條邊的代價開始選擇搜索方向。 

  當F(x) = h(x) 的時候就相當於一個貪婪優先搜索。每次都是向最靠近目標的狀態靠近。

  人們發現,等代價搜索雖然具有完備性,能找到最優解,但是效率太低。貪婪優先搜索不具有完備性,不一定能找到解,最壞的情況下類似於dfs。

  這時候,有人提出了A算法。令F(x) = g(x) + h(x) 。(這里的 h(x) 沒有限制。雖然提高了算法效率,但是不能保證找到最優解,不適合的 h(x)定義會導致算法找不到解。不具有完備性和最優性

  幾年后有人提出了 A*算法。該算法僅僅對A算法進行了小小的修改。並證明了當估價函數滿足一定條件,算法一定能找到最優解。估價函數滿足一定條件的算法稱為A*算法。

它的限制條件是 F(x) = g(x) + h(x) 。 代價函數g(x) >0 ;h(x) 的值不大於x到目標的實際代價 h*(x) 。即定義的 h(x) 是可納的,是樂觀的

怎么理解第二個條件呢?

  打個比方:你要從x走到目的地,那么 h(x) 就是你感覺或者目測大概要走的距離,h*(x) 則是你到達目的地后,發現你實際走了的距離。你預想的距離一定是比實際距離短,或者剛好等於實際距離的值。這樣我們稱你的 h(x) 是可納的,是樂觀的。

 不同的估價函數對算法的效率可能產生極大的影響。尤其是 h(x) 的選定,比如在接下來的八數碼問題中,我們選擇了曼哈頓距離之和作為 h(x) ,你也可以選擇相差的格子作為 h(x),只不過搜索的次數會不同。當 h(x) 越接近 h*(x) ,那么擴展的結點越少!

  那么A*算法的具體實現是怎么樣的呢?

1、將源點加入open表 2while(OPEN!=NULL) { 從OPEN表中取f(n)最小的節點n; if(n節點==目標節點) break; for(當前節點n的每個子節點X) { 計算f(X); if(XinOPEN) if(新的f(X)<OPEN中的f(X)) { 把n設置為X的父親; 更新OPEN表中的f(n); //不要求記錄路徑的話可以直接加入open表,舊的X結點是不可能比新的先出隊
 } if(XinCLOSE) continue; if(Xnotinboth) { 把n設置為X的父親; 求f(X); 並將X插入OPEN表中; } }//endfor
 將n節點插入CLOSE表中; 按照f(n)將OPEN表中的節點排序;//實際上是比較OPEN表內節點f的大小,從最小路徑的節點向下進行。
}//endwhile(OPEN!=NULL)

3、保存路徑,從目標點出發,按照父節點指針遍歷,直到找到起點。

 

以八數碼問題為例:

我們從1、僅考慮代價函數; 2、僅考慮貪婪優先; 3、A*算法。

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 struct Maze{
  4     char s[3][3];
  5     int i,j,fx,gx;
  6     bool operator < (const Maze &a )const{
  7         return fx>a.fx;
  8     }
  9 } c;
 10 int fx[4][2]={{-1,0},{1,0},{0,1},{0,-1}};
 11 map<char ,Maze > mp;
 12 int T;
 13 int get_hx(char s[3][3]){
 14     int hx=0;
 15     for(int i=0;i<3;i++){
 16         for(int j=0;j<3;j++){
 17             hx+=abs(mp[s[i][j]].i-i)+abs(mp[s[i][j]].j-j);
 18         }
 19     }
 20     return (int)hx;
 21 }
 22 void pr(char s[3][3]){
 23     cout<<"step: "<<T++<<endl;
 24     for(int i=0;i<3;i++){
 25         for(int j=0;j<3;j++)
 26             cout<<s[i][j];
 27         cout<<endl;
 28     }
 29     cout<<endl;
 30 }
 31 int key(char s[3][3]){
 32     int ans=0;
 33     for(int i=0;i<3;i++)
 34         for(int j=0;j<3;j++)
 35             ans=ans*10+(s[i][j]-'0');
 36     return ans;
 37 }
 38 void BFS(){
 39     T=0;
 40     map<int ,bool >flag;
 41     queue < Maze > q;
 42     q.push(c);
 43     flag[key(c.s)]=1;
 44     while(!q.empty()){
 45         Maze now=q.front();
 46         q.pop();
 47         pr(now.s);
 48         if(get_hx(now.s)==0){
 49             break;
 50         }
 51         for(int i=0;i<4;i++){
 52             int x,y;
 53             x=now.i+fx[i][0];
 54             y=now.j+fx[i][1];
 55             if(!(x>=0&&x<3&&y>=0&&y<3)) continue;
 56             Maze tmp=now;
 57             tmp.s[now.i][now.j]=tmp.s[x][y];
 58             tmp.s[x][y]='0';
 59             tmp.i=x ; tmp.j=y ;
 60             tmp.fx++;
 61             if(!flag[key(tmp.s)]){
 62                 q.push(tmp);
 63                 flag[key(tmp.s)]=1;
 64             }
 65         }
 66     }
 67 }
 68 void Greedy_best_first_search(){
 69     T=0;
 70     priority_queue< Maze > q ;
 71     map<int ,int >flag;
 72     c.fx=get_hx(c.s);
 73     q.push(c);
 74     flag[key(c.s)]=1;
 75     while(!q.empty()){
 76         Maze now=q.top();
 77         q.pop();
 78         pr(now.s);
 79         if(get_hx(now.s)==0){
 80             break;
 81         }
 82         for(int i=0;i<4;i++){
 83             int x,y;
 84             x=now.i+fx[i][0];
 85             y=now.j+fx[i][1];
 86             if(!(x>=0&&x<3&&y>=0&&y<3)) continue;
 87             Maze tmp=now;
 88             tmp.s[now.i][now.j]=tmp.s[x][y];
 89             tmp.s[x][y]='0';
 90             tmp.i=x ; tmp.j=y ;
 91             tmp.fx=get_hx(tmp.s);
 92             if(!flag[key(tmp.s)]){
 93                 q.push(tmp);
 94                 flag[key(tmp.s)]=1;
 95             }
 96         }
 97     }
 98 }
 99 void A_star(){
100     T=0;
101     priority_queue< Maze > q ;
102     map<int ,int >flag;
103     c.gx=0;
104     c.fx=get_hx(c.s)+c.gx;
105     q.push(c);
106     while(!q.empty()){
107         Maze now=q.top();
108         q.pop();
109         flag[key(now.s)]=now.fx;
110         pr(now.s);
111         if(get_hx(now.s)==0){
112             break;
113         }
114         for(int i=0;i<4;i++){
115             int x,y;
116             x=now.i+fx[i][0];
117             y=now.j+fx[i][1];
118             if(!(x>=0&&x<3&&y>=0&&y<3)) continue;
119             Maze tmp=now;
120             tmp.s[now.i][now.j]=tmp.s[x][y];
121             tmp.s[x][y]='0';
122             tmp.i=x ; tmp.j=y ;
123             tmp.gx++;
124             tmp.fx=get_hx(tmp.s)+tmp.gx;
125             if(!flag[key(tmp.s)]){
126                 q.push(tmp);
127             }else if(flag[key(tmp.s)]>tmp.fx){
128                 flag[key(tmp.s)]=0;
129                 q.push(tmp);
130             }
131         }
132     }
133 }
134 int main(){
135     mp['1'].i=0;mp['1'].j=0;
136     mp['2'].i=0;mp['2'].j=1;
137     mp['3'].i=0;mp['3'].j=2;
138     mp['4'].i=1;mp['4'].j=2;
139     mp['5'].i=2;mp['5'].j=2;
140     mp['6'].i=2;mp['6'].j=1;
141     mp['7'].i=2;mp['7'].j=0;
142     mp['8'].i=1;mp['8'].j=0;
143     mp['0'].i=1;mp['0'].j=1;
144     for(int i=0;i<3;i++){
145         for(int j=0;j<3;j++){
146             cin>>c.s[i][j];
147         }
148         char x=getchar();
149     }
150     cin>>c.i>>c.j;
151     c.fx=0;
152     cout<<"八數碼問題 BFS 解法(即僅以當前代價 g(x)搜索): "<<endl;
153     BFS();
154     cout<<"八數碼問題 Greedy_best_first_search 解法(即僅以估計函數 h(x)搜索): "<<endl;
155     Greedy_best_first_search();
156     cout<<"八數碼問題 A* 解法: "<<endl;
157     A_star();
158     return 0;
159 }
160 /*
161 283
162 164
163 705
164 2 1
165 */

結果顯示:

1、僅考慮代價函數:36步。

2、僅考慮貪婪優先:5步。

3、A*算法:5步。

明顯,在引入了啟發式信息后,大大的提高了搜索的效率。

 

引申問題: 第 k 短路問題。

思路: 先從終點求出最短路,作為 h(x) 。然后維護優先隊列,維護 F(x) 最小,第一次出來的終點是最短路,終點第二次出來的是次短路……

求第k短路時,A*算法優化的是查找的次數,可以理解為剪枝,更快速的找到最短路,次短路……
其他操作和正常的求最短路沒有什么區別,找到終點第k次出隊的值,就是第k短路。

(可能你會說在無向圖中存在有回頭路,沒錯,有可能次短路只是最短路走了一次回頭路,但這確實也是一條次短路)。

 


免責聲明!

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



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