#include<iostream>
#include<algorithm>
using namespace std;
struct node{
char num[9];
int gvalue;
int hvalue;
int fvalue;
char action;
node *parent;
node *next;
};
node *openlist,*closelist,*bestnode;
char start[9];
char target[]={'1','2','3','4','5','6','7','8','x'};
bool isEmpty(node *p){
if(p->next==NULL) return true;
return false;
}
bool isSolved(){
int s,t,i,j;
s=t=0;
for(i=0;i<9;i++)
for(j=i;j>=0;j--){
if((start[i]<start[j])&&start[i]!='x'&&start[j]!='x') s++;
if((target[i]<target[j])&&target[i]!='x'&&target[j]!='x') t++;
}
if((s%2)==(t%2)) return true;
return false;
}
void print_result(node* p){
if(p!=NULL){
print_result(p->parent);
cout<<p->action;
}
}
int diffnum(node *p){
int i,count=0;
for(i=0;i<9;i++)
if(p->num[i]!=target[i]) count++;
return count;
}
int findX(char num[]){
int i;
for(i=0;i<9;i++)
if(num[i]=='x') return i;
}
void move_up(node *&p){
int pos=findX(p->num);
if(pos>2) {swap(p->num[pos],p->num[pos-3]);p->action='u';return;}
return ;
}
void move_down(node *&p){
int pos=findX(p->num);
if(pos<6) {swap(p->num[pos],p->num[pos+3]);p->action='d';return;}
return;
}
void move_left(node *&p){
int pos=findX(p->num);
if(pos!=0&&pos!=3&&pos!=6){swap(p->num[pos],p->num[pos-1]);p->action='l';return;}
return;
}
void move_right(node *&p){
int pos=findX(p->num);
if(pos!=2&&pos!=5&&pos!=8){swap(p->num[pos],p->num[pos+1]);p->action='r';return;}
return;
}
void operate(node *&p,int op){
switch(op){
case 1:move_up(p);break;
case 2:move_down(p);break;
case 3:move_left(p);break;
case 4:move_right(p);break;
default:return;
}
}
void expand(node* p){
int op,i;
node* pNode;
node* p1;
for(op=1;op<=4;op++){
pNode=new node;
for(i=0;i<9;i++)
pNode->num[i]=p->num[i];
operate(pNode,op);
pNode->gvalue=p->gvalue+1;
pNode->hvalue=diffnum(pNode);
pNode->fvalue=pNode->gvalue+pNode->hvalue;
pNode->parent=p;
pNode->next=NULL;
if(bestnode==NULL) {bestnode=pNode;bestnode->next=NULL;}
else {pNode->next=bestnode;bestnode=pNode;}
}
}
struct node* openlist_insert(node *p){
node* temp=openlist;
node* pre=openlist;
if(temp==NULL) {openlist=p;openlist->next=NULL;return openlist;}
while(temp!=NULL){
if(p->fvalue>temp->fvalue){
pre=temp;
temp=temp->next;
}
else if(pre==temp){p->next=temp;return p;}
else {pre->next=p;p->next=temp;return openlist;}
}
pre->next=p;p->next=NULL;
return openlist;
}
struct node* Del(node* p,node* pList){
node* temp=pList;
node* pre=pList;
int i,count=0;
while(temp!=NULL){
count=0;
for(i=0;i<9;i++)
if(p->num[i]==temp->num[i]) count++;
if(count==9) {
if(temp==pList) {pList=pList->next;delete temp;return pList;}
else
{pre->next=temp->next;delete temp;return pList;}
}
else pre=temp;
temp=temp->next;
}
}
struct node *InList(node *p,node *pList){
node *temp=pList;
int i,k;
while(temp!=NULL){
k=0;
for(i=0;i<9;i++)
if(p->num[i]==temp->num[i]) k++;
if(k==9) return temp;
temp=temp->next;
}
return NULL;
}
bool IsGoal(node* p){
int i;
for(i=0;i<9;i++)
if(p->num[i]!=target[i]) return false;
return true;
}
void AStar(){
node *n=NULL,*m=NULL,*pNode=NULL;
closelist=NULL;
while(openlist!=NULL){
n=openlist;
if(IsGoal(n)) {print_result(n);return;}
openlist=openlist->next;
n->next=closelist;
closelist=n;
expand(n);
while(bestnode!=NULL){
m=bestnode;
bestnode=bestnode->next;
if(pNode=InList(m,openlist)){
if(m->fvalue<pNode->fvalue){
// pNode->fvalue=m->fvalue;
openlist=Del(pNode,openlist);
openlist=openlist_insert(m);
}
else delete m;
}
else if(pNode=InList(m,closelist)){
if(m->fvalue<pNode->fvalue){cout<<m->hvalue<<endl;
closelist=Del(pNode,closelist);
openlist=openlist_insert(m);
}else delete m;
}
else openlist=openlist_insert(m);
}
}
}
int main(){
node *p=new node;
openlist=new node;
int i;
for(i=0;i<9;i++)
cin>>start[i];
for(i=0;i<9;i++)
p->num[i]=start[i];
p->gvalue=0;
p->hvalue=diffnum(p);
p->fvalue=p->gvalue+p->hvalue;
p->parent=NULL;
p->next=NULL;
openlist=p;
if(isSolved())
AStar();
else cout<<"unsolved"<<endl;
}
用组合数学的方法可以快速地进行判断,例如SOJ 2061题 2360题中都是关于此类的问题。但八数码的判断方法比他们简单多了。
本文用的方法是计算排列的逆序数值,以2 3 1 5 8 4 6 7 5 为例子,5表示的是空格,不计算,那么求23158467 的逆序值为
0 + 0 + 2 (1<2 1<3 ) + 0 + 0 + 2 ( 4<5 4<8) + 1 ( 6<8 ) + 1 ( 7<8 ) = 6
目标状态1 2 3 4 5 6 7 8 9 的逆序自然就是0。
两个状态之间是否可达,可以通过计算两者的逆序值,若两者奇偶性相同则可达,不然两个状态不可达。
简单证明一下:
l 和 r 操作,不会影响状态的逆序值,因为只会改变个位数(空格的位置)。
u和d操作是使某个位置的数字 右/左 移两位。 由于数字序列的每一次移动会使逆序值奇偶性改变,所以移动两次后奇偶性不变。
所以 四个操作均不会影响序列的奇偶性。