[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. Pydev--unresolved import:解决办法

    1.右键点击项目,选择Properties-->Pydev-Interpreter/Grammar 2.点击"Click here to configure an interprete ...

  2. 通过Map间接比较两个Json格式是否相同

    首先,我们举个例子来对两个Json格式进行比较 第一个Json格式: {"singleway":[],"multiway":{"channelSlav ...

  3. cobbler自动安装系统

    一.简介 Cobbler是一个快速网络安装linux的服务,而且在经过调整也可以支持网络安装windows.该工具使用python开发,小巧轻便(才15k行python代码),使用简单的命令即可完成P ...

  4. Array对象

    1.    Array定义 Array是可以保存任意变量的.也就是说,第一项可以是一个基本类型,第二项就可以是一个引用类型,长度也是根据内容动态增加的 var arr = new Array(); 当 ...

  5. SQL Server--获取磁盘空间使用情况

    对于DBA来说,监控磁盘使用情况是必要的工作,然后没有比较简单的方法能获取到磁盘空间使用率信息,下面总结下这些年攒下的脚本: 最常用的查看磁盘剩余空间,这个属于DBA入门必记的东西: -- 查看磁盘可 ...

  6. [译]Selenium Python文档:五、Waits等待

    大多数现代web应用都使用了AJAX技术.当浏览器加载一个页面的时候,该页面内的元素可能在不用的时间间隔内进行加载.这使得元素定位变得比较困难:如果一个元素还没有出现在DOM中,定位函数将会抛出一个E ...

  7. VAO VBO IBO大乱炖

    最近对程序中绘制卡顿的问题忍无可忍,终于决定下手处理了.程序涉及的绘制比较多,除了点.线.三角形.多边形.圆柱体之外,还有自组格式模型.开始想全部采用显示列表优化,毕竟效率最高,虽然显示列表存在编译之 ...

  8. TextRank:关键词提取算法中的PageRank

    很久以前,我用过TFIDF做过行业关键词提取.TFIDF仅仅从词的统计信息出发,而没有充分考虑词之间的语义信息.现在本文将介绍一种考虑了相邻词的语义关系.基于图排序的关键词提取算法TextRank [ ...

  9. EventBus通信小能手

     1.EventBus简介 EventBus 是由 greenrobot 组织开发的一个 Android 事件发布/订阅轻量级框架,特点:代码简洁,是一种发布订阅设计模式(观察者设计模式). Even ...

  10. dede织梦数据表字段解释

    提示:常用字段,可以在dede后台->系统->SQL命令行工具,执行sql语句来批量修改 dede_addonarticle   附加文章表   aid  int(11)  文章编号    ...