广搜与双向广搜解决八数码问题

刚刚开始学习《算法竞赛从入门到进阶》一书,对c++也不是特别了解,算是跨考生对编程知识的学习吧。
在此先提出对任务一的解决,任务二的解决也就是改进
在此先提出对任务一的解决,任务二的解决也就是修改几处地方罢了。

  • 状态判重方法:利用康托展开函数进行展开,利用哈希表对其全排列进行排序。
    在这里插入图片描述
  • 展开式:
    在这里插入图片描述
  • 广搜方法:
#include<bits/stdc++.h>
using namespace std;
const int LEN=362880;
#define Swap(a,b){int tmp=a;a=b;b=tmp;} 
struct node{int state[9];int dis;};
long int fac[]={1,1,2,6,24,120,720,5040,40320,362880};//X+=a[i]*(i-1)!int dir[4][2]={    {0,1},    {0,-1},    {1,0},    {-1,0}};
int vis[LEN]={0};
int start[9];
int goal[9];
bool Cantor(int str[],int n)//康托展开函数,用于判定状态是否重复,通过哈希转换成n个数的n!个全排并编号
{    
long res=0;    
for(int i=0;i<n;i++){        
int counted=0;        
for(int j=i+1;j<n;j++){            if(str[i]>str[j])++counted;//当前未访问元素排在第几个        
}        
res+=counted*fac[n-i-1];    
}    
if(!vis[res]){        
vis[res]=1;        
return true;    
}    
else return false;
}
int BFS(){    
queue<node>q;    
node pre;    
memcpy(pre.state,start,sizeof(pre.state));//复制起始状态存入pre.state    
pre.dis=0;    
Cantor(pre.state,9);//标记已经访问    
q.push(pre);    
while(!q.empty()){        
pre=q.front();        
q.pop();        
int z;        
for(z=0;z<9;z++)if(pre.state[z]==0)break;        
int x=z%3;        
int y=z/3;        
for(int i=0;i<4;i++){           
int newx=x+dir[i][0];            
int newy=y+dir[i][1];            
int newz=newx+3*newy;            
if(newx<3&&newx>=0&&newy<3&&newy>=0){                
node next;                
memcpy(&next,&pre,sizeof(struct node));                
Swap(next.state[z],next.state[newz]);                
next.dis++;                
if(memcmp(next.state,goal,sizeof(goal))==0)return 
next.dis;                
if(Cantor(next.state,9))q.push(next);            
}        
}    
}    
return -1;
}
int main(){    
for(int i=0;i<9;i++)cin>>start[i];    
for(int i=0;i<9;i++)cin>>goal[i];    
int num=BFS();    
if(num!=-1)cout<<num<<endl;    
else cout<<"impossible"<<endl;    
return 0;
}
  • 双向广搜方法(优点:比广搜方便,且时间复杂度减半;缺点:必须知道起点和终点的状态),最大的改动就在于康托函数(Cantor)不只用于判重,并且用于判定是否搜索完毕。
#include<bits/stdc++.h>
using namespace std;
#define Swap(a,b){int tmp=a;a=b;b=tmp;}//交换函数
struct node{int state[9];int dis;};//state为棋局当前摆放状态,dis用来记录与队首节点之间的距离
const int LEN=362880;//判重数组长度为9!
int visit[LEN]={0};//注意visit为1时是正向队列,visit为2时是反向队列
long int factory[]={1,1,2,6,24,120,720,5040,40320,362880};//省事,直接写出n!
int dir[4][2]={    {0,1},    {0,-1},    {1,0},    {-1,0}};//方向向量,用于定义对当前节点的四个方向的扩展
int start[9];//起始状态
int goal[9];//目标状态
bool found=false;//用于判定是否搜索完毕
bool Cantor(int str[],int n,bool flag){//flag用于区分两个队列,true为Q1    
int res=0;    
for(int i=0;i<9;i++){       
 int count=0;        
 for(int j=i+1;j<n;j++){if(str[i]>str[j])++count;}       
  res+=count*factory[n-i-1];  
  }    
  if(visit[res]==0){//若未访问过就访问位置1/2        
  if(flag){            
  visit[res]=1;            
  return true;        
  }        
  else{            
  visit[res]=2;            
  return true;        
  }    
  }    
  else if(flag==false&&visit[res]==1){found=true;return false;}//若访问的节点已在正向队列中且反向队列正在访问该节点,则搜索完毕    
  else if(flag==true&&visit[res]==2){found=true;return false;}//若访问的节点已在反向队列中且正向队列正在访问,则搜索完毕    
  else return false;}
  int DBFS(){    
  node now,next;    
  queue<node>q1,q2;//q1为正向队列,q2为反向队列
  int res1,res2;    
  memcpy(now.state,start,sizeof(start));//将起始状态复制到now节点的内存中    
  now.dis=0;    
  Cantor(now.state,9,true);    
  q1.push(now);    
  memcpy(now.state,goal,sizeof(goal));    
  now.dis=0;    
  Cantor(now.state,9,false);    
  q2.push(now);    
  while(!q1.empty()||!q2.empty()){        
  if(!q1.empty()){            
  now=q1.front();            
  q1.pop();            
  res1=now.dis;            //cout<<"res1:"<<res1<<endl;            
  int z;            
  for(z=0;z<9;z++)if(now.state[z]==0)break;//找到状态里0的位置            
  int x=z%3;//化为二维横坐标            
  int y=z/3;//化为二维纵坐标
  for(int i=0;i<4;i++){                
  int newx=x+dir[i][0];                
  int newy=y+dir[i][1];                
  int nz=newx+newy*3;//重新化为一维坐标                
  if(newx>=0&&newx<3&&newy>=0&&newy<3){                
  memcpy(&next,&now,sizeof(struct node));                
  Swap(next.state[z],next.state[nz]);                
  next.dis++;                
  if(Cantor(next.state,9,true))q1.push(next);                
  if(found){return res1+res2+2;}                
  }            
  }        
  }        
  if(!q2.empty()){            
  now=q2.front();            
  q2.pop();            
  res2=now.dis;            //cout<<"res2:"<<res2<<endl;            
  int z;            
  for(z=0;z<9;z++)if(now.state[z]==0)break;            
  int x=z%3;            
  int y=z/3;            
  for(int i=0;i<4;i++){                
  int newx=x+dir[i][0];                
  int newy=y+dir[i][1];                
  int nz=newx+newy*3;                
  if(newx>=0&&newx<3&&newy>=0&&newy<3){                
  memcpy(&next,&now,sizeof(struct node));                
  Swap(next.state[z],next.state[nz]);                
  next.dis++;                
  if(Cantor(next.state,9,false))q2.push(next);                
  if(found){return res1+res2+2;}                
  }            
  }        
  }    
  }    
  return -1;
  }
  int main(){    
  for(int i=0;i<9;i++)cin>>start[i];    
  for(int i=0;i<9;i++)cin>>goal[i];    
  int num=DBFS();    
  if(num!=-1)cout<<num<<endl;    
  else cout<<"impossible"<<endl;    
  return 0;
  }

结语:广搜方法在书中已有代码,而双向广搜没有,所以通过查阅资料自己写出来,还是挺有成就感的。其实还可以通过A*算法进行优化,但毕竟水平有限,还没有接触过贪心算法,后面有机会再写A*算法对八数码问题的优化。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值