八数码问题小结

一. 八数码问题

八数码问题也称为九宫问题。在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");
	}	
}



未完待续。。。

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