(皇后移動類)八數碼難題引發的搜索思考及總結

POJ 1077 Eight

The 15-puzzle has been around for over 100 years; even if you don't know it by that name, you've seen it. It is constructed with 15 sliding tiles, each with a number from 1 to 15 on it, and all packed into a 4 by 4 frame with one tile missing. Let's call the missing tile 'x'; the object of the puzzle is to arrange the tiles so that they are ordered as:
 1  2  3  4 
 5  6  7  8 
 9 10 11 12 
13 14 15  x 
where the only legal operation is to exchange 'x' with one of the tiles with which it shares an edge. As an example, the following sequence of moves solves a slightly scrambled puzzle:
 1  2  3  4    1  2  3  4    1  2  3  4    1  2  3  4 
 5  6  7  8    5  6  7  8    5  6  7  8    5  6  7  8 
 9  x 10 12    9 10  x 12    9 10 11 12    9 10 11 12 
13 14 11 15   13 14 11 15   13 14  x 15   13 14 15  x 

           r->           d->           r-> 
The letters in the previous row indicate which neighbor of the 'x' tile is swapped with the 'x' tile at each step; legal values are 'r','l','u' and 'd', for right, left, up, and down, respectively.
Not all puzzles can be solved; in 1870, a man named Sam Loyd was famous for distributing an unsolvable version of the puzzle, and
frustrating many people. In fact, all you have to do to make a regular puzzle into an unsolvable one is to swap two tiles (not counting the missing 'x' tile, of course).
In this problem, you will write a program for solving the less well-known 8-puzzle, composed of tiles on a three by three
arrangement.

Input

You will receive a description of a configuration of the 8 puzzle. The description is just a list of the tiles in their initial positions, with the rows listed from top to bottom, and the tiles listed from left to right within a row, where the tiles are represented by numbers 1 to 8, plus 'x'. For example, this puzzle
 1  2  3 
 x  4  6 
 7  5  8 
is described by this list:
 1 2 3 x 4 6 7 5 8 

Output

You will print to standard output either the word ``unsolvable'', if the puzzle has no solution, or a string consisting entirely of the letters 'r', 'l', 'u' and 'd' that describes a series of moves that produce a solution. The string should include no spaces and start at the beginning of the line.

Sample Input

 2  3  4  1  5  x  7  6  8 

Sample Output

ullddrurdllurdruldr

Source


八數碼難題中文題意

八數碼問題也稱爲九宮問題。在3×3的棋盤上擺有八個棋子,每個棋子上標有1至8的某一數字,不同棋子上標的數字不相同。棋盤上還有一個空格,與空格相鄰的棋子可以移到空格中。給出一個初始狀態和一個目標狀態,求出從初始狀態轉變成目標狀態的移動棋子步數的最少值。

一般的目標狀態是指下面這樣的排列方式。

要求解決的問題是:給出一個初始狀態和一個目標狀態,找出一種從初始轉變成目標狀態的移動棋子步數最少的移動步驟。
所謂問題的一個狀態就是棋子在棋盤上的一種擺法。棋子移動後,狀態就會發生改變。解八數碼問題實際上就是找出從初始狀態到達目標狀態所經過的一系列中間過渡狀態。

一般解決這類問題的辦法爲搜索

1.首先判斷是否有解

核心思想是根據一維狀態的逆序數奇偶性來判斷

將它表徵爲一維狀態(0 1 2 3 4 5 6 7 8),它的逆序數爲0,偶數。考慮數字的移動,左移or右移均不改變其一維狀態,因此逆序數的奇偶性不變。上移or下移時,一維狀態中某一位的數字往前或者往後跳了兩格(+/-2),相應的,逆序數+/-2,依然不改變奇偶性。因此有結論:八數碼問題有解 iff 初始狀態與終止狀態的逆序數奇偶性一致。

所以一個完美的八數碼問題求解,必須先判斷其解是否存在,再行搜索。

八數碼問題有解的條件及其推廣    判斷是否有解這裏面描述的很詳盡

