传教士野人过河问题---Java版本

本文出处:http://blog.csdn.net/xizhibei

=============================


M个传教士和C个野人(Missionaries and Cannibals)过河,显然必须要M>=C,只有一艘载重为2的小船,野人会听从传教士的安排,并且野人和传教士都会划船,但是,在河的两岸不能出现野人比传教士多的情况,否则野人就会吃传教士。


好了,现在,编程解决这个问题,安排他们全部过河,并保证传教士的安全。


嗯,据说这是个经典的人工智能问题,显然,解决方法是搜索状态空间。于是各种方法就出来了,BFS、DFS还有大名鼎鼎的A*。


首先,规定下状态空间两岸以及船上的传教士还有野人的数量,船的朝向以及,父状态的Index。


public int lm,lc;//我用left以及right表示这边的岸和对岸了
public int rm,rc;
public int m_in_ship;
public int c_in_ship;
public int parent;
public int direction;//船的朝向

另外,船一旦上岸后,就会有人员的变动,于是,仔细分析下情况,不难发现,船上只有6种情况:(我用一对数表示船上传教士还有野人的数量)

1、(0,0)开始状态

2、(0,1)

3、(1,0)

4、(1,1)

5、(2,0)

6、(0,2)


以上,除了开始状态,2-6状态都会在每次抵达岸边后开始互相转换,这里规定不能转换为相同状态,因为这样的话就无意义了,等于坐了无用功,对解决问题没有帮助。


好了,针对每次的状态转换,我用一个四维数组来表示:

private static int[][][][] Transform = {
		{//0
			{//0,0
				{2,0},{0,2},{1,0},{0,1},{1,1}
			},
			{//0,1
				{1,1},{2,0},{0,2},{1,0}
			},
			{//0,2
				{1,1},{2,0},{1,0},{0,1}
			}
		},
		{//1
			{//1,0
				{1,1},{2,0},{0,2},{0,1}
			},
			{//1,1
				{2,0},{0,2},{1,0},{0,1}
			},
			{//1,2
				{}
			}
		},
		{//2
			{//2,0
				{1,1},{0,2},{1,0},{0,1}
			},
			{//2,1
				{}
			},
			{//2,2
				{}
			}
		}
	};

于是产生子状态的代码就很容易写了:

public List getChildren(State s,int parent){
		List cs = new ArrayList();
		int m = s.m_in_ship;
		int c = s.c_in_ship;
		int[][] trans = Transform[m][c];
		if(s.direction == State.TO){//到对岸
			for(int i = 0;i < trans.length;i++){
				if(s.rm >= trans[i][0] - m && s.rc >= trans[i][1] - c)
					cs.add( new State(s.lm,s.lc,s.rm + m - trans[i][0],
							s.rc + c - trans[i][1], 
							trans[i][0], trans[i][1],parent,State.FROM) );
			}
		}else{//State.FROM //开始或从对岸回来
			for(int i = 0;i < trans.length;i++){
				if(s.lm >= trans[i][0] - m && s.lc >= trans[i][1] - c)
					cs.add( new State(s.lm + m - trans[i][0],s.lc + c - trans[i][1],
							s.rm,s.rc, 
							trans[i][0], trans[i][1],parent,State.TO) );
			}
		}
		return cs;
	}

最后,实现A*搜索算法即可,需要注意的是,我认为安全的状态是两边的传教士数量大于等于野人的,或者,一边野人数量小于等于2并且没有传教士(野人数量大于2的话,就不安全了)

public void search(int M,int C){
		List states = new ArrayList();
		//List q = new ArrayList();
		PriorityQueue q = new PriorityQueue();//这里用优先队列实现状态的选取,你也可以试试BFS,用队列实现即可
		HashSet sset = new HashSet();//用来判断状态是否重复

		State init = new State(M,C,0,0,0,0,-1,State.START);
		q.add(init);
		sset.add(init);

		while(!q.isEmpty()){
			//State s = q.get(0);
			//q.remove(0);
			State s = q.poll();
			states.add(s);

			if(s.lc == 0 && s.lm == 0){
				System.out.println("Got it!");
				break;
			}

			List cs = getChildren(s,states.size() - 1);
			for(int i = 0;i < cs.size();i++){
				State c = cs.get(i);
				State np= c.getNoParentState();
				if(((c.lc <= c.lm || c.lc <= 2 && c.lm == 0) //判断是否安全
						&& (c.rc <= c.rm || c.rc <= 2 && c.rm == 0)) 
						&& !sset.contains(np)){
					q.add(c);
					sset.add(np);
				}
			}
			if(q.size() > 10000){//状态太多就没必要搜索了
				System.out.println("Error!!!");
				return;
			}
		}

		List o = new ArrayList();
		int idx = states.size() - 1;
		System.out.println("Total states: " +(idx+1));
		while(idx != -1){
			State s = states.get(idx);
			o.add(s.toString());
			//System.out.println(s.toString());
			idx = s.parent;
		}
		System.out.println("Total steps: " + (o.size() - 1));//去除开始状态
		for(int i = o.size() - 1;i >= 0;i--){
			System.out.println(o.get(i));
		}
	}


好了,最后把状态的完整实现贴出来:

private class State implements Comparable{
		public static final int TO = 0;
		public static final int FROM = 1;
		public static final int START = 2;
		public int lm,lc;
		public int rm,rc;
		public int m_in_ship;
		public int c_in_ship;
		public int parent;
		public int direction;
		public State(int lm,int lc,int rm,int rc,int m_in_ship,int c_in_ship,int parent,int direction){
			this.lm = lm;
			this.lc = lc;
			this.rm = rm;
			this.rc = rc;
			this.m_in_ship = m_in_ship;
			this.c_in_ship = c_in_ship;
			this.parent = parent;
			this.direction = direction;
		}
		public State getNoParentState(){//显然,需要把父节点的idx去掉才能判断是否重复
			return new State(lm,lc,rm,rc,m_in_ship,c_in_ship,-1,direction);
		}

		public String toString(){
			if(direction == State.TO)
				return lm + "," + lc + " =" + m_in_ship + "," + c_in_ship + "=> "+ rm + "," + rc;
			else if(direction == State.FROM)
				return lm + "," + lc + " <=" + m_in_ship + "," + c_in_ship + "= "+ rm + "," + rc;
			else
				return lm + "," + lc + " =" + m_in_ship + "," + c_in_ship + "= "+ rm + "," + rc;
		}
		
		@Override
		public int compareTo(State s) {//A*实现的关键,状态的比较,用来启发搜索
			int sum1 = 2*lm + 2*lc + m_in_ship + c_in_ship;
			int sum2 = 2*s.lm + 2*s.lc + s.m_in_ship + s.c_in_ship;
			return sum1 - sum2;
		}
	}

好了,我试了下结果还行,但是仍不敢保证完全正确,还望各位指教。




你可能感兴趣的:(Algorithm,Java)