刚刚开始学习《算法竞赛从入门到进阶》一书,对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*算法对八数码问题的优化。