搜索的方法有 DFS, BFS, DBFS, A*等多種

2.雙向廣搜理論上可以減少一半的空間,時間。

1)將 "始, 終" 狀態都入隊列並在相當的標記中爲1,2(加以區分)

2)每次新的狀態的標記都與上次的相同,並判斷若有一個標記走到了另一個標記,結束

3)若要輸出過程,標記變化的地方要單獨輸出

雙向BFS例題

POJ 1915 Knight Moves

題意:

一個N*N的棋牌上,問你中國象棋的馬從一個指定點走到另外一個指定點最少需要多少步.


#include <stdio.h>
#include <stdlib.h>

int vis[305][305], mat[305][305];
int dx[] = {-2, -2, -1, 1, 2, 2, 1, -1};
int dy[] = {-1, 1, 2, 2, 1, -1, -2, -2};
int casenum, nNum, sx, sy, tx, ty, i;

struct point
{
    int x, y;
}cur, next, q[90005]={0};

int IsInBound(int x, int y)
{
    return (x>=0 && y>=0 && x<nNum && y<nNum);
}/* IsInBound */

int Solve()
{
    int rear = -1;
    int front = -1;

    cur.x = sx;
    cur.y = sy;
    vis[sx][sy] = 1; /* 從起始位置開始的探索標記爲 1 */ 
    q[++rear] = cur; /* 起始座標入隊 */ 
    
    next.x = tx;
    next.y = ty;
    vis[tx][ty] = 2;  /* 從終點位置開始的探索標記爲 2 */ 
    q[++rear] = next; /* 終點座標入隊 */ 
    
    while (front < rear)
    {
        cur = q[++front]; /* 隊首節點座標出隊 */
        for (i=0; i<8; ++i)
        {
            next.x = cur.x + dx[i];
            next.y = cur.y + dy[i];
            
            if (!IsInBound(next.x, next.y))
                continue;
                
            if (!vis[next.x][next.y])
            {
                vis[next.x][next.y] = vis[cur.x][cur.y];     /* 設爲與當前探索路徑相同的標記 */
                mat[next.x][next.y] = mat[cur.x][cur.y] + 1; /* 記錄步數 */ 
                q[++rear] = next; /* 當前合法座標位置入隊 */ 
            }
            else if (vis[cur.x][cur.y] != vis[next.x][next.y])
            {   /* 說明從起點出發的探索與從終點出發的探索重合 */ 
                return mat[cur.x][cur.y]+mat[next.x][next.y]+1;//步數
            }
        }/* End of For */
    }/* End of While */
}/* Solve */

int main()
{
    scanf("%d", &casenum);
    while (casenum--)
    {
        memset(vis, 0, sizeof(vis));
        memset(mat, 0, sizeof(mat));
        
        scanf("%d", &nNum);
        scanf("%d %d", &sx, &sy);
        scanf("%d %d", &tx, &ty);
        
        if (sx==tx && sy==ty)
        {
            printf("0\n");
        }
        else
        {
            printf("%d\n", Solve());
        }    
    }/* End of While */
    return 0;
}
POJ 1077 Eight 八數碼問題解法分析

1、(反向)BFS + hash( cantor展開 )

因爲狀態總數不多,只有不到40萬種,因此可以從目標節點開始,進行一遍徹底的廣搜,找出全部有解狀態到目標節點的路徑。

2、雙向廣度優先搜索(DBFS)

DBFS算法:從兩個方向以廣度優先的順序同時擴展,一個是從起始節點開始擴展,另一個是從目的節點擴展,直到一個擴展隊列中出現另外一個隊列中已經擴展的節點,也就相當於兩個擴展方向出現了交點,那麼可以認爲我們找到了一條路徑。

3、A*算法+hash(康託)

A*算法入門:http://www.policyalmanac.org/games/Chine%20Translation%20-%20For%20beginners.html

A*算法詳細解析請看:http://blog.csdn.net/acm_cxlove/article/details/7745323

康託展開請看:康託展開     USACO 描述Link

八數碼八大境界:八數碼的八境界


