[kuangbin带你飞]专题二 搜索进阶 之 A-Eight

这是一道经典的八数码问题。首先,简单介绍一下八数码问题:

八数码问题也称为九宫问题。在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字,不同棋子上标的数字不相同。棋盘上还有一个空格,与空格相邻的棋子可以移到空格中。要求解决的问题是:给出一个初始状态和一个目标状态,找出一种从初始转变成目标状态的移动棋子步数最少的移动步骤。所谓问题的一个状态就是棋子在棋盘上的一种摆法。棋子移动后,状态就会发生改变。解八数码问题实际上就是找出从初始状态到达目标状态所经过的一系列中间过渡状态。

八数码问题有多种解法,BFS,双向BFS,A*等等,可以参考八数码的八境界多种方法求解八数码问题.

求解这道题我用了两种方法:BFS+HASH(这种方法会TLE);改进之后的反向BFS打表+HASH。

整体思路:用BFS搜索,搜索过程中要保存当前棋盘状态,3*3的棋盘有9!中状态,可以考虑用康拓展开来压缩空间( 康托展开是一个全排列到一个自然数的映射,常用于构建哈希表时的空间压缩。 康托展开的实质是计算当前排列在所有由小到大全排列中的顺序,因此是可逆的。)目标状态(1 2 3 4 5 6 7 8 0)对应的自然数是46233。

方法一:

 #include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
//目标状态对应的自然数
#define END 46233
using namespace std;
//9!
const int maxn=;
//初始状态对应的自然数
int flag_star;
//判重
struct
{
int flag;//指向上一个状态
char dir;//从上一状体到这一状态的方向
}vis[maxn];
//阶乘,以求康拓展示开
int fact[];
//记录当前棋盘状态和x的位置
struct node
{
int a[];//当前棋盘的状态
int x;//x所在的位置
}; //递归输出路径 ,从目标状态往初始状态推 ,从初始状态往目标状态输出
void Print(int n)
{
if(n!=flag_star)
{
Print(vis[n].flag);
printf("%c",vis[n].dir);
}
}
//求阶乘
void Init()
{
fact[]=;
for(int i=;i<;i++)
{
fact[i]=fact[i-]*i;
}
}
//康拓展示
int Hash(int a[])
{
int ans=;
for(int i=;i<;i++)
{
int temp=;
for(int j=i+;j<;j++)
{
if(a[j]<a[i])
{
temp++;
}
}
ans+=temp*fact[-i];
}
return ans;
}
//str和dir是相对应的
char str[]="udlr";
int dir[][]={{-,},{,},{,-},{,}}; int bfs(node star)
{
queue<node> Q;
Q.push(star);
while(!Q.empty())
{
node q=Q.front();
Q.pop();
int flag=Hash(q.a);
if(flag==END)
{
return flag;
}
int pos=q.x;
for(int i=;i<;i++)
{
int xpos=q.x/;
int ypos=q.x%;
int xx=xpos+dir[i][];
int yy=ypos+dir[i][];
if(xx>=&&xx<&&yy>=&&yy<)
{
int now=xx*+yy;
swap(q.a[now],q.a[pos]);
q.x=now;
int v=Hash(q.a);
if(v==END)
{
vis[v].dir=str[i];
vis[v].flag=flag;
return v;
}
if(vis[v].flag==-)
{
vis[v].dir=str[i];
vis[v].flag=flag;
Q.push(q);
}
//回退
q.x=pos;
swap(q.a[now],q.a[pos]);
}
}
}
return maxn;
} int main()
{
Init();
char c[]; while(~scanf("%s",c))
{
for(int i=;i<maxn;i++)
{
vis[i].flag=-;
}
node star;
if(c[]=='x')
{
star.a[]=;
star.x=;
}
else
{
star.a[]=c[]-'';
}
for(int i=;i<;i++)
{
scanf("%s",c);
if(c[]=='x')
{
star.x=i;
star.a[i]=;
}
else
{
star.a[i]=c[]-'';
}
}
//初始状态
flag_star=Hash(star.a);
//特判,如果已经到达最后状态
if(flag_star==END)
{
printf("\n");
continue;
}
int f=bfs(star);
if(f==END)
{
Print(f);
printf("\n");
}
else
{
printf("unsolvable\n");
}
}
return ;
}

直接BFS

方法二:

因为有多个输入,直接BFS会T,所以可以反向BFS预处理,以目标状态为起点搜出所以可以到达的状态。

 #include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
