您的位置:首页 > 其它

八数码问题小结

2015-10-16 17:03 483 查看

一. 八数码问题

八数码问题也称为九宫问题。在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字,不同棋子上标的数字不相同。棋盘上还有一个空格,与空格相邻的棋子可以移到空格中。要求解决的问题是:给出一个初始状态和一个目标状态,找出一种从初始转变成目标状态的移动棋子步数最少的移动步骤。

二. 问题分析

看到不错的文章,就直接传送门了,谁叫我这么懒呢。。。
八数码的八境界:
http://www.cnblogs.com/goodness/archive/2010/05/04/1727141.html

康托展开和康托展开的逆运算:
http://blog.csdn.net/acdreamers/article/details/7982067

另外则个问题是有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");
}
}


未完待续。。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息