八數碼問題小結

一. 八數碼問題

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


二. 問題分析

看到不錯的文章,就直接傳送門了,誰叫我這麼懶呢。。。
八數碼的八境界:
康託展開和康託展開的逆運算:

另外則個問題是有Special Judge的。


三. 各種思路的代碼實現

自己看了兩天把各種思路的代碼敲了下,題目是:POJ - 1077 Eight(鬆鬆可以過的) HDU - 1043 Eight(多組輸入,容易卡內存,打表也能過)

境界一:暴力BFS+STL

各種無腦用stl,索性各種狀態轉移也寫成像模擬一樣的了,代碼應該很好看,大概能保證10m內出結果,什麼題也過不了。微笑
//境界一:BFS+STL
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<set>
#include<queue>
#include<stack>
#include<map>
using namespace std;

string g;
set<string>vis;
queue<string>que;

struct node
{
	string pre;
	char op;
};

map<string, node>road;

bool moveUp(string &g)
{
	int posx;
	for (int i = 0; i < 9; i++)
	{
		if (g[i] == 'x')
		{
			posx = i;
			break;
		}
	}
	if (posx <= 2) return false;
	else
	{
		swap(g[posx], g[posx - 3]);
		return true;
	}
}

bool moveDown(string &g)
{
	int posx;
	for (int i = 0; i < 9; i++)
	{
		if (g[i] == 'x')
		{
			posx = i;
			break;
		}
	}
	if (posx >= 6) return false;
	else
	{
		swap(g[posx], g[posx + 3]);
		return true;
	}
}

bool moveLeft(string &g)
{
	int posx;
	for (int i = 0; i < 9; i++)
	{
		if (g[i] == 'x')
		{
			posx = i;
			break;
		}
	}
	if (posx == 0 || posx==3 || posx==6) return false;
	else
	{
		swap(g[posx], g[posx - 1]);
		return true;
	}
}

bool moveRight(string &g)
{
	int posx;
	for (int i = 0; i < 9; i++)
	{
		if (g[i] == 'x')
		{
			posx = i;
			break;
		}
	}
	if (posx == 2 || posx==5 ||posx==8 ) return false;
	else
	{
		swap(g[posx], g[posx + 1]);
		return true;
	}
}

int bfs()
{
	while (!que.empty()) que.pop();
	vis.clear();
	road.clear();

	que.push(g);
	vis.insert(g);

	while (!que.empty())
	{
		string loc = que.front();
		que.pop();

		if (loc == "12345678x")
			return true;

		for (int i = 0; i < 4; i++)
		{
			string noc = loc;

			if (i == 0)
			{
				if (moveUp(noc) && vis.count(noc)==0)
				{
					que.push(noc);
					vis.insert(noc);
					road[noc].pre = loc;
					road[noc].op = 'u';
				}
			}
			else if (i == 1)
			{
				if (moveDown(noc) && vis.count(noc) == 0)
				{
					que.push(noc);
					vis.insert(noc);
					road[noc].pre = loc;
					road[noc].op = 'd';
				}
			}
			else if (i == 2)
			{
				if (moveLeft(noc) && vis.count(noc) == 0)
				{
					que.push(noc);
					vis.insert(noc);
					road[noc].pre = loc;
					road[noc].op = 'l';
				}
			}
			else if (i == 3)
			{
				if (moveRight(noc) && vis.count(noc) == 0)
				{
					que.push(noc);
					vis.insert(noc);
					road[noc].pre = loc;
					road[noc].op = 'r';
				}
			}
		}
	}
	return -1;
}

int main()
{
	char c;
	for (int i = 0; i < 9; i++)
	{
		cin >> c;
		g += c;
	}
	
	int ans=bfs();

	if (ans == -1)
		cout << "unsolvable" << endl;
	else
	{
		string np = "12345678x";

		stack<char>sop;

		while (np != g)
		{
			sop.push(road[np].op);
			np = road[np].pre;
		}

		while (!sop.empty())
		{
			cout << sop.top();
			sop.pop();
		}

		cout << endl;
	}
}

境界二:BFS+哈希(康託展開)


queue代價有點大,應該是過不了題的,用數組手動實現堆,這個代碼和紫書199頁的差不多,在有個康託展開來判重。能過POJ那題,HDU的是過不了了。

//境界二:BFS+康託展開
#include<iostream>
#include<cstdio>
#include<cstring>
#include<stack>
using namespace std;

typedef int State[9];
const int maxn = 1000000;
State st[maxn], goal;		//狀態數組,用來實現隊列
int dis[maxn];              //距離數組
int fa[maxn];				//記錄前一個狀態的編號
char curop[maxn];			//記錄達到該狀態的對應操作
bool vis[maxn];				//結點是否訪問過