//目标状态对应的自然数
#define END 46233
using namespace std;
//9!
const int maxn=;
//初始状态对应的自然数
int flag_star;
//判重
struct
{
int flag;//指向上一个状态
char dir;//从上一状体到这一状态的方向
}vis[maxn];
//阶乘,以求康拓展示开
int fact[];
//记录当前棋盘状态和x的位置
struct node
{
int a[];//当前棋盘的状态
int x;//x所在的位置
}; //递归输出路径 ,从目标状态往初始状态推 ,从初始状态往目标状态输出
void Print(int n)
{
if(n!=END)
{
printf("%c",vis[n].dir);
Print(vis[n].flag);
}
}
//求阶乘
void Init()
{
fact[]=;
for(int i=;i<;i++)
{
fact[i]=fact[i-]*i;
}
}
//康拓展示
int Hash(int a[])
{
int ans=;
for(int i=;i<;i++)
{
int temp=;
for(int j=i+;j<;j++)
{
if(a[j]<a[i])
{
temp++;
}
}
ans+=temp*fact[-i];
}
return ans;
}
//str和dir是相对应的
//char str[5]="udlr";
char str[]="durl";
int dir[][]={{-,},{,},{,-},{,}}; void bfs()
{
node star;
//以目标状态(1 2 3 4 5 6 7 0)为起点
for(int i=;i<;i++)
{
star.a[i]=i+;
}
star.a[]=;
star.x=;
queue<node> Q;
Q.push(star);
while(!Q.empty())
{
node q=Q.front();
Q.pop();
int flag=Hash(q.a);
int pos=q.x;
for(int i=;i<;i++)
{
int xpos=q.x/;
int ypos=q.x%;
int xx=xpos+dir[i][];
int yy=ypos+dir[i][];
if(xx>=&&xx<&&yy>=&&yy<)
{
int now=xx*+yy;
swap(q.a[now],q.a[pos]);
q.x=now;
int v=Hash(q.a);
if(vis[v].flag==-)
{
vis[v].dir=str[i];
vis[v].flag=flag;
Q.push(q);
}
//回退
q.x=pos;
swap(q.a[now],q.a[pos]);
}
}
}
} int main()
{
Init();
char c[];
for(int i=;i<maxn;i++)
{
vis[i].flag=-;
}
bfs();
while(~scanf("%s",c))
{ node star;
if(c[]=='x')
{
star.a[]=;
star.x=;
}
else
{
star.a[]=c[]-'';
}
for(int i=;i<;i++)
{
scanf("%s",c);
if(c[]=='x')
{
star.x=i;
star.a[i]=;
}
else
{
star.a[i]=c[]-'';
}
}
//初始状态
flag_star=Hash(star.a);
//特判,如果已经到达最后状态
if(flag_star==END)
{
printf("\n");
continue;
}
if(vis[flag_star].flag!=-)
{
Print(flag_star);
printf("\n");
}
else
{
printf("unsolvable\n");
}
}
return ;
}

有几点要注意的地方:

1.因为以目标状态为起点,方向正好反了

 //char str[5]="udlr";
char str[]="durl";
int dir[][]={{-,},{,},{,-},{,}};

方向要变

2.输出要变,直接从当前状态外目标状态推

 void Print(int n)
{
// if(n!=flag_star)
// {
// Print(vis[n].flag);
// printf("%c",vis[n].dir);
// }
if(n!=END)
{
printf("%c",vis[n].dir);
Print(vis[n].flag);
}
}

3.判断是否有解是输入状态是否在bfs的时候被经过。

 if(vis[flag_star].flag!=-)
{
Print(flag_star);
printf("\n");
}
else
{
printf("unsolvable\n");
}

方法三:参考别人的代码。A*算法。

最关键的部分是估价函数和判断逆序是否为偶数的剪枝

