题目2:隐式图的搜索问题(A*算法解决八数码)
數據結構課程實踐系列
題目1:學生成績檔案管理系統(tǒng)(實驗準備)
題目2:隱式圖的搜索問題(A*算法解決八數碼)
題目3:文本文件單詞的檢索與計數(實驗準備)
文章目錄
- 數據結構課程實踐系列
- 題目1:學生成績檔案管理系統(tǒng)(實驗準備)
- 題目2:隱式圖的搜索問題(A*算法解決八數碼)
- 題目3:文本文件單詞的檢索與計數(實驗準備)
- 聲明
- 題目要求
- 何為八數碼?
- 狀態(tài)如何表示
- 所需知識
- 導出所需知識
- 優(yōu)先隊列BFS算法缺陷
- A*搜索算法
- 總結:
- 實際操作
- 搜索過程描述
- 啟發(fā)式策略工作工程:紅圈數字表示擴展順序
- 代碼實現
- Map+BFS+A*
- Hash+BFS+A*
- GAMEOVER
聲明
題目要求
看過這次實驗要求之后
總結:利用A*來解決八數碼問題,狀態(tài)很好找,每次移動空格就會形成一種新的狀態(tài),
何為八數碼?
八數碼游戲包括一個3X3的棋盤,棋盤上擺放著8個數字的棋子,留下一個空位。與空位相鄰的棋子可以滑動到空位中。游戲的目的是要達到一個特定的目標狀態(tài)。標注的形式化如下(舉例):
| 1 | 8 | 4 |
| 7 | 6 | 5 |
(初始狀態(tài))
| 8 | 4 | |
| 7 | 6 | 5 |
(目標狀態(tài))
狀態(tài)如何表示
所需知識
優(yōu)先隊列BFS算法,因為A*算法就是帶有估價函數的優(yōu)先隊列BFS。
導出所需知識
優(yōu)先隊列BFS算法缺陷
該算法維護了一個優(yōu)先隊列(二叉堆),不斷從堆中取出“當前代價最小”的狀態(tài)(堆頂)去進行擴展。但是它得到只是初態(tài)到該狀態(tài)的最小代價(并沒有去考慮從該狀態(tài)出發(fā)到目標狀態(tài)的情況)
舉例說明:
如果給定一個“目標狀態(tài)”,需要求出從初態(tài)到目標狀態(tài)的最小代價,優(yōu)先隊列BFS顯然不行。因為一個狀態(tài)的當前代價最小,只能說明從起始狀態(tài)到該狀態(tài)的代價很小,而在未來的探索中,從該狀態(tài)到目標狀態(tài)可能會花費很大的代價;另外一些狀態(tài)雖然當前代價略大,但是未來到目標狀態(tài)的代價可能會很小,于是從起始狀態(tài)到目標狀態(tài)的總代價反而更優(yōu)。
A*搜索算法
于是,我們?yōu)榱私鉀Q上面的問題:可以對未來可能產生的代碼進行預估。詳細地講,我們去設計一個**“估價函數”**,以任意狀態(tài)為輸入,計算出從該狀態(tài)到目標狀態(tài)所需代價的估計值。在搜索之中,仍然維護了一個堆,不斷從堆中取出“當前代價+未來估價”最小的狀態(tài)來進行擴展。
為了保證第一次從堆中取出目標狀態(tài)時得到的就是最優(yōu)解,我們設計的估價函數需要滿足一個基本準則:
設當前狀態(tài)state到目標狀態(tài)所需代價的估計值為f(state);設在未來的探索中,實際求出的從當前狀態(tài)state到目標狀態(tài)的最小代價為g(state)
對于任意的state,應該有f(state)<=g(state)
也就是說,估價函數的估值不能大于未來實際代價,估價比實際代價更優(yōu)。估價函數f(n)=g(n)+h(n)
總結:
這種帶有估價函數的優(yōu)先隊列BFS就成為A* 算法。只要保證對于任意狀態(tài)state,都有f(state)<=g(state),A* 算法就一定能在目標狀態(tài)第一次從堆中被取出時得到最優(yōu)解,并且在搜索過程中每個狀態(tài)只需要擴展一次(之后再被取出就可以直接忽略 )。估價f(state)越準確,越接近g(state),A*算法的效率越高。如果估價始終為0,就等于普通的優(yōu)先隊列BFS。
實際操作
搜索過程描述
A算法又稱為啟發(fā)式搜索算法。對啟發(fā)式搜索算法,又可根據搜索過程中選擇擴展節(jié)點的范圍,將其分為全局擇優(yōu)搜索算法和局部擇優(yōu)搜索算法。
在全局擇優(yōu)搜索中,每當需要擴展節(jié)點時,總是從 Open 表的所有節(jié)點中選擇一個估價函數值最小的節(jié)點進行擴展。其搜索過程可能描述如下:
這里采用的啟發(fā)式策略為:f(n) = g(n) + h(n),其中g(n)為從初始節(jié)點到當前節(jié)點的步數(層數),h(n)為 當前節(jié)點 “不在位 ”的方塊數(也就是說不在位的方塊數越少,那么臨目標狀態(tài)越近)例如下圖中的h(n)=5,有的講解的是不包含空格,我這里是包含了的,經測試只要前后標準一致,包不包含空格都一樣。
g(n)為已經消耗的實際代價,即已經走了的步數。
h(n)為預測路徑,即還有幾個數字待走。
h(n)=5
啟發(fā)式策略工作工程:紅圈數字表示擴展順序
代碼實現
Map+BFS+A*
#include<cstdio> #include<queue> #include<map> using namespace std; char arr[10], brr[10] = "123804765"; struct node {int num, step, cost, zeroPos;bool operator<(const node& a)const {return cost > a.cost;}node(int n, int s, int p) {num = n, step = s, zeroPos = p;setCost();}void setCost() {char a[10];int c = 0;sprintf(a, "%09d", num);for (int i = 0; i < 9; i++)if (a[i] != brr[i])c++;cost = c + step;} }; int des = 123804765; int changeId[9][4] = { {-1,-1,3,1},{-1,0,4,2},{-1,1,5,-1},{0,-1,6,4},{1,3,7,5},{2,4,8,-1},{3,-1,-1,7},{4,6,-1,8},{5,7,-1,-1} }; map<int, bool>mymap; priority_queue<node> que;//優(yōu)先級隊列 void swap(char* ch, int a, int b) { char c = ch[a]; ch[a] = ch[b]; ch[b] = c; } int bfsHash(int start, int zeroPos) {char temp[10];node tempN(start, 0, zeroPos);//創(chuàng)建一個節(jié)點 que.push(tempN);//壓入優(yōu)先級隊列 mymap[start] = 1;//標記開始節(jié)點被訪問過 while (!que.empty()) {tempN = que.top();que.pop();//彈出一個節(jié)點 sprintf(temp, "%09d", tempN.num);int pos = tempN.zeroPos, k;for (int i = 0; i < 4; i++) {if (changeId[pos][i] != -1) {swap(temp, pos, changeId[pos][i]);sscanf(temp, "%d", &k);if (k == des)return tempN.step + 1;if (mymap.count(k) == 0) {node tempM(k, tempN.step + 1, changeId[pos][i]);que.push(tempM);//創(chuàng)建一個新節(jié)點并壓入隊列 mymap[k] = 1;}swap(temp, pos, changeId[pos][i]);}}} } int main() {int n, k, b;scanf("%s", arr);for (k = 0; k < 9; k++)if (arr[k] == '0')break;sscanf(arr, "%d", &n);b = bfsHash(n, k);printf("%d步即可變換完成", b);return 0; }
所得結果跟我們上圖分析結果一樣,
這個數組也就代表九個位置中四個方向的可置換的數組元素下標(-1表示該方向不能交換),注意:這里的四個方向的話,是逆時針,(上左下右)
Hash+BFS+A*
#include<cstdio> #include<queue> using namespace std; char arr[10],brr[10]="123804765"; struct node{int num,step,cost,zeroPos;bool operator<(const node &a)const{return cost>a.cost;}node(int n,int s,int p){num=n,step=s,zeroPos=p;setCost();}void setCost(){char a[10];int c=0;sprintf(a,"%09d",num);for(int i=0;i<9;i++)if(a[i]!=brr[i])c++;cost=c+step;} }; int changeId[9][4]={{-1,-1,3,1},{-1,0,4,2},{-1,1,5,-1},{0,-1,6,4},{1,3,7,5},{2,4,8,-1},{3,-1,-1,7},{4,6,-1,8},{5,7,-1,-1}}; const int M=2E+6,N=1000003;//362897; int hashTable[M];//hashtable中key為hash值,value為被hash的值 int Next[M],des=123804765;//next表示如果在某個位置沖突,則沖突位置存到hashtable[next[i]] priority_queue<node> que;//優(yōu)先級隊列 int Hash(int n){return n%N; } bool tryInsert(int n){int hashValue=Hash(n);while(Next[hashValue]){//如果被hash出來的值得next不為0則向下查找 if(hashTable[hashValue]==n)//如果發(fā)現已經在hashtable中則返回false return false; hashValue=Next[hashValue];}//循環(huán)結束hashValue指向最后一個hash值相同的節(jié)點 if(hashTable[hashValue]==n)//再判斷一遍 return false; int j=N-1;//在N后面找空余空間,避免占用其他hash值得空間造成沖突 while(hashTable[++j]);//向后找一個沒用到的空間 Next[hashValue]=j;hashTable[j]=n;return true; } void swap(char* ch,int a,int b){char c=ch[a];ch[a]=ch[b];ch[b]=c;}int bfsHash(int start,int zeroPos){char temp[10];node tempN(start,0,zeroPos); que.push(tempN);while(!que.empty()){tempN=que.top();que.pop();sprintf(temp,"%09d",tempN.num);int pos=tempN.zeroPos,k;for(int i=0;i<4;i++){if(changeId[pos][i]!=-1){swap(temp,pos,changeId[pos][i]);sscanf(temp,"%d",&k);if(k==des)return tempN.step+1;if(tryInsert(k)){//插入新狀態(tài)成功,則說明新狀態(tài)沒有被訪問過 node tempM(k,tempN.step+1,changeId[pos][i]);que.push(tempM);}swap(temp,pos,changeId[pos][i]);}}} } int main(){int n,k,b=0;scanf("%s",arr);for(k=0;k<9;k++)if(arr[k]=='0')break;sscanf(arr,"%d",&n);if(n!=des)b=bfsHash(n,k);printf("%d步即可變換完成",b); return 0; }GAMEOVER
總結
以上是生活随笔為你收集整理的题目2:隐式图的搜索问题(A*算法解决八数码)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c++11中智能指针的原理,使用,实现
- 下一篇: 题目3:文本文件单词的检索与计数(实验准