const int dirx[] = { -1, 1, 0, 0 };
const int diry[] = { 0, 0, -1, 1 };
const char cop[] = { 'u', 'd', 'l', 'r' };

const int f[] = { 1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880 };

//康託展開
int cantor(State s)
{
	int ans = 0;
	for (int i = 0; i<9; i++)
	{
		int tmp = 0;
		for (int j = i + 1; j<9; j++)
		if (s[j] < s[i]) tmp++;
		ans += tmp * f[9 - i - 1];  //f[]爲階乘
	}
	return ans;  //返回該字符串是全排列中第幾大,從0開始
}

int bfs()
{
	memset(vis, false, sizeof(vis));				//初始化查找表
	vis[cantor(st[1])] = true;
	int front = 1, rear = 2;
	while (front < rear)
	{
		State& s = st[front];						//用引用簡化代碼
		int cs = cantor(s);
		if (memcmp(goal, s, sizeof(s)) == 0) return front;		//找到目標狀態,返回

		int pos;
		for (pos = 0; pos < 9; pos++) if (!s[pos]) break;

		int x = pos / 3, y = pos % 3;
		for (int d = 0; d < 4; d++)
		{
			int newx = x + dirx[d];
			int newy = y + diry[d];
			int newpos = newx * 3 + newy;
			if (newx >= 0 && newx < 3 && newy >= 0 && newy < 3)
			{
				State& t = st[rear];
				memcpy(&t, &s, sizeof(s));
				t[newpos] = 0;
				t[pos] = s[newpos];
				dis[rear] = dis[front] + 1;
				int ct = cantor(t);
				if (!vis[ct])			//結點合法且未訪問,加入查找表,修改隊尾指針
				{
					rear++;
					curop[ct] = cop[d];
					fa[ct] = cs;
					vis[ct] = true;
				}
			}
		}
		front++;		//擴展完畢後修改隊首指針
	}

	return 0;
}

int main()
{
	char c;
	for (int i = 0; i < 9; i++)
	{
		cin >> c;
		if (c == 'x') st[1][i] = 0;
		else st[1][i] = c - '0';
	}

	goal[8] = 0;
	for (int i = 0; i < 8; i++)
		goal[i] = i + 1;

	int ans = bfs();

	if (!ans) printf("unsolvable\n");
	else
	{
		stack<char>ansop;
		int np = cantor(goal);
		int sp = cantor(st[1]);
		while (fa[np] != sp)
		{
			ansop.push(curop[np]);
			np = fa[np];
		}
		ansop.push(curop[np]);

		while (!ansop.empty())
		{
			printf("%c", ansop.top());
			ansop.pop();
		}
		printf("\n");
	}
}

境界三:逆向BFS+康託展開+打表


從最終狀態逆向bfs到所有可以達到的狀態,保存路徑,此外用了逆序數判斷是否有解。HDU的過的了,差點卡內存,記錄狀態的數組太大了,換成字符串應該好一些。

//境界3:逆向廣搜+康託展開+打表
//答案和樣例的不同,但沒關係,八數碼問題是有多個解的,有Special Judge
#include<iostream>
#include<cstdio>
#include<cstring>
#include<stack>
#include<string>
using namespace std;

const int maxn = 363000;

bool vis[maxn];		
string path[maxn];		//方便的存下路徑

const int dirx[] = { -1, 1, 0, 0 };
const int diry[] = { 0, 0, -1, 1 };
const char cop[] = { 'd', 'u', 'r', 'l' };     //和上面的方向數組相反,因爲是逆向bfs

const int f[] = { 1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880 };

struct node
{
	int map[9];
	int pos;
}que[maxn];		//用數組實現棧,stl代價太大

//直接求逆序數,因爲移動x並不會改變map的逆序數的奇偶性,很容易想到
//而原始逆序數爲0,故無解的情況是:逆序數%2!=0
int getnixu(int s[])
{
	int ret = 0;
	for (int i = 0; i < 9; i++)
	{
		if (s[i] == 0) continue;
		for (int j = 0; j < i; j++)
		{
			if (s[j] == 0) continue;
			if (s[i] < s[j]) ret++;
		}
	}
	return ret;
}

//康託展開
int cantor(int s[])
{
	int ans = 0;
	for (int i = 0; i<9; i++)
	{
		int tmp = 0;
		for (int j = i + 1; j<9; j++)
		if (s[j] < s[i]) tmp++;
		ans += tmp * f[9 - i - 1];  //f[]爲階乘
	}
	return ans;  //返回該字符串是全排列中第幾大,從0開始
}