估价函数,是根据与目标解的曼哈顿距离,也就是每个数字与目标位置的曼哈顿距离之和。

 #include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std; struct node //状态
{
int a[];
int f, h, g;
int x; //x在的位置 // bool operator<(const node n1)const{ //优先队列第一关键字为h,第二关键字为g
// return h!=n1.h?h>n1.h:g>n1.g;
// }
friend bool operator < (node a, node b)
{
return a.f > b.f;
}
}; priority_queue<node>que;
int fac[];
//
struct
{
int father;
char dir;
}vis[]; int get_h(int a[])
{
int h = ;
for(int i = ; i < ; i++)
{
if(a[i])
h += fabs((a[i]-)/ - i/) + fabs((a[i]-)% - i%);
}
return h;
} int Hash(int a[])
{
int ans = ;
for(int i = ; i < ; i++)
{
int tmp = ;
for(int j = i+; j < ; j++)
{
if(a[i] > a[j]) tmp++;
}
ans += tmp*fac[-i];
}
return ans+;
} void prin(int n)
{
// printf("n=%d\n", n);
if(vis[n].father!=-)
{
prin(vis[n].father);
printf("%c", vis[n].dir);
}
} void SWAP(int &x, int &y)
{
int t = x;
x = y;
y = t;
} int dir[][] = { {, }, {-, }, {, -}, {, } };
char dd[] = "dulr"; bool is(int a[])
{
int ans = ;
for(int i = ; i < ; i++)
{
if(a[i])
for(int j = i+; j < ; j++)
{
if(a[i] > a[j] && a[j])
ans++;
}
}
return !(ans&);
} void debug(int a[])
{
for(int i = ; i < ; i++)
{
for(int j = ; j < ; j++)
{
printf("%d ", a[i*+j]);
}
printf("\n");
}
printf("\n");
} int bfs(node star)
{
while(!que.empty()) que.pop();
que.push( star );
star.h = get_h( star.a ); star.g = ;
star.f = star.g + star.h;
vis[ Hash( star.a ) ].father = -;
while(!que.empty())
{
node tmp = que.top();
que.pop();
int father = Hash(tmp.a); // printf("father=%d\n", father); debug(tmp.a); for(int i = ; i < ; i++)
{
int x = dir[i][] + tmp.x/;
int y = dir[i][] + tmp.x%;
if( <= x && x < && <= y && y < )
{
node s = tmp;
s.x = x*+y;
SWAP( s.a[ tmp.x ], s.a[ s.x ] );
s.g++;
s.h = get_h( s.a );
s.f = s.h + s.g;
int son = Hash(s.a);
// printf("tmp.x =%d s.x=%d\n", tmp.x, s.x);
// printf("son=%d\n", son); debug(s.a);
if(son == )
{
vis[ son ].father = father;
vis[ son ].dir = dd[i];
prin();printf("\n");
return ;
}
if(!vis[ son ].father && is(s.a))
{
vis[ son ].father = father;
vis[ son ].dir = dd[i];
que.push( s );
}
}
}
}
return ;
} int main(void)
{
int i;
fac[] = ;
for(i = ; i < ; i++) fac[i] = fac[i-]*i;
node star;
char in[];
// freopen("ou.txt", "w", stdout);
while(~scanf("%s", in))
{
memset(vis, , sizeof(vis));
if(in[] == 'x')
{
star.a[] = ;
star.x = ;
}
else star.a[] = in[] - '';
for(i = ; i < ; i++)
{
scanf("%s", in);
if(in[] == 'x')
{
star.a[i] = ;
star.x = i;
}
else star.a[i] = in[] - '';
}
if(!is(star.a))
{
printf("unsolvable\n");continue;
}
if(Hash(star.a) == ) {printf("\n"); continue;}
if(bfs(star))
{
printf("unsolvable\n");
}
}
return ;
}

A*

