八数码简介
八数码问题也称为九宫问题。在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字,不一样棋子上标的数字不一样样。棋盘上另外一个空格,与空格相邻的棋子可以移到空格中。要求解决的问题是:给出一个初始状态和一个目标状态,找出一种从初始转变成目标状态的移动棋子步数最少的移动步骤。所谓问题的一个状态就是棋子在棋盘上的一种摆法。棋子移动后,状态就会发生改变。解八数码问题实际上就是找出从初始状态到达目标状态所通过的一系列中间过渡状态。ios
求解八数码问题要懂得的知识
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位置的数与它相邻的上下左右的位置的数交换不会影响这个格局的逆序数的奇偶性。好比有如下格局:spa
4 | 6 | 7 |
---|---|---|
5 | 8 | 1 |
2 | 3 | 0 |
若是2这个数和5这个数交换就会致使格局的逆序数的奇偶性的变化,而若是0和1交换就不会致使奇偶性的变化。咱们要保证移动前和移动后逆序数的奇偶性不改变,用一维数组来存储格局,要注意索引。code
广度优先搜索求解八数码问题代码
#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; }