用A*算法实现传道士野人渡河问题

在这里插入图片描述
思路:
① 状态描述:用(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 这个情况,很不完善,期望后续能加以改善。
  • 1
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 好的,以下是使用A*算法解决野人传教士问题的Java代码: 首先,定义一个节点类,表示当前状态和移动到此状态的代价: ```java public class Node implements Comparable<Node> { int[] state; // 当前状态 Node parent; // 父节点 int cost; // 从起点到当前节点的代价 int heuristic; // 启发式函数值 public Node(int[] s, Node p, int c, int h) { state = s; parent = p; cost = c; heuristic = h; } // 比较两个节点的代价和启发式函数值,用于优先队列排序 public int compareTo(Node other) { return (cost + heuristic) - (other.cost + other.heuristic); } } ``` 然后,定义一个搜索函数,使用优先队列实现A*算法: ```java public static void aStarSearch() { int[] init = {3, 3, 1}; // 初始状态 int[] goal = {0, 0, 0}; // 目标状态 PriorityQueue<Node> queue = new PriorityQueue<Node>(); // 优先队列 queue.add(new Node(init, null, 0, heuristic(init))); // 将初始状态加入队列 while (!queue.isEmpty()) { Node current = queue.poll(); // 取出代价最小的节点 // 到达目标状态,输出路径 if (Arrays.equals(current.state, goal)) { printPath(current); return; } // 扩展当前节点,生成所有可能的子节点 ArrayList<Node> children = expand(current); // 将子节点加入队列 for (Node child : children) { queue.add(child); } } System.out.println("No solution found."); } ``` 接下来,实现启发式函数,计算当前状态到目标状态的估计代价: ```java public static int heuristic(int[] state) { int cost = 0; // 野人传教士分别需要过河的数量 int num_missionaries = state[0]; int num_cannibals = state[1]; // 判断野人是否会攻击传教士,如果会则需要多一艘船 if ((num_missionaries > 0 && num_missionaries < num_cannibals) || (num_missionaries < 0 && num_missionaries > num_cannibals)) { cost += 1; } // 计算野人传教士需要过河的次数 cost += (Math.abs(num_missionaries) + Math.abs(num_cannibals) + state[2] - 1) / 2; return cost; } ``` 最后,实现扩展函数,生成所有可能的子节点: ```java public static ArrayList<Node> expand(Node node) { ArrayList<Node> children = new ArrayList<Node>(); int[] state = node.state; int boat = state[2]; // 生成所有可能的移动方式 for (int i = 0; i <= 2; i++) { for (int j = 0; j <= 2; j++) { if (i + j <= 2 && i + j >= 1) { int[] new_state = state.clone(); new_state[0] += (boat == 1 ? -i : i); new_state[1] += (boat == 1 ? -j : j); new_state[2] = 1 - boat; if (isValid(new_state)) { // 判断移动是否合法 Node child = new Node(new_state, node, node.cost + 1, heuristic(new_state)); children.add(child); } } } } return children; } public static boolean isValid(int[] state) { int num_missionaries = state[0]; int num_cannibals = state[1]; // 判断野人是否会攻击传教士 if ((num_missionaries > 0 && num_missionaries < num_cannibals) || (num_missionaries < 0 && num_missionaries > num_cannibals)) { return false; } // 判断过河的人数是否超过船的容量 if (Math.abs(num_missionaries) + Math.abs(num_cannibals) > 2) { return false; } // 判断传教士数量是否为负数 if (num_missionaries < 0 || num_cannibals < 0) { return false; } return true; } ``` 完整代码如下: ```java import java.util.*; public class AStar { public static void main(String[] args) { aStarSearch(); } public static void aStarSearch() { int[] init = {3, 3, 1}; // 初始状态 int[] goal = {0, 0, 0}; // 目标状态 PriorityQueue<Node> queue = new PriorityQueue<Node>(); // 优先队列 queue.add(new Node(init, null, 0, heuristic(init))); // 将初始状态加入队列 while (!queue.isEmpty()) { Node current = queue.poll(); // 取出代价最小的节点 // 到达目标状态,输出路径 if (Arrays.equals(current.state, goal)) { printPath(current); return; } // 扩展当前节点,生成所有可能的子节点 ArrayList<Node> children = expand(current); // 将子节点加入队列 for (Node child : children) { queue.add(child); } } System.out.println("No solution found."); } public static int heuristic(int[] state) { int cost = 0; // 野人传教士分别需要过河的数量 int num_missionaries = state[0]; int num_cannibals = state[1]; // 判断野人是否会攻击传教士,如果会则需要多一艘船 if ((num_missionaries > 0 && num_missionaries < num_cannibals) || (num_missionaries < 0 && num_missionaries > num_cannibals)) { cost += 1; } // 计算野人传教士需要过河的次数 cost += (Math.abs(num_missionaries) + Math.abs(num_cannibals) + state[2] - 1) / 2; return cost; } public static ArrayList<Node> expand(Node node) { ArrayList<Node> children = new ArrayList<Node>(); int[] state = node.state; int boat = state[2]; // 生成所有可能的移动方式 for (int i = 0; i <= 2; i++) { for (int j = 0; j <= 2; j++) { if (i + j <= 2 && i + j >= 1) { int[] new_state = state.clone(); new_state[0] += (boat == 1 ? -i : i); new_state[1] += (boat == 1 ? -j : j); new_state[2] = 1 - boat; if (isValid(new_state)) { // 判断移动是否合法 Node child = new Node(new_state, node, node.cost + 1, heuristic(new_state)); children.add(child); } } } } return children; } public static boolean isValid(int[] state) { int num_missionaries = state[0]; int num_cannibals = state[1]; // 判断野人是否会攻击传教士 if ((num_missionaries > 0 && num_missionaries < num_cannibals) || (num_missionaries < 0 && num_missionaries > num_cannibals)) { return false; } // 判断过河的人数是否超过船的容量 if (Math.abs(num_missionaries) + Math.abs(num_cannibals) > 2) { return false; } // 判断传教士数量是否为负数 if (num_missionaries < 0 || num_cannibals < 0) { return false; } return true; } public static void printPath(Node node) { ArrayList<Node> path = new ArrayList<Node>(); while (node != null) { path.add(node); node = node.parent; } Collections.reverse(path); System.out.println("Solution:"); for (Node n : path) { System.out.println(Arrays.toString(n.state)); } } } class Node implements Comparable<Node> { int[] state; // 当前状态 Node parent; // 父节点 int cost; // 从起点到当前节点的代价 int heuristic; // 启发式函数值 public Node(int[] s, Node p, int c, int h) { state = s; parent = p; cost = c; heuristic = h; } // 比较两个节点的代价和启发式函数值,用于优先队列排序 public int compareTo(Node other) { return (cost + heuristic) - (other.cost + other.heuristic); } } ``` 这段代码可以求出野人传教士问题的最优解,欢迎测试。 ### 回答2: A*算法是一种常用于搜索和路径规划的算法。在野人传教士问题中,我们需要解决的是如何将三个野人和三个传教士安全地通过一艘只能容纳两个人的小船渡河,在任意一岸,野人的数量不能超过传教士的数量。为了解决这个问题,我们可以使用A*算法。 首先,我们需要定义状态。在这个问题中,一个状态可以由野人传教士的位置以及船的位置来表示。我们可以将其表示为一个由七个数值组成的元组,如(3, 3, 1, 0, 0, 0, 0),前两个数字表示左岸的野人传教士的数量,接下来两个数字表示右岸的野人传教士的数量,然后是船在左岸和右岸的位置表示。 接下来,我们需要定义启发函数。在这个问题中,我们可以使用每一步移动的成本作为启发函数。每一步移动的成本都为1,因为我们希望尽量少移动。我们还需要定义从一个状态到另一个状态的转换规则,即规定当只能船移动时,如何改变状态。 然后,我们将初始状态设为(3, 3, 1, 0, 0, 0, 0),目标状态设为(0, 0, 0, 3, 3, 1, 1)。我们可以使用A*算法来搜索从初始状态到目标状态的路径。在搜索过程中,我们根据启发函数的值选择下一个访问的状态,直到找到目标状态或者无法找到路径。 为了实现A*算法,我们可以使用优先队列来保存待访问的状态,并计算每个状态的启发函数值。根据启发函数的值,我们选择具有最小启发函数值的状态进行扩展,直到找到目标状态。 总的来说,A*算法可以用于解决野人传教士问题。我们可以根据定义的状态、启发函数和转换规则,使用A*算法来搜索并找到从初始状态到目标状态的路径。 ### 回答3: A*算法是一种用于解决搜索问题的启发式搜索算法,可以用于解决野人传教士问题。在野人传教士问题中,有3个野人和3个传教士需要过河,但是河边只有一个船,且每次船上最多只能载两个人。在任何一边,如果野人的数量超过传教士的数量,野人就会吃掉传教士。目标是找到一种过河方案,让所有人都安全到达对岸。 使用A*算法求解野人传教士问题,需要定义状态表示、评估函数和操作集合。 1. 状态表示:状态包括目前河岸上野人传教士的数量,以及船的位置(左岸或右岸)。 2. 评估函数:为了找到最优解,需要定义一个评估函数来评估每个状态的优先级。这个函数需要考虑到野人传教士的数量以及船的位置。野人数量和传教士数量的不平衡度越小,评估函数的值越小,表示该状态越优。 3. 操作集合:采取的操作有将1或2个人从一边带到另一边(如果合法)。每个操作会生成一个新状态,并根据评估函数为新状态赋予优先级。 通过以上三个步骤,可以使用A*算法解决野人传教士问题。首先,将初始状态放入一个优先级队列中。然后,循环执行以下步骤:从队列中取出优先级最高的状态,检查是否为目标状态;如果是,则找到了解决方案;否则,生成所有合法的下一步状态,并根据评估函数为它们赋予优先级,并将它们加入队列中。循环继续直到找到解决方案或队列为空。 最后,野人传教士问题可以用Java编程语言实现A*算法。可以使用图的搜索算法或基于状态的搜索算法实现。在编程过程中,需要定义状态类、评估函数、操作集合以及优先级队列。通过递归或循环结构来实现算法的迭代过程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值