第一階段  (康託展開,hash判重)

/*
HDU 1043 Eight
思路:反向搜索,從目標狀態找回狀態對應的路徑
用康託展開判重
*/
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<queue>
#include<string>
using namespace std;
const int MAXN=1000000;//最多是9!/2
int fac[]={1,1,2,6,24,120,720,5040,40320,362880};//康拖展開判重
//         0!1!2!3! 4! 5!  6!  7!   8!    9!
bool vis[MAXN];//標記
string path[MAXN];//記錄路徑
int cantor(int s[])//康拖展開求該序列的hash值
{
    int sum=0;
    for(int i=0;i<9;i++)
    {
        int num=0;
        for(int j=i+1;j<9;j++)
          if(s[j]<s[i])num++;
        sum+=(num*fac[9-i-1]);
    }
    return sum+1;
}
struct Node
{
    int s[9];
    int loc;//“0”的位置
    int status;//康拖展開的hash值
    string path;//路徑
};
int move[4][2]={{-1,0},{1,0},{0,-1},{0,1}};//u,d,l,r
char indexs[5]="durl";//和上面的要相反,因爲是反向搜索
int aim=46234;//123456780對應的康拖展開的hash值
void bfs()
{
    memset(vis,false,sizeof(vis));
    Node cur,next;
    for(int i=0;i<8;i++)cur.s[i]=i+1;
    cur.s[8]=0;
    cur.loc=8;
    cur.status=aim;
    cur.path="";
    queue<Node>q;
    q.push(cur);
    path[aim]="";
    while(!q.empty())
    {
        cur=q.front();
        q.pop();
        int x=cur.loc/3;
        int y=cur.loc%3;
        for(int i=0;i<4;i++)
        {
            int tx=x+move[i][0];
            int ty=y+move[i][1];
            if(tx<0||tx>2||ty<0||ty>2)continue;
            next=cur;
            next.loc=tx*3+ty;
            next.s[cur.loc]=next.s[next.loc];
            next.s[next.loc]=0;
            next.status=cantor(next.s);
            if(!vis[next.status])
            {
                vis[next.status]=true;
                next.path=indexs[i]+next.path;
                q.push(next);
                path[next.status]=next.path;
            }
        }
    }

}
int main()
{
    char ch;
    Node cur;
    bfs();
    while(cin>>ch)
    {
        if(ch=='x') {cur.s[0]=0;cur.loc=0;}
        else cur.s[0]=ch-'0';
        for(int i=1;i<9;i++)
        {
            cin>>ch;
            if(ch=='x')
            {
                cur.s[i]=0;
                cur.loc=i;
            }
            else cur.s[i]=ch-'0';
        }
        cur.status=cantor(cur.s);
        if(vis[cur.status])
        {
            cout<<path[cur.status]<<endl;
        }
        else cout<<"unsolvable"<<endl;
    }
    return 0;
}

第二境界|廣搜+哈希+打表

/*
HDU 1043 Eight 
反向搜索+hash+打表。
思路:反向搜索,從目標狀態找回狀態對應的路徑
用康託展開判重
*/
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<stack>
#include<string>
#include<map>
#include<set>
#include<ctime>
#define eps 1e-6
#define LL long long
#define pii pair<int, int>
//#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int MAXN = 400000;
//const int INF = 0x3f3f3f3f;
bool has[MAXN];
int fac[9] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320};
int dx[] = {-1, 0, 1, 0};
int dy[] = {0, -1, 0, 1};
int last[MAXN]; 
short int mov[MAXN];

struct State {
	short int s[9];
	short int pos;		
	int hash;
} ini;
queue<State> q;

int Hash(short int* s) {
	int res = 0;
	for(int i = 0; i < 8; i++) {
		int cnt = 0;
		for(int j = i + 1; j < 9; j++) {
			if(s[i] > s[j]) cnt++; 
		}
		res += cnt * fac[8-i];
	}
	return res;
} 

