八数码简介
八数码问题也称为九宫问题。在3×3的棋盘,摆有八个棋子,每一个棋子上标有1至8的某一数字,不同棋子上标的数字不同样。棋盘上另一个空格,与空格相邻的棋子能够移到空格中。要求解决的问题是:给出一个初始状态和一个目标状态,找出一种从初始转变成目标状态的移动棋子步数最少的移动步骤。所谓问题的一个状态就是棋子在棋盘上的一种摆法。棋子移动后,状态就会发生改变。解八数码问题实际上就是找出从初始状态到达目标状态所经过的一系列中间过渡状态。
求解八数码问题要懂得的知识
1.康托展开,八数码在交换的过程中状态会改变,康托展开用于求出某一格局的状态数。唐托展开公式:X=a[n](n-1)!+a[n-1](n-2)!+...+a[i](i-1)!+...+a[2]1!+a[1]*0!其中a[i]为当前未出现的元素中是排在第几个(从0开始),并且0<=a[i]<i(1<=i<=n)
2.逆序数,通过求初始格局和目标格局逆序数,然后在比较两者的逆序数的奇偶性是否相同,如果奇偶性相同,则可以从初始格局变到目标格局。否则,不可达。逆序数求法:假设有n个元素的排列,a[i]为排列中的元素(0<=i<n),求在排列中比a[i]小的数的和。比如有这么一个排列:54120,5后面比它小的数有3个(0不算,后同);4后面比它小的数有2个;1后面比它小的数有0个;2后面比它的小的数有0个;所以,该排列的逆序数=3+2+0+0=5.
3.在八数码中0位置的数与它相邻的上下左右的位置的数交换不会影响这个格局的逆序数的奇偶性。比如有以下格局:
4 | 6 | 7 |
---|---|---|
5 | 8 | 1 |
2 | 3 | 0 |
如果2这个数和5这个数交换就会导致格局的逆序数的奇偶性的变化,而如果0和1交换就不会导致奇偶性的变化。我们要保证移动前和移动后逆序数的奇偶性不改变,用一维数组来存储格局,要注意索引。
广度优先搜索求解八数码问题代码
#include <iostream>
#include <string>
#include <cstring>
#include <cmath>
#include <vector>
#include <queue>
#include <set>
using namespace std;
#define N 9
int jc[N+1]={1,1,2,6,24,120,720,5040,40320,362880};//0-9的阶乘
typedef struct data
{
int arr[N];//格局
int hash;//存储某一格局的哈希
int pos;//0当前位置
int step;//记录步数
}Node;
int dir[4][2]={
{0,1},
{1,0},
{0,-1},
{-1,0}
};
/**
* 康托展开
*/
int cantor(int arr[N])
{
int i,j;
int sum=0;
for( i=0;i<N;i++)
{
int nmin=0;
for(j=i+1;j<N;j++)
{
if(arr[i]>arr[j])
nmin++;
}
sum+=(nmin*jc[N-i-1]);
}
return sum;
}
/**
*数据交换
*/
void swap(int *arr,int i,int j)
{
int t=arr[i];
arr[i]=arr[j];
arr[j]=t;
}
/**
* 打印数组,测试用
*/
void printArray(int * arr)
{
int i,j;
for (i=0;i<N;i++)
{
if(i%3==0)
cout<<"\n";
cout<<arr[i]<<" ";
}
cout<<endl;
}
/**
* 复制数组
*/
void copyArray(int src[N],int target[N])
{
int i;
for(i=0;i<N;i++)
{
target[i]=src[i];
}
}
/**
* 广搜
*/
int bfs(int arr[N],int sHash,int tHash)
{
if(sHash==tHash)
return 0;
int i,j;
queue<Node> q;
set<int> setHash;
Node now,next;
copyArray(arr,now.arr);
int pos=0;
for (i=0;i<N;i++)
{
if(arr[i]==0)
break;
pos++;
}
now.hash=sHash;
now.step=0;
next.step=0;
now.pos=pos;
q.push(now);
setHash.insert(now.hash);
while(!q.empty())
{
now=q.front();
q.pop();
for (i=0;i<4;i++)
{
int offsetX=0,offsetY=0;
offsetX=(now.pos%3+dir[i][0]);
offsetY=(now.pos/3+dir[i][1]);
if(offsetX>=0&&offsetX<3&&offsetY<3&&offsetY>=0)
{
copyArray(now.arr,next.arr);//每次换方向,就复制
next.step=now.step;
next.step++;
swap(next.arr,now.pos,offsetY*3+offsetX);
next.hash=cantor(next.arr);
next.pos=(offsetY*3+offsetX);
int begin=setHash.size();
setHash.insert(next.hash);
int end=setHash.size();
if(next.hash==tHash){
return next.step;
}
if(end>begin)
{
q.push(next);
}
}
}
}
return -1;
}
/**
*求逆序数
*/
int inversion(int arr[N])
{
int sum=0;
for(int i=0;i<N;++i)
{
for(int j=i+1;j<N;++j)
{
if(arr[j]<arr[i]&&arr[j]!=0)//不与0比较
{
sum++;
}
}
}
return sum;
}
int main(int argc, char **argv)
{
int i,j;
string s="123456780";
string t="123456078";
int is[N],it[N];//源int数组和目标int数组
for (i=0;i<9;i++)
{
if (s.at(i)>='0'&&s.at(i)<='8')
{
is[i]=s.at(i)-'0';
}else
{
is[i]=0;
}
if (t.at(i)>='0'&&t.at(i)<='8')
{
it[i]=t.at(i)-'0';
}else
{
it[i]=0;
}
}
int sHash,tHash;//源哈希和目标哈希
sHash=cantor(is);
tHash=cantor(it);
int inver1=inversion(is);//求初始格局的逆序数
int inver2=inversion(it);//求目标格局的逆序数
if((inver1+inver2)%2==0)
{
int step=bfs(is,sHash,tHash);
cout<<step<<endl;
}
else
{
cout<<"无法从初始状态到达目标状态"<<endl;
}
return 0;
}
原文地址:https://www.cnblogs.com/luoyesiqiu/p/11235238.html