状态空间法的应用
修道士(Missionaries)和野人(Cannibals)问题
在河的左岸有N个传教士(M)、N个野人(C)和一条船(Boat),传教士们想用这条船把所有人都运过河去,但有以下条件限制:
(1)修道士和野人都会划船,但船每次最多只能运K个人;
(2)在任何岸边野人数目都不能超过修道士,否则修道士会被野人吃掉。
假定野人会服从任何一种过河安排,请规划出一个确保修道士安全过河的计划。
约束条件:① M≧C 任何时刻两岸、船上都必须满足传教士人数不少于野人数(M=0时除外,既没有传教士)② M+C≦K 船上人数限制在K以内
求解:传教士与野人全部安全渡到对岸的解决方案。
解:设N=3,K=2(三个M和三个C,每次渡河二人以下)
L:左岸,R:右岸,
B:是否有船(0:无船,1:有船)
①状态表示
定义:用三元组(ML,CL,BL)表示左岸状态,其中:
0≦ML,CL≦3,BL∈{0,1}
如:(0,3,1)表示左岸有三个野人,船在左岸。
M&C的问题描述: 从(3,3,1)到(0,0,0)的状态转换
状态空间:32 种状态,其中:
12种不合理状态:如(1,0,1)说明右岸有2个M,3个C;4种不可能状态:如(3,3,0)说明所有M和C都在左岸,而船在右岸
∴可用的状态共16种,组成合理的状态空间
②操作集
定义:Pmc操作:从左岸划向右岸
Qmc操作:从右岸划向左岸
船上人数组合(m,c)共5种应P,Q二种操作
∴系统共有 5×(1,0),(1,1),(2,0),(0,1),(0,2)
∵每一种船上的人数组合同时对2=10种操作(规则)
如:P10:if (ML,CL,BL=1) then (ML-1,CL,BL-1)
如果船在左岸,那么一个传教士划船到右岸
Q01:if (ML,CL,BL=0) then (ML,CL+1,BL+1)
如果船在右岸,那么一个野人划船回到左岸
总共有10种操作
F={P10, P20, P11, P01, P02, Q 10, Q 20, Q11, Q 01, Q 02}
③控制策略
最短路径有4条,由11次操作构成。
(P11、Q10、P02、Q01、P20、Q11、P20、Q01、P02、Q01、P02)
(P11、Q10、P02、Q01、P20、Q11、P20、Q01、P02、Q10、P11)
(P02、Q01、P02、Q01、P20、Q11、P20、Q01、P02、Q01、P02)
(P02、Q01、P02、Q01、P20、Q11、P20、Q01、P02、Q10、P11)
④状态空间图
状态空间图是一个有向图,图中的节点代表状态,节点之间的连线代表操作,箭头代表状态的转换方向。
package cn.itAI.YeRenAndXiuDaoShi;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;
/**
* 野人与修道士
* @author
*
*/
/**
* 数据结构分析:使用List对象存储code节点信息
* @author dell-
*
*/
public class Main {
static int N;//N个野人,N个修道士
static int K;//船上至多有K个人
static int count=0;
static List<code>s=new ArrayList<code>();
public static void main(String[] args) {
Scanner in=new Scanner(System.in);
System.out.println("请输入野人/传教士:");
N=in.nextInt();
System.out.println("请输入船至多载人的个数:");
K=in.nextInt();
code c=new code(N,N,1);//1:船在左岸 0:船在右岸
s.add(c);
bfs(c);
System.out.println("count:"+count);
}
public static boolean check(List<code>s,code c){//合法状态的判断
//1.不可重复
if(!s.isEmpty()){//包含此元素,表中含重复元素
for(int i=0;i<s.size();i++){
if(s.get(i).Boat==c.Boat&&s.get(i).leftC==c.leftC&&s.get(i).leftM==c.leftM){
return false;
}
}
}
//2.是否满足m>=c
if((c.leftM!=0&&c.leftM<c.leftC)){//((N-c.leftM!=0)&&(N-c.leftM<c.leftC))){
return false;
}
if((N-c.leftM!=0)&&(N-c.leftM<N-c.leftC)){
return false;
}
return true;
}
/**
* 深搜:输出所有满足序列
*/
public static void bfs(code c){
if(c.leftC==0&&c.leftM==0&&c.Boat==0){
for(int i=0;i<s.size();i++){
s.get(i).toGetString();
}
count++;
System.out.println("==============================");
return;
}
/**
* 判断船上至多野人和修道士的数量
*/
int k,k1;
if(c.Boat==1){
k=c.leftM>=K?K:c.leftM;
k1=c.leftC>=K?K:c.leftC;
}
else{
k=(N-c.leftM)>=K?K:(N-c.leftM);
k1=(N-c.leftC)>=K?K:(N-c.leftC);
}
/**
* 枚举过河的所有情况
*/
for(int i=0;i<=k;i++){
int m=i;//船上修道士数量
for(int j=0;j<=((K-m)>=k1?k1:(K-m));j++){
int n=j;//船上野人数量
int tleftM,tleftC,tflag;
if(m==0&&n==0) continue;
code ct;
if(c.Boat==1){//当前状态是合法条件,船在左岸可以开往右岸
tflag=0;//m,n是船上的野人和修道士
tleftM=N-((N-c.leftM)+m);//
tleftC=N-((N-c.leftC)+n);
ct=new code(tleftM,tleftC,tflag);
if(check(s,ct)){
s.add(ct);//加入表
bfs(ct);
s.remove(ct);//回溯
}
}else{//右岸-》左岸
tflag=1;
tleftM=N-((N-c.leftM)-m);
tleftC=N-((N-c.leftC)-n);
ct=new code(tleftM,tleftC,tflag);
if(check(s,ct)){
s.add(ct);
bfs(ct);
s.remove(ct);
}
}
}
}
return ;
}
}
class code{
int leftM;//左岸修道士
int leftC;//左岸野人
int Boat;//船的状态
public code(int leftM,int leftC,int Boat){
this.leftC=leftC;
this.leftM=leftM;
this.Boat=Boat;
}
public void toGetString(){
System.out.print("修道士:"+this.leftM+" 野人: "+this.leftC+" 船的方向:");
if(this.Boat==1){
System.out.println("左岸-->右岸");
}else{
System.out.println("右岸-->左岸");
}
}
}