int cal_pos(int pos, int i) {
	int nx = pos/3+dx[i], ny = pos%3+dy[i];
	if(nx<0 || nx>2 || ny<0 || ny>2) return -1;
	return nx*3 + ny;
}

void BFS() {
	State target;
	for(int i = 0; i < 9; i++) target.s[i] = i+1;
	target.pos = 8;
	target.hash = 0;
	has[0] = 1;
	q.push(target);
	while(!q.empty()) {
		State ha = q.front();
		q.pop();
		State tmp;  
		for(int i = 0; i < 4; i++) {
			tmp.pos = cal_pos(ha.pos, i);
			if(tmp.pos<0) continue;
			for(int j = 0; j < 9; j++) {
				if(j==ha.pos) tmp.s[j] = ha.s[tmp.pos];
				else if(j == tmp.pos) tmp.s[j] = ha.s[ha.pos];
				else tmp.s[j] = ha.s[j];
			}
			tmp.hash = Hash(tmp.s);
			if(has[tmp.hash]) continue;
			q.push(tmp);
			has[tmp.hash] = 1;
			last[tmp.hash] = ha.hash;
			mov[tmp.hash] = i;
		}
	}
}

void print_path(int x) {
	if(x==0) return;
	int i = mov[x];
	if(!i) printf("d");
	else if(i==1) printf("r");
	else if(i==2) printf("u");
	else printf("l");
	print_path(last[x]);
}

int main() {
    //freopen("input.txt", "r", stdin);
	memset(has, 0, sizeof(has));
	BFS();
	char tmp;
	while(cin >> tmp) { 	
		if(tmp != 'x') ini.s[0] = tmp - '0';
		else {
			ini.s[0] = 9;
			ini.pos = 0;
		}
		for(int i = 1; i < 9; i++) {
			cin >> tmp;
			if(tmp == 'x') {
				ini.s[i] = 9;
				ini.pos = i;
			}
			else ini.s[i] = tmp - '0';
		}
		ini.hash = Hash(ini.s);
		if(!has[ini.hash]) printf("unsolvable");
		else print_path(ini.hash);
		puts("");		
	}
    return 0;
}

第三境界|雙向廣搜+哈希

#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<stack>
#include<string>
#include<map>
#include<set>
#include<ctime>
#define eps 1e-6
#define LL long long
#define pii pair<int, int>
//#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int MAXN = 400000;
//const int INF = 0x3f3f3f3f;
short int has[MAXN];
int fac[9] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320};
int dx[] = {-1, 0, 1, 0};
int dy[] = {0, -1, 0, 1};
int last1[MAXN], last2[MAXN]; 
short int mov1[MAXN], mov2[MAXN];

struct State {
	short int s[9];
	short int pos;		
	int hash;
} ini, target;
queue<State> q1, q2;

int Hash(short int* s) {
	int res = 0;
	for(int i = 0; i < 8; i++) {
		int cnt = 0;
		for(int j = i + 1; j < 9; j++) {
			if(s[i] > s[j]) cnt++; 
		}
		res += cnt * fac[8-i];
	}
	return res;
} 

int cal_pos(int pos, int i) {
	int nx = pos/3+dx[i], ny = pos%3+dy[i];
	if(nx<0 || nx>2 || ny<0 || ny>2) return -1;
	return nx*3 + ny;
}

void DBFS_init() {
	while(!q1.empty()) q1.pop();
	while(!q2.empty()) q2.pop();
	for(int i = 0; i < 9; i++) target.s[i] = i+1;
	target.pos = 8;
	target.hash = 0;
	has[0] = 2;
	q2.push(target);
	q1.push(ini);
	has[ini.hash] = 1;
}

int q1_expand() {
	if(q1.empty()) return -1;
	State ha = q1.front();
	q1.pop();
	State tmp;  
	for(int i = 0; i < 4; i++) {
		tmp.pos = cal_pos(ha.pos, i);
		if(tmp.pos<0) continue;
		for(int j = 0; j < 9; j++) {
			if(j==ha.pos) tmp.s[j] = ha.s[tmp.pos];
			else if(j == tmp.pos) tmp.s[j] = ha.s[ha.pos];
			else tmp.s[j] = ha.s[j];
		}
		tmp.hash = Hash(tmp.s);
		if(has[tmp.hash] == 1) continue;
		q1.push(tmp);
		last1[tmp.hash] = ha.hash;
		mov1[tmp.hash] = i;
		if(has[tmp.hash] == 2) return tmp.hash;
		has[tmp.hash] = 1;
	}
	return -1;
}

