八数码问题的问题,有解条件以及求解算法(宽度优先搜索)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了八数码问题的问题,有解条件以及求解算法(宽度优先搜索)相关的知识,希望对你有一定的参考价值。
参考技术A八数码问题:
取一个 3*3 的矩阵,将1-8(或者任意从小到大的八个数字),随机分布在矩阵中,留下一个空格(可以用0代替),作为初始状态;再去一个3*3的矩阵,将1-8(或者任意从小到大的八个数字,其取值必须与初始状态相同),随机分布在矩阵中,留下一个空格(可以用0代替),作为目标状态;对初始状态进行操作,其允许的操作是:将空格向上,下,左,右移动(即将空格与周边的数字进行交换),操作初始状态的矩阵,在若干步后达目标状态。求解其过程为八数码问题。如图:
八数码问题的有解条件:
将矩阵从上到下从左到右的顺序分布成一个数列,并去掉空格,例如:
2 8 3 (0为空格) 分布成数列后:
1 0 4 2 8 3 1 4 7 6 5
7 6 5
如果此 初始状态的数列(矩阵) 的 逆序数 与 目标状态的数列(矩阵) 的 逆序数 的 奇偶性一样 ,则此问题有解。
逆序数 的定义:
有一个数列,在这个数列中任意取一对数字(两个数字),其两个数字在数列中的(从前到后)顺序与数字数值的大小相反,称其为一个逆序。这个数列中所有的逆序的和为逆序数。
证明 :
空格向左右移动时,不改变逆序数的大小;空格向上下移动时,会改变逆序数,改变的幅度为±2或者0 (1)。所以±2或者0的改变幅度不会对逆序数造成奇偶性的改变。所以如果两个矩阵状态如果能互相到达,则必须有其逆序数的奇偶性一样。
(1) 在矩阵中操作空格上下移动时,在数列中的表现是将被交换的数字提前到他前面两个数字之前或者推后到他后面两个数字之后;例如,被交换的数字的下标是 Z 的话,空格向下移动(即被交换数向上移动)后,被交换数在数列中的位置是 Z-2 ;空格向上移动(即被交换数向下移动)后,则被交换数在数列中的位置是 Z+2。这种交换不会影响到被交换数与被它跨过的两个数以外的数字的顺序。比如说:被交换数的位置 Z ,如果空格向上移动,被交换数位置变成 Z+2,但是Z-1处的数字 与 Z 的顺序没有因为 Z 变成 Z+2 而失去了Z-1 在 Z 的前面的顺序,Z-1在Z前面,也在Z+2前面,同样的,Z变成Z+2也不会改变Z与Z+3的顺序。并且,如果有顺序 2 3 4 ,这个顺序的逆序数为0,如果我把4放到2 3前面,变成4 2 3,其逆序数就变成了+2,逆序数就增长了2;如果有顺序 4 2 3,其逆序数为+2,如果我把4放到2 3后面,变成2 3 4,其逆序数就变成了0,逆序数减少了2;如果有6 4 5,其逆序数为+2,如果我把5放在6 4 的前面,变成5 6 4,其逆序数为2,逆序数改变为0。所以改变的幅度为±2,或者0。
八数码问题的解法以及算法(宽度优先):
解法:
空格可以上下左右移动,则父状态可以衍生出若干个子状态(考虑其空格不能越3*3的界以及其不返回到父状态或者父父状态等等之类的情况的话,最大的子状态数量为4 最小为0),这种思路的话实际上这个问题就是一个树的搜索问题,所以用搜索的算法可以解决。
算法(宽度优先):
(1)判断初始状态与目标状态的逆序数的奇偶性来判断是否有解
(2)建立一个OPEN表(队列),用于存放待展开子节点(状态)的父节点
(3)建立一个CLOSED表(队列),用于存放已经展开了子节点的父节点
(4)将初始状态放到OPEN表中,作为第一个
(5)将OPEN表中第一个节点取出(并在OPEN表中删除这个节点),放到CLOSED表中,排在CLOSED表中最后一个,整理好OPEN表
(6)把CLOSED表最后一个节点按照条件(不越界,不返回到父节点)展开其子节点,并且将可能的子节点按照顺序存入OPEN表中
(7)判断此父节点是否为目标状态:
①是,退出,打印答案
②不是,回到步骤(4)
问题:
(1)如果不用数字,而是用毫无关系的符号来填充矩阵,怎么判断是否有解呢?
想法:将初始状态作为参考,将其不同的符号的顺序作为其符号的值的大小,计算出初始状态的逆序数为0,按照初始状态的值大小来判断目标状态的逆序数,然后判断奇偶性进而判断是否有解。
题解UVAUVA10181 15-Puzzle Problem
题外话:
老师:这些题都不难,都只是搜索+剪枝
我:不会……
题面
十五数码问题 保证45步内有解
题解
IDA*入门题目,和八数码问题没差多少
↑抱着天真想法的我
事实上,这题比八数码难了不少……
首先,先像八数码一样把IDA*敲好
然后?
然后你发现样例你都T了
WDNMD
——发现自己样例TLE之后的我
冷静分析一波
首先我们从开始条件入手:
- 如果一开始就符合,那就不用搜了,直接输出空行
- 如果无解,也不用搜了,直接输出“This puzzle is not solvable.”
似乎很科学
问题是怎么判断无解呢
直接上结论,证明在此
先将表格平铺,然后计算N=逆序数对之和,e=空白所在的行数。若N+e为偶数,则有解,反之无解
如此,我们可以减掉很多的无解情况
然后你满心欢喜地交上去
WDNMD*2
我们还要继续剪枝
我们知道,IDA*的效率很大程度上建立在估价函数的好坏上
然后我们看下我这种菜鸡写出来的估价函数(g(x)):
inline int count() {
int cnt=0;
for(int i=0;i<4;i++) {
for(int j=0;j<4;j++) {
if (a[i][j]!=ans[i][j]) {
cnt++;
}
}
}
return cnt;
}
发现了吗?它给出的下界太松了
考虑这样一个事实:
我们要让一个元素归位,最理想的情况是一路畅通无阻地换过去,没有任何浪费的步数
所以,让一个元素归为的最小代价是这个元素当前位置和目标位置的曼哈顿距离
根据上一个结论,我们可以打出这个估价函数:
inline int count() {
int cnt=0;
for(int i=0;i<4;i++) {
for(int j=0;j<4;j++) {
if (!a[i][j]) {
continue;
}
int goal_x=(a[i][j]-1)/4;
int goal_y=(a[i][j]-1)%4;
cnt+=abs(goal_x-i)+abs(goal_y-j);
}
}
return cnt;
}
完美~
交上去,过啦!
再不过就没有天理了
code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int d[4][2]={{1,0},{0,1},{0,-1},{-1,0}};
const int ans[4][4]={{1,2,3,4},
{5,6,7,8},
{9,10,11,12},
{13,14,15,0}};
const char mp[4]={'D','R','L','U'};
inline void read(int &x) {
x=0;
int f=1;
char ch=getchar();
while(ch<'0'||ch>'9') {
if (ch=='-') {
f=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9') {
x=x*10+ch-'0';
ch=getchar();
}
x*=f;
}
int a[4][4];
inline int check() {
for(int i=0;i<4;i++) {
for(int j=0;j<4;j++) {
if (a[i][j]!=ans[i][j]) {
return 0;
}
}
}
return 1;
}
int found;
inline int count() {
int cnt=0;
for(int i=0;i<4;i++) {
for(int j=0;j<4;j++) {
if (!a[i][j]) {
continue;
}
int goal_x=(a[i][j]-1)/4;
int goal_y=(a[i][j]-1)%4;
cnt+=abs(goal_x-i)+abs(goal_y-j);
}
}
return cnt;
}
char sol[50];
void Astar(int step,int x,int y,int max_step,int last_step) {
if (step==max_step) {
if (check()) {
found=1;
}
return;
}
if (found) {
return;
}
for(int i=0;i<4;i++) {
int x1=x+d[i][0],y1=y+d[i][1];
if (i+last_step==3) {
continue;
}
if (x1<0||x1>3||y1<0||y1>3) {
continue;
}
swap(a[x][y],a[x1][y1]);
if (!(count()+step>max_step)&&!found) {
sol[step+1]=mp[i];
Astar(step+1,x1,y1,max_step,i);
}
swap(a[x][y],a[x1][y1]);
}
}
int p[20];
inline int solveable() {
int cnt=0,con=0;
for(int i=0;i<4;i++) {
for(int j=0;j<4;j++) {
p[con++]=a[i][j];
}
}
for(int i=0;i<16;i++) {
if (p[i]==0) {
cnt+=3-(i/4);
} else {
for(int j=0;j<i;j++) {
if (p[j]&&p[j]>p[i]) {
cnt++;
}
}
}
}
return !(cnt&1);
}
int main() {
int t;
read(t);
while(t--) {
int sx,sy;
for(int i=0;i<4;i++) {
for(int j=0;j<4;j++) {
read(a[i][j]);
if (a[i][j]==0) {
sx=i;
sy=j;
}
}
}
if (check()) {
printf("
");
continue;
}
if (!solveable()) {
printf("This puzzle is not solvable.
");
continue;
}
int max_step=1;
for(;;max_step++) {
Astar(0,sx,sy,max_step,-1);
if (found) {
break;
}
}
if (found) {
for(int i=1;i<=max_step;i++) {
putchar(sol[i]);
}
putchar('
');
}
found=0;
}
return 0;
}
以上是关于八数码问题的问题,有解条件以及求解算法(宽度优先搜索)的主要内容,如果未能解决你的问题,请参考以下文章