思路:
① 状态描述:用(al,bl,b)这个三元组来表示位置,al表示在左岸的传教士数目,bl表示在左岸的野人数目,b为1时代表船要从左往右,b为0时代表船要从右往左;
② 启发函数设计:
F(s)=G(s)+H(s)。其中G(s)是从初状态到目前状态s所耗费的移动代价即depth,H(s)是从目前状态s到目标状态所耗费的预测代价,而这个代价我们用s.al+ s.bl来计算。
③ 规则判断条件:通过判断该状态是否被访问及是否在closed表里来做出相应操作。
④ 具体实现:初始化初状态为(m,n,1),目标状态为(0,0,0); 建立一个状态数组father存储每个状态的父状态,close表存储遍历过的结点,ans存储路径。
把初状态存入优先队列,开始遍历,依次判断能做的操作以及是否被访问,是否在已加入的open队列里,若在open队列里,则判断是否需要更新,如不在,则加入队列中,并更新depth和father。
因为优先队列的原因,每次取得最顶元素就是f最小的状态,然后直到找到了目标状态,再依据father遍历回去
代码:
#include<iostream>
#include<queue>
#include<cmath>
#include<vector>
using namespace std;
int operatora[8]={1,2,3,0,0,0,1,2};//做某操作时传教士变化数量
int operatorb[8]={0,0,0,1,2,3,1,1};//做某操作时野人变化数量
int m,n;
int depth[4][4][2];
bool vis[4][4][2]={false};
struct state{
int al,bl,b;
bool operator==(const state& s2){
if(this->al==s2.al&&this->bl==s2.bl&&this->b==s2.b) return true;
else return false;
}
bool operator!=(const state& s2){
if(this->al!=s2.al||this->bl==s2.bl||this->b==s2.b) return true;
else return false;
}
};
state father[4][4][2];
vector<state> closed;
vector<state> ans;
bool isclosed(state s){
for(int i=0;i<closed.size();i++){
if(closed[i]==s) return true;
}
return false;
}
bool ok(state s,int a,int b){
if(s.b==1){
if(s.al>=a&&s.bl>=b&&(((m-s.al)+a)>=((n-s.bl)+b)||((m-s.al)+a)==0||((n-s.bl)+b)==0)&&((s.al-a)>=(s.bl-b)||(s.al-a)==0||(s.bl-b)==0)) return true;
}
else{
if((m-s.al)>=a&&(n-s.bl)>=b&&((s.al+a)>=(s.bl+b)||(s.al+a)==0||(s.bl+b)==0)&&(((m-s.al)-a)>=((n-s.bl)-b)||((m-s.al)-a)==0||((n-s.bl)-b)==0)) return true;
}
return false;
}
int main(){
cin>>m>>n;
state s,end;
s.al=m;s.bl=n;s.b=1;
end.al=0;end.bl=0;end.b=0;
depth[m][n][1]=0;vis[m][n][1]=true;
struct cmp
{
bool operator()(const state &s1,const state &s2)
{
int g1=depth[s1.al][s1.bl][s1.b];
int h1=s1.al+s1.bl;
int g2=depth[s2.al][s2.bl][s2.b];
int h2=s2.al+s2.bl;
return (g1+h1)<(g2+h2);
}
};
priority_queue<state,vector<state>, cmp> q;
q.push(s);
while(!q.empty()){
state top=q.top();
q.pop();
closed.push_back(top);
if(top==end) break;
for(int i=0;i<8;i++){
if(ok(top,operatora[i],operatorb[i])){
state now;
if(top.b==1){
now.al=top.al-operatora[i];
now.bl=top.bl-operatorb[i];
now.b=0;
}
else{
now.al=top.al+operatora[i];
now.bl=top.bl+operatorb[i];
now.b=1;
}
if(vis[now.al][now.bl][now.b]){
if(isclosed(now)) continue;//在close表内,不需要再次遍历
else{//在open表内,算算是否需要更新
if(depth[now.al][now.bl][now.b]>(depth[top.al][top.bl][top.b]+1)){
depth[now.al][now.bl][now.b]=(depth[top.al][top.bl][top.b]+1);
father[now.al][now.bl][now.b]=top;
}
}
}
else{//没被访问过
depth[now.al][now.bl][now.b]=depth[top.al][top.bl][top.b]+1;
father[now.al][now.bl][now.b]=top;
q.push(now);
vis[now.al][now.bl][now.b]=true;
}
}
}
}
while(end.al!=s.al||end.bl!=s.bl||end.b!=s.b){
ans.push_back(end);
end=father[end.al][end.bl][end.b];
}
ans.push_back(s);
for(int i=ans.size()-1;i>=0;i--) printf("(%d,%d,%d)%s",ans[i].al,ans[i].bl,ans[i].b,i==0?"\n":"->");
return 0;
}
tips:
- 在这个作业里学习到了很多,比如说启发式搜索,比如说之前一直没能实现的优先队列与结构体的结合,还自定义排序。cmp函数是参考了其他博主的写法仿照写出来的。
其实这次的代码里基本上只针对于3 3 这个情况,很不完善,期望后续能加以改善。