int q2_expand() {
	if(q2.empty()) return -1;
	State ha = q2.front();
	q2.pop();
	State tmp;  
	for(int i = 0; i < 4; i++) {
		tmp.pos = cal_pos(ha.pos, i);
		if(tmp.pos<0) continue;
		for(int j = 0; j < 9; j++) {
			if(j==ha.pos) tmp.s[j] = ha.s[tmp.pos];
			else if(j == tmp.pos) tmp.s[j] = ha.s[ha.pos];
			else tmp.s[j] = ha.s[j];
		}
		tmp.hash = Hash(tmp.s);
		if(has[tmp.hash] == 2) continue;
		q2.push(tmp);
		last2[tmp.hash] = ha.hash;
		mov2[tmp.hash] = i;
		if(has[tmp.hash] == 1) return tmp.hash;
		has[tmp.hash] = 2;
	}
	return -1;
}

int DBFS() {
	DBFS_init();
	while(!q1.empty() || !q2.empty()) {
		int ans1 = q1_expand();
		if(ans1 >= 0) return ans1;
		int ans2 = q2_expand();
		if(ans2 >= 0) return ans2;
	}
	return -1;
}
 
void print_path1(int x) {
	if(x==ini.hash) return;
	print_path1(last1[x]);
	int i = mov1[x];
	if(!i) printf("u");
	else if(i==1) printf("l");
	else if(i==2) printf("d");
	else printf("r");
	
}

void print_path2(int x) {
	if(x==0) return;
	int i = mov2[x];
	if(!i) printf("d");
	else if(i==1) printf("r");
	else if(i==2) printf("u");
	else printf("l");
	print_path2(last2[x]);
}

int main() {
    //freopen("input.txt", "r", stdin);
	char tmp;
	while(cin >> tmp) { 
		memset(has, 0, sizeof(has));	
		if(tmp != 'x') ini.s[0] = tmp - '0';
		else {
			ini.s[0] = 9;
			ini.pos = 0;
		}
		for(int i = 1; i < 9; i++) {
			cin >> tmp;
			if(tmp == 'x') {
				ini.s[i] = 9;
				ini.pos = i;
			}
			else ini.s[i] = tmp - '0';
		}
		ini.hash = Hash(ini.s);
		int ans = DBFS();
		if(ans < 0) printf("unsolvable");
		else {
			print_path1(ans);
			print_path2(ans);
		}
		puts("");		
	}
    return 0;
}

八數碼第四境界 A*+哈希+簡單估價函數+打表

#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<stack>
#include<string>
#include<map>
#include<set>
#include<ctime>
#define eps 1e-6
#define LL long long
#define pii pair<int, int>
//#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int MAXN = 400000;
//const int INF = 0x3f3f3f3f;
bool has[MAXN];
int fac[9] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320};
int dx[] = {-1, 0, 1, 0};
int dy[] = {0, -1, 0, 1};
int last[MAXN]; 
short int mov[MAXN];

struct State {
	int f, g;
	int s[9];
	int pos;		
	int hash;
	bool operator < (const State& A) const {
		return A.f==f ? g>A.g : f>A.f;
	}
} ini, target;

int cal_h(int* s1) {
	int ans = 0;
	for(int i = 0; i < 9; i++) {
		if(s1[i] != target.s[i]) ans++;
	}
	return ans;
}

priority_queue<State> q;

int Hash(int* s) {
	int res = 0;
	for(int i = 0; i < 8; i++) {
		int cnt = 0;
		for(int j = i + 1; j < 9; j++) {
			if(s[i] > s[j]) cnt++; 
		}
		res += cnt * fac[8-i];
	}
	return res;
} 