void bfs()
{
	memset(vis, false, sizeof(vis));
	
	int tag[9] = { 1, 2, 3, 4, 5, 6, 7, 8, 0 };		//設置最終狀態
	int ct_tag = cantor(tag);
	vis[ct_tag] = true;
	path[ct_tag] = "";

	memcpy(que[1].map , tag, sizeof(tag));	
	que[1].pos = 8;

	int front = 1, rear = 2;
	while (front < rear)
	{
		node loc = que[front];
		int ct_loc = cantor(loc.map);

		int x = loc.pos / 3, y = loc.pos % 3;
		for (int d = 0; d < 4; d++)
		{
			int newx = x + dirx[d];
			int newy = y + diry[d];
			int newpos = newx * 3 + newy;
			if (newx >= 0 && newx < 3 && newy >= 0 && newy < 3)
			{
				node noc;
				memcpy(noc.map, loc.map, sizeof(noc.map));
				noc.map[newpos] = 0;
				noc.map[loc.pos] = loc.map[newpos];
				noc.pos = newpos;
				
				int ct_noc = cantor(noc.map);
				
				if (!vis[ct_noc])
				{
					que[rear++]=noc;
					vis[ct_noc] = true;
					path[ct_noc] = cop[d] + path[ct_loc];
				}
			}
		}
		front++;
	}
}

int main()
{
	bfs();
	
	char c;
	while (cin >> c)
	{
		int g[15];
		if (c == 'x') g[0] = 0;
		else g[0] = c - '0';
		for (int i = 1; i < 9; i++)
		{
			cin >> c;
			if (c == 'x') g[i] = 0;
			else g[i] = c - '0';
		}

		int temp = cantor(g);

		if (getnixu(g) % 2)
			cout << "unsolvable" << endl;
		else
			cout << path[temp] << endl;
	}
}

境界四:雙向廣搜+康託展開

poj那題用了63ms,快多了。。


#include<iostream>
#include<cstdio>
#include<cstring>
#include<stack>
#include<string>
using namespace std;

const int maxn = 363000;

int vis[maxn];
string path[maxn];		

const int dirx[] = { -1, 1, 0, 0 };
const int diry[] = { 0, 0, -1, 1 };
const char zop[] = { 'u', 'd', 'l', 'r' }; 
const char cop[] = { 'd', 'u', 'r', 'l' };

const int f[] = { 1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880 };

int tag[9] = { 1, 2, 3, 4, 5, 6, 7, 8, 0 };
int start[9];
int spos;

struct node
{
	int map[9];
	int pos;
}que[maxn];

int getnixu(int s[])
{
	int ret = 0;
	for (int i = 0; i < 9; i++)
	{
		if (s[i] == 0) continue;
		for (int j = 0; j < i; j++)
		{
			if (s[j] == 0) continue;
			if (s[i] < s[j]) ret++;
		}
	}
	return ret;
}

int cantor(int s[])
{
	int ans = 0;
	for (int i = 0; i<9; i++)
	{
		int tmp = 0;
		for (int j = i + 1; j<9; j++)
		if (s[j] < s[i]) tmp++;
		ans += tmp * f[9 - i - 1];  
	}
	return ans;
}

string bfs_d()
{
	memset(vis, 0, sizeof(vis));

	memcpy(que[1].map, tag, sizeof(tag));
	que[1].pos = 8;
	int ct_tag = cantor(tag);
	vis[ct_tag] = 2;
	path[ct_tag] = "";
	
	memcpy(que[2].map, start, sizeof(start));
	que[2].pos = spos;
	int ct_start = cantor(start);
	vis[ct_start] = 1;
	path[ct_start] = "";

	int front = 1, rear = 3;
	while (front < rear)
	{
		node loc = que[front];
		int ct_loc = cantor(loc.map);

		int x = loc.pos / 3, y = loc.pos % 3;
		for (int d = 0; d < 4; d++)
		{
			int newx = x + dirx[d];
			int newy = y + diry[d];
			int newpos = newx * 3 + newy;
			if (newx >= 0 && newx < 3 && newy >= 0 && newy < 3)
			{
				node noc;
				memcpy(noc.map, loc.map, sizeof(noc.map));
				noc.map[newpos] = 0;
				noc.map[loc.pos] = loc.map[newpos];
				noc.pos = newpos;

				int ct_noc = cantor(noc.map);

				if (!vis[ct_noc])
				{
					que[rear++] = noc;
					vis[ct_noc] = vis[ct_loc];
					if(vis[ct_noc]==2) path[ct_noc] = cop[d] + path[ct_loc];
					else path[ct_noc] = path[ct_loc] + zop[d];
				}
				else if (vis[ct_noc]!=vis[ct_loc])
				{
					if (vis[ct_noc] == 1) return path[ct_noc] +cop[d]+ path[ct_loc];
					else return path[ct_loc] +zop[d]+ path[ct_noc];
				}
			}
		}
		front++;
	}
	return "unsolvable";
}