【算法系列学习三】[kuangbin带你飞]专题二 搜索进阶 之 A-Eight 反向bfs打表和康拓展开的更多相关文章

  1. 【算法系列学习】[kuangbin带你飞]专题二 搜索进阶 D - Escape (BFS)

    Escape 参考:http://blog.csdn.net/libin56842/article/details/41909459 [题意]: 一个人从(0,0)跑到(n,m),只有k点能量,一秒消 ...

  2. 【算法系列学习】[kuangbin带你飞]专题十二 基础DP1 G - 免费馅饼

    https://vjudge.net/contest/68966#problem/G 正解一: http://www.clanfei.com/2012/04/646.html #include< ...

  3. 【算法系列学习】[kuangbin带你飞]专题十二 基础DP1 F - Piggy-Bank 【完全背包问题】

    https://vjudge.net/contest/68966#problem/F http://blog.csdn.net/libin56842/article/details/9048173 # ...

  4. 【算法系列学习】[kuangbin带你飞]专题十二 基础DP1 E - Super Jumping! Jumping! Jumping!

    https://vjudge.net/contest/68966#problem/E http://blog.csdn.net/to_be_better/article/details/5056334 ...

  5. 【算法系列学习】[kuangbin带你飞]专题十二 基础DP1 C - Monkey and Banana

    https://vjudge.net/contest/68966#problem/C [参考]http://blog.csdn.net/qinmusiyan/article/details/79862 ...

  6. 【算法系列学习】[kuangbin带你飞]专题十二 基础DP1 B - Ignatius and the Princess IV

    http://www.cnblogs.com/joeylee97/p/6616039.html 引入一个cnt,输入元素与上一个元素相同,cnt增加,否则cnt减少,当cnt为零时记录输入元素,因为所 ...

  7. HDU - 3001 Travelling 状压dp + 三进制 [kuangbin带你飞]专题二

    终于刷完搜索专题了. 题意:给定n个城市,每个城市参观不能超过两次,两个城市之间有道路通过需要花费X,求通过能所有城市的最小花费. 思路:每个城市有三个状态0,1,2,可用三进制存储所有城市的访问状态 ...

  8. [kuangbin带你飞]专题二十二 区间DP

            ID Origin Title   17 / 60 Problem A ZOJ 3537 Cake   54 / 105 Problem B LightOJ 1422 Hallowee ...

  9. HDU - 3085 双向BFS + 技巧处理 [kuangbin带你飞]专题二

    题意:有两只鬼,一个男孩女孩被困在迷宫中,男孩每秒可以走三步,女孩只能1步,鬼可以两步且可以通过墙.问男孩女孩是否可以在鬼抓住他们之前会合? 注意:每秒开始鬼先移动,然后两人开始移动. 思路:以男孩和 ...

随机推荐

  1. Azure Messaging-ServiceBus Messaging消息队列技术系列5-重复消息:at-least-once at-most-once

    上篇博客中,我们用实际的业务场景和代码示例了Azure Messaging-ServiceBus Messaging对复杂对象消息的支持和消息的持久化: Azure Messaging-Service ...

  2. python 中的input()和raw_input()功能与使用区别

    在python中raw_input()和input()都是提示并获取用户输入的函数,然后将用户的输入数据存入变量中.但二者在处理返回数据类型上有差别. input()函数是raw_intput()和e ...

  3. Java 中的数组

    1.声明数组String [] arr;int arr1[];String[] array=new String[5];int score[]=new int[3]; 2.初始化数组://静态初始化i ...

  4. 手把手教你做个AR涂涂乐

    前段时间公司有一个AR涂涂乐的项目,虽然之前接触过AR也写过小Demo,但是没有完整开发过AR项目.不过经过1个多星期的学习,现在已经把项目相关的技术都学会了,在此向互联网上那些乐于分享的程序员前辈们 ...

  5. kvm基本原理

    KVM源代码分析1:基本工作原理 下了很大决心挖这个坑,虽然之前对kvm有些了解,但纸上得来终觉浅,只有深入到代码层面,才能摈弃皮毛,看到血肉,看到真相.作为挖坑的奠基石,准备写上几篇:kvm基本工作 ...

  6. vue-miniQQ——基于Vue2实现的仿手机QQ单页面应用(接入了聊天机器人,能够进行正常对话)

    使用Vue2进行的仿手机QQ的webapp的制作,作品由个人独立开发,源码中进行了详细的注释. 由于自己也是初学Vue2,所以注释写的不够精简,请见谅. 项目地址 https://github.com ...

  7. Unity3D中的AI架构模型

    我们都知道现在AI(由人工制造出来的系统所表现出来的模拟人类的智能活动)非常的火,可以说是家喻户晓.当然,在游戏中,AI也是到处可以找到的,对于AI,我们应该关注的问题是如何让游戏角色能够向人或动物那 ...

  8. GDOI2014模拟 旅行【SPFA】

    旅行(travel) 从前有一位旅者,他想要游遍天下所有的景点.这一天他来到了一个神奇的王国:在这片土地上,有n个城市,从1到n进行编号.王国中有m条道路,第i条道路连接着两个城市ai,bi,由于年代 ...

  9. 【Electron】Electron开发入门(五):项目打包

    一.安装 electron-packager PS:安装之前,先复制一份package.json文件到./app目录下,然后改下./app目录下package.json里 "main&quo ...

  10. 老李分享:HTTP协议之协议头

    老李分享:HTTP协议之协议头   当我们打开一个网页时,浏览器要向网站服务器发送一个HTTP请求头,然后网站服务器根据HTTP请求头的内容生成当次请求的内容发送给浏览器.你明白HTTP请求头的具体含 ...