int cal_pos(int pos, int i) {
	int nx = pos/3+dx[i], ny = pos%3+dy[i];
	if(nx<0 || nx>2 || ny<0 || ny>2) return -1;
	return nx*3 + ny;
}

void BFS() {
	for(int i = 0; i < 9; i++) target.s[i] = i+1;
	target.pos = 8;
	target.hash = 0;
	has[0] = 1;
	q.push(target);
	while(!q.empty()) {
		State ha = q.top();
		q.pop();
		State tmp;  
		for(int i = 0; i < 4; i++) {
			tmp.pos = cal_pos(ha.pos, i);
			tmp.g = ha.g + 1;
			if(tmp.pos<0) continue;
			for(int j = 0; j < 9; j++) {
				if(j==ha.pos) tmp.s[j] = ha.s[tmp.pos];
				else if(j == tmp.pos) tmp.s[j] = ha.s[ha.pos];
				else tmp.s[j] = ha.s[j];
			}
			tmp.hash = Hash(tmp.s);
			if(has[tmp.hash]) continue;
			tmp.f = ha.f + cal_h(tmp.s);
			q.push(tmp);
			has[tmp.hash] = 1;
			last[tmp.hash] = ha.hash;
			mov[tmp.hash] = i;
		}
	}
}

void print_path(int x) {
	if(x==0) return;
	int i = mov[x];
	if(!i) printf("d");
	else if(i==1) printf("r");
	else if(i==2) printf("u");
	else printf("l");
	print_path(last[x]);
}

int main() {
    //freopen("input.txt", "r", stdin);
	memset(has, 0, sizeof(has));
	BFS();
	char tmp;
	while(cin >> tmp) { 	
		if(tmp != 'x') ini.s[0] = tmp - '0';
		else {
			ini.s[0] = 9;
			ini.pos = 0;
		}
		for(int i = 1; i < 9; i++) {
			cin >> tmp;
			if(tmp == 'x') {
				ini.s[i] = 9;
				ini.pos = i;
			}
			else ini.s[i] = tmp - '0';
		}
		ini.hash = Hash(ini.s);
		if(!has[ini.hash]) printf("unsolvable");
		else print_path(ini.hash);
		puts("");		
	}
    return 0;
}

八數碼的幾種做法的總結以及是否有解的判斷

經典的八數碼問題,嘗試了一些不同的做法,現在總結下。

1.廣搜+哈希

這是最容易想到的一種做法,哈希的方法是康託展開,組合數學上有介紹。

廣搜+哈希

2.雙向廣搜+哈希

雙向廣搜的複雜度大約是單向的一半,所以效率上會有不錯的提高。

雙向廣搜+哈希

3.A*+哈希+曼哈頓距離

用到廣搜,就可以想到能用經典的A*解決,用深度作爲g(n),剩下的自然是啓發函數了。對於八數碼,啓發函數可以用兩種狀態不同數字的數目。接下來就是A*的套路,A*的具體思想不再贅述,因爲人工智能課本肯定比我講的清楚。但是必須得注意到,A*需要滿足兩個條件:

1.h(n)>h'(n),h'(n)爲從當前節點到目標點的實際的最優代價值。

2.每次擴展的節點的f值大於等於父節點的f值小。

自然,我們得驗證下我們的啓發函數,h驗證比較簡單不用說,由於g是深度,每次都會較父節點增1。再看h,認識上, 我們只需要將h看成真正的“八數碼”,將空格看空。這裏,就會發現,每移動一次,最多使得一個數字迴歸,或者說不在位減一個。 h最多減小1,而g認爲是深度,每次會增加1。所以,f=g+h, 自然非遞減,這樣,滿足了A*的兩個條件,可以用A*了!

A*+哈希+曼哈頓距離

4.IDA*+曼哈頓距離

因爲要用到IDA*搜索,所以我們搜索之前先判斷一下是否有解。

判斷的方法是學習一個大神的: 判斷八數碼問題是否有解 

