八数码 poj 1077 广搜 A* IDA*
經(jīng)典的八數(shù)碼問(wèn)題,有人說(shuō)不做此題人生不完整,哈哈。
狀態(tài)總數(shù)是9! = 362880 種,不算太多,可以滿足廣搜和A*對(duì)于空間的需求。
狀態(tài)可以每次都動(dòng)態(tài)生成,也可以生成一次存儲(chǔ)起來(lái),我用的動(dòng)態(tài)生成,《組合數(shù)學(xué)》書上有一種生成排列的方法叫做"序數(shù)法",我看了一會(huì)書,把由排列到序數(shù),和由序數(shù)到排列的兩個(gè)函數(shù)寫了出來(lái),就是代碼中的int order(const char *s, int n) 和void get_node(int num, node &tmp)兩個(gè)函數(shù)。
啟發(fā)函數(shù),用的是除空格外的八個(gè)數(shù)字到正確位置的網(wǎng)格距離。
幾種方法的比較:廣搜,效率最低,500ms;A*,32ms,已經(jīng)比較高效了;IDA*, 0ms,空間也減少許多。A*為判重付出了巨大代價(jià),時(shí)間 and 空間,uva上還有一個(gè)15數(shù)碼的題,用A*肯定會(huì)爆空間的。IDA*不記錄已經(jīng)走過(guò)的路徑,所以省去了空間,也省去了判斷重復(fù)的步驟,但是會(huì)出現(xiàn)重復(fù)計(jì)算。
?
廣搜:
?
// BFS #include<iostream> #include<cstdio> #include<queue> using namespace std;/* 把1..n的排列映射為數(shù)字 0..(n!-1) */ int fac[] = { 1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880 };//... int order(const char *s, int n) {int i, j, temp, num;num = 0;for (i = 0; i < n-1; i++) {temp = 0;for (j = i + 1; j < n; j++) {if (s[j] < s[i])temp++;}num += fac[s[i] -1] * temp;}return num; }bool is_equal(const char *b1, const char *b2){for(int i=0; i<9; i++)if(b1[i] != b2[i])return false;return true; }//hash struct node{char board[9];char space;//空格所在位置 };const int TABLE_SIZE = 362880;int hash(const char *cur){return order(cur, 9); }/* 整數(shù)映射成排列 */ void get_node(int num, node &tmp) {int n=9;int a[9]; //求逆序數(shù) for (int i = 2; i <= n; ++i) {a[i - 1] = num % i;num = num / i;tmp.board[i - 1] = 0;//初始化 }tmp.board[0] = 0;int rn, i;for (int k = n; k >= 2; k--) {rn = 0;for (i = n - 1; i >= 0; --i) {if (tmp.board[i] != 0)continue;if (rn == a[k - 1])break;++rn;}tmp.board[i] = k;}for (i = 0; i < n; ++i)if (tmp.board[i] == 0) {tmp.board[i] = 1;break;}tmp.space = n - a[n-1] -1; }char visited[TABLE_SIZE]; int parent[TABLE_SIZE]; char move[TABLE_SIZE]; int step[4][2] = {{-1, 0},{1, 0}, {0, -1}, {0, 1}};//u, d, l, r void BFS(const node & start){int x, y, k, a, b;int u, v;for(k=0; k<TABLE_SIZE; ++k)visited[k] = 0;u = hash(start.board);parent[u] = -1;visited[u] = 1;queue<int> que;que.push(u);node tmp, cur;while(!que.empty()){u = que.front();que.pop();get_node(u, cur);k = cur.space;x = k / 3;y = k % 3;for(int i=0; i<4; ++i){a = x + step[i][0];b = y + step[i][1];if(0<=a && a<=2 && 0<=b && b<=2){tmp = cur;tmp.space = a*3 + b;swap(tmp.board[k], tmp.board[tmp.space]);v = hash(tmp.board);if(visited[v] != 1){move[v] = i;visited[v] = 1;parent[v] = u;if(v == 0) //目標(biāo)結(jié)點(diǎn)hash值為0 return;que.push(v);}}}} }void print_path(){int n, u;char path[1000];n = 1;path[0] = move[0];u = parent[0];while(parent[u] != -1){path[n] = move[u];++n;u = parent[u];}for(int i=n-1; i>=0; --i){if(path[i] == 0)printf("u");else if(path[i] == 1)printf("d");else if(path[i] == 2)printf("l");elseprintf("r");} }int main(){freopen("in", "r", stdin);node start;char c;for(int i=0; i<9; ++i){cin>>c;if(c == 'x'){start.board[i] = 9;start.space = i;}elsestart.board[i] = c - '0';}BFS(start);if(visited[0] == 1)print_path();elseprintf("unsolvable");return 0; }?
?
A*算法:
代碼中對(duì)priority_queue<>模板的使用還是很有技巧性的,通過(guò)push一個(gè)最小的,再把它pop出來(lái)就解決了由于更改造成不一致性。前面這種做法是錯(cuò)誤的,多謝一位朋友的提醒,這種方式確實(shí)不能保持堆的性質(zhì)。不過(guò)我們可以采用冗余的辦法,直接插入新值,這就不會(huì)破壞堆的性質(zhì)了。
修改過(guò)的代碼:
// A* #include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<queue> using namespace std;/* 把1..n的排列映射為數(shù)字 0..(n!-1) */ int fac[] = { 1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880 };//... int order(const char *s, int n) {int i, j, temp, num;num = 0;for (i = 0; i < n-1; i++) {temp = 0;for (j = i + 1; j < n; j++) {if (s[j] < s[i])temp++;}num += fac[s[i] -1] * temp;}return num; }bool is_equal(const char *b1, const char *b2){for(int i=0; i<9; i++)if(b1[i] != b2[i])return false;return true; }//hash struct node{char board[9];char space;//空格所在位置 };const int TABLE_SIZE = 362880;int hash(const char *cur){return order(cur, 9); }/* 整數(shù)映射成排列 */ void get_node(int num, node &tmp) {int n=9;int a[9]; //求逆序數(shù) for (int i = 2; i <= n; ++i) {a[i - 1] = num % i;num = num / i;tmp.board[i - 1] = 0;//初始化 }tmp.board[0] = 0;int rn, i;for (int k = n; k >= 2; k--) {rn = 0;for (i = n - 1; i >= 0; --i) {if (tmp.board[i] != 0)continue;if (rn == a[k - 1])break;++rn;}tmp.board[i] = k;}for (i = 0; i < n; ++i)if (tmp.board[i] == 0) {tmp.board[i] = 1;break;}tmp.space = n - a[n-1] -1; }//啟發(fā)函數(shù): 除去x之外到目標(biāo)的網(wǎng)格距離和 int goal_state[9][2] = {{0,0}, {0,1}, {0,2},{1,0}, {1,1}, {1,2}, {2,0}, {2,1}, {2,2}}; int h(const char *board){int k;int hv = 0;for(int i=0; i<3; ++i)for(int j=0; j<3; ++j){k = i*3+j;if(board[k] != 9){hv += abs(i - goal_state[board[k]-1][0]) +abs(j - goal_state[board[k] -1][1]);}}return hv; }int f[TABLE_SIZE], d[TABLE_SIZE];//估計(jì)函數(shù)和深度//優(yōu)先隊(duì)列的比較對(duì)象 struct cmp{bool operator () (int u, int v){return f[u] > f[v];} }; char color[TABLE_SIZE];//0, 未訪問(wèn);1, 在隊(duì)列中,2, closed int parent[TABLE_SIZE]; char move[TABLE_SIZE]; int step[4][2] = {{-1, 0},{1, 0}, {0, -1}, {0, 1}};//u, d, l, r void A_star(const node & start){int x, y, k, a, b;int u, v;priority_queue<int, vector<int>, cmp> open;memset(color, 0, sizeof(char) * TABLE_SIZE);u = hash(start.board);parent[u] = -1;d[u] = 0;f[u] = h(start.board);open.push(u);color[u] = 1;node tmp, cur;while(!open.empty()){u = open.top();if(u == 0)return;open.pop();get_node(u, cur);k = cur.space;x = k / 3;y = k % 3;for(int i=0; i<4; ++i){a = x + step[i][0];b = y + step[i][1];if(0<=a && a<=2 && 0<=b && b<=2){tmp = cur;tmp.space = a*3 + b;swap(tmp.board[k], tmp.board[tmp.space]);v = hash(tmp.board);if(color[v] == 1 && (d[u] + 1) < d[v]){//v in open move[v] = i;f[v] = f[v] - d[v] + d[u] + 1;//h[v]已經(jīng)求過(guò) d[v] = d[u] + 1;parent[v] = u;//直接插入新值, 有冗余,但不會(huì)錯(cuò) open.push(v);}else if(color[v] == 2 && (d[u]+1)<d[v]){//v in closed move[v] = i;f[v] = f[v] - d[v] + d[u] + 1;//h[v]已經(jīng)求過(guò) d[v] = d[u] + 1;parent[v] = u;open.push(v);color[v] = 1;}else if(color[v] == 0){move[v] = i;d[v] = d[u] + 1;f[v] = d[v] + h(tmp.board);parent[v] = u;open.push(v);color[v] = 1;}}}color[u] = 2; // } }void print_path(){int n, u;char path[1000];n = 1;path[0] = move[0];u = parent[0];while(parent[u] != -1){path[n] = move[u];++n;u = parent[u];}for(int i=n-1; i>=0; --i){if(path[i] == 0)printf("u");else if(path[i] == 1)printf("d");else if(path[i] == 2)printf("l");elseprintf("r");} }int main(){//freopen("in", "r", stdin); node start;char c;for(int i=0; i<9; ++i){cin>>c;if(c == 'x'){start.board[i] = 9;start.space = i;}elsestart.board[i] = c - '0';}A_star(start);if(color[0] != 0)print_path();elseprintf("unsolvable");return 0; }
?
?
IDA*: // IDA* #include<iostream> #include<cstdio> #include<cstdlib> using namespace std;#define SIZE 3char board[SIZE][SIZE];//啟發(fā)函數(shù): 除去x之外到目標(biāo)的網(wǎng)格距離和 int goal_state[9][2] = {{0,0}, {0,1}, {0,2},{1,0}, {1,1}, {1,2}, {2,0}, {2,1}, {2,2}}; int h(char board[][SIZE]){int cost = 0;for(int i=0; i<SIZE; ++i)for(int j=0; j<SIZE; ++j){if(board[i][j] != SIZE*SIZE){cost += abs(i - goal_state[board[i][j]-1][0]) +abs(j - goal_state[board[i][j]-1][1]);}}return cost; }int step[4][2] = {{-1, 0}, {0, -1}, {0, 1}, {1, 0}};//u, l, r, d char op[4] = {'u', 'l', 'r', 'd'};char solution[1000]; int bound; //上界 bool ans; //是否找到答案 int DFS(int x, int y, int dv, char pre_move){// 返回next_bound int hv = h(board);if(hv + dv > bound)return dv + hv;if(hv == 0){ans = true;return dv;}int next_bound = 1e9;for(int i=0; i<4; ++i){if(i + pre_move == 3)//與上一步相反的移動(dòng) continue;int nx = x + step[i][0];int ny = y + step[i][1];if(0<=nx && nx<SIZE && 0<=ny && ny<SIZE){solution[dv] = i;swap(board[x][y], board[nx][ny]);int new_bound = DFS(nx, ny, dv+1, i);if(ans)return new_bound;next_bound = min(next_bound, new_bound);swap(board[x][y], board[nx][ny]);}}return next_bound; }void IDA_star(int sx, int sy){ans = false;bound = h(board);//初始代價(jià) while(!ans && bound <= 100)//上限 bound = DFS(sx, sy, 0, -10); }int main(){freopen("in", "r", stdin);int sx, sy;//起始位置 char c;for(int i=0; i<SIZE; ++i)for(int j=0; j<SIZE; ++j){cin>>c;if(c == 'x'){board[i][j] = SIZE * SIZE;sx = i;sy = j;}elseboard[i][j] = c - '0';}IDA_star(sx, sy);if(ans){for(int i=0; i<bound; ++i)cout<<op[solution[i]];}elsecout<<"unsolvable";return 0; }總結(jié)
以上是生活随笔為你收集整理的八数码 poj 1077 广搜 A* IDA*的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 全排列的生成算法
- 下一篇: 算法之排列与组合算法