int main()
{
	char c;
	for (int i = 0; i < 9; i++)
	{
		cin >> c;
		if (c == 'x')
		{
			start[i] = 0; spos = i;
		}
		else start[i] = c - '0';
	}

	if (getnixu(start) % 2)
		cout << "unsolvable" << endl;
	else
		cout << bfs_d() << endl;

}


打表,HDU 3567-Eight Ⅱ

也是打表解決,可以打表的原因是從564178X23搜到7568X4123和從235614X78 搜到1234X5678的本質是一樣的(機智 。_。)然後最終狀態的區別就只是X位置的區別了,
打出X在不同位置的9張表,然後做法和境界三的做法一樣。自己實現的代碼過的好險。。。時間。。。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<stack>
#include<string>
using namespace std;

const int maxn = 363000;

bool vis[maxn];
int pre[9][maxn];
char preop[9][maxn];
int dis[9][maxn];

const int dirx[] = { 1, 0, 0, -1 };
const int diry[] = { 0, -1, 1, 0 };
const char cop[] = "dlru";

const int f[] = { 1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880 };

int tag[9][9];

void getTag()
{
	for (int px = 0; px < 9; px++)
	{
		int  j = 1;
		for (int i = 0; i < 9; i++)
		{
			if (i == px) tag[px][i] = 0;
			else tag[px][i] = j++;
		}
	}
}

struct node
{
	int map[9];
	int pos;
}que[maxn];

//康託展開
int cantor(int s[])
{
	int ans = 0;
	for (int i = 0; i<9; i++)
	{
		int tmp = 0;
		for (int j = i + 1; j<9; j++)
		if (s[j] < s[i]) tmp++;
		ans += tmp * f[9 - i - 1]; 
	}
	return ans; 
}

void bfs(int px)
{
	memset(vis, false, sizeof(vis));

	int ct_tag = cantor(tag[px]);
	vis[ct_tag] = true;
	dis[px][ct_tag] = 0;
	pre[px][ct_tag] = -1;

	memcpy(que[1].map, tag[px], sizeof(tag[px]));
	que[1].pos = px;

	int front = 1, rear = 2;
	while (front < rear)
	{
		node loc = que[front];
		int ct_loc = cantor(loc.map);

		int x = loc.pos / 3, y = loc.pos % 3;
		for (int d = 0; d < 4; d++)
		{
			int newx = x + dirx[d];
			int newy = y + diry[d];
			int newpos = newx * 3 + newy;
			if (newx >= 0 && newx < 3 && newy >= 0 && newy < 3)
			{
				node noc;
				memcpy(noc.map, loc.map, sizeof(noc.map));
				noc.map[newpos] = 0;
				noc.map[loc.pos] = loc.map[newpos];
				noc.pos = newpos;

				int ct_noc = cantor(noc.map);

				if (!vis[ct_noc])
				{
					que[rear++] = noc;
					vis[ct_noc] = true;
					preop[px][ct_noc] = cop[d];
					pre[px][ct_noc] = ct_loc;
					dis[px][ct_noc] = dis[px][ct_loc] + 1;
				}
			}
		}
		front++;
	}
}

void output(int p, int np)
{
	if (pre[p][np] == -1) return;
	output(p, pre[p][np]);
	printf("%c", preop[p][np]);
}

int main()
{
	getTag();

	for (int i = 0; i < 9; i++)
		bfs(i);

	int casen;
	cin >> casen;
	for (int cas = 1; cas <= casen; cas++)
	{
		string s1, s2;
		cin >> s1 >> s2;
		
		int p;
		int g1[9], g2[9];
		int ji = 1;
		for (int i = 0; i < 9; i++)
		{
			if (s1[i] == 'X') { p = i; g1[0] = 0; }
			else g1[s1[i]-'0'] = ji++;
		}

		for (int i = 0; i < 9; i++)
		{
			if (s2[i] == 'X') g2[i] = g1[0];
			else g2[i] = g1[s2[i] - '0'];
		}
		int temp = cantor(g2);

		printf("Case %d: %d\n", cas, dis[p][temp]);
	
		output(p, temp);

		printf("\n");
	}	
}



未完待續。。。

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