IDA*比起BFS的好處是空間複雜度極低,同時因爲有剪枝,比起BFS降低了盲目性;比起A*的好處是不用去維護一個堆。

IDA*+曼哈頓距離

由八數碼難題和N皇后問題聯想到搜索小結

一般來說,廣搜常用於找單一的最短路線,或者是規模小的路徑搜索,它的特點是"搜到就是最優解", 而深搜用於找多個解或者是"步數已知(好比3步就必需達到前提)"的標題,它的空間效率高,然則找到的不必定是最優解,必需記實並完成全數搜索,故一般情況下,深搜需要很是高效的剪枝(優化).
像搜索最短路徑這些的很顯著若是用廣搜,因爲廣搜的特徵就是一層一層往下搜的,保證當前搜到的都是最優解,最短路徑只是一方面的操作,狀態轉換也是可以操作的。

深搜就是優先搜索一棵子樹,然後是另一棵,它和廣搜對比,有着內存需要相對較少的所長,八皇后標題就是典範楷模的操作,這類標題很顯著是不能用廣搜往解決的。或者像圖論裏面的找圈的算法,數的前序中序後序遍歷等,都是深搜。深搜和廣搜的分歧之處是在於搜索次序的分歧

狀態,判重,剪枝,遞歸函數參數

1.封裝函數。比如判重,是否在仍在圖中IsInBound,是否達到要求meet_require
2.命名。point node
3.定義狀態。這個非常重要,bfs存儲判重,動態規劃遞歸方程,圖論描述都要求對狀態的清晰描述(R,M)pos....
4.狀態映射,對於數組對點纔對應一個狀態

5. 遞歸的回溯,出口。核心在於函數內循環時判斷點能否進入,進入後出來不滿足在回溯恢復。運用遞歸迭代量判斷函數出口,每個函數剛進來表示的是上次完成了多少

6. 遞歸的參數,包含遞歸深度量,查詢迭代量。

HDU1401:Solitaire(BFS)
四個點,每個用兩個量來描述
對於判重,bool vis[8][8][8][8][8][8][8][8];值得借鑑學習。多個位置纔對應一點。若爲判重還有康託展開

5.剪枝

這個一般對於dfs算法,降低深度。其中一點核心是發現其本質相同的情況

poj 1011 sticks

發現對於首根木棒和最後一根木棒,無論如何替換都繞不過其他木棍也要使用的條件。

對於重複木棒若上次的沒有被選則這次也不被選則。(若選了也可能用上)奇偶剪枝

把map看作

                             0 1 0 1 0 1
                             1 0 1 0 1 0
                             0 1 0 1 0 1
                             1 0 1 0 1 0
                             0 1 0 1 0 1

從 0->1 需要奇數步

從 0->0 需要偶數步
那麼設所在位置 (x,y) 與 目標位置 (dx,dy)如果abs(x-y)+abs(dx-dy)爲偶數,則說明 abs(x-y) 和 abs(dx-dy)的奇偶性相同,需要走偶數步如果abs(x-y)+abs(dx-dy)爲奇數,那麼說明 abs(x-y) 和 abs(dx-dy)的奇偶性不同,需要走奇數步理解爲 abs(si-sj)+abs(di-dj) 的奇偶性就確定了所需要的步數的奇偶性!!

而 (ti-setp)表示剩下還需要走的步數,由於題目要求要在 ti時 恰好到達,那麼  (ti-step) 與 abs(x-y)+abs(dx-dy) 的奇偶性必須相同。因此 temp=ti-step-abs(dx-x)-abs(dy-y) 必然爲偶數!

類似八數碼問題中可行解的判斷,逆序數的奇偶性
6.枚舉技巧,優先枚舉可能的解。eg:枚舉數字從大到小

康託展開的解釋

康託展開就是一種特殊的哈希函數把一個整數X展開成如下形式:

  X=a[n]*n!+a[n-1]*(n-1)!+...+a[2]*2!+a[1]*1!

  其中,a爲整數,並且0<=a<i,i=1,2,..,n

{1,2,3,4,...,n}表示1,2,3,...,n的排列如 {1,2,3} 按從小到大排列一共6個。123 132 213 231 312 321 。

代表的數字 1 2 3 4 5 6 也就是把10進制數與一個排列對應起來。

他們間的對應關係可由康託展開來找到。如我想知道321是{1,2,3}中第幾個大的數可以這樣考慮 :

第一位是3,當第一位的數小於3時,那排列數小於321 如 123、 213 ,小於3的數有1、2 。所以有2*2!個。再看小於第二位2的:小於2的數只有一個就是1 ,所以有1*1!=1 所以小於321的{1,2,3}排列數有2*2!+1*1!=5個
。所以321是第6個大的數。

2*2!+1*1!是康託展開。

康託展開求法:

比如2143 這個數,求其展開:

從頭判斷,至尾結束,

① 比 2(第一位數)小的數有多少個->1個就是1,1*3!

② 比 1(第二位數)小的數有多少個->0個0*2!

③ 比 4(第三位數)小的數有多少個->3個就是1,2,3,但是1,2之前已經出現,所以是  1*1!

將所有乘積相加=7

比該數小的數有7個,所以該數排第8的位置。

1234  1243  1324  1342  1423  1432
2134  2143  2314  2341  2413  2431
3124  3142  3214  3241  3412  3421

4123  4132  4213  4231  4312  4321

求康託展開的代碼實現:

    int  fac[] = {1,1,2,6,24,120,720,5040,40320}; //i的階乘爲fac[i]  
    // 康託展開-> 表示數字a是 a的全排列中從小到大排,排第幾  
    // n表示1~n個數  a數組表示數字。  
    int kangtuo(int n,char a[])  
    {  
        int i,j,t,sum;  
        sum=0;  
        for( i=0; i<n ;++i)  
        {  
            t=0;  
            for(j=i+1;j<n;++j)  
                if( a[i]>a[j] )  
                    ++t;  
            sum+=t*fac[n-i-1];  
        }  
        return sum+1;  
    }  

       康託展開的逆運算
  例 {1,2,3,4,5}的全排列,並且已經從小到大排序完畢

  (1)找出第96個數

  首先用96-1得到95

  用95去除4! 得到3餘23

  用23去除3! 得到3餘5

  用5去除2!得到2餘1

  用1去除1!得到1餘0有3個數比它小的數是4

  所以第一位是4

  有3個數比它小的數是4但4已經在之前出現過了所以是5(因爲4在之前出現過了所以實際比5小的數是3個)

  有2個數比它小的數是3

  有1個數比它小的數是2

  最後一個數只能是1

  所以這個數是45321

康託展開的逆:

康託展開是一個全排列到自然數的雙射,可以作爲哈希函數。

所以當然也可以求逆運算了。

逆運算的方法:

假設求4位數中第19個位置的數字。

① 19減去1  → 18

② 18 對3!作除法 → 得3餘0

③  0對2!作除法 → 得0餘0

④  0對1!作除法 → 得0餘0

據上面的可知:

我們第一位數(最左面的數),比第一位數小的數有3個,顯然 第一位數爲→ 4

比第二位數小的數字有0個,所以 第二位數爲→1

比第三位數小的數字有0個,因爲1已經用過,所以第三位數爲→2

第四位數剩下 3

該數字爲  4123  (正解)

用代碼實現上述步驟爲:

    int  fac[] = {1,1,2,6,24,120,720,5040,40320};  
    //康託展開的逆運算,{1...n}的全排列,中的第k個數爲s[]  
    void reverse_kangtuo(int n,int k,char s[])  
    {  
        int i, j, t, vst[8]={0};  
        --k;  
        for (i=0; i<n; i++)  
        {  
            t = k/fac[n-i-1];  
            for (j=1; j<=n; j++)  
                if (!vst[j])  
                {  
                    if (t == 0) break;  
                    --t;  
                }  
            s[i] = '0'+j;  
            vst[j] = 1;  
            k %= fac[n-i-1];  
        }  
    }  


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章