A*算法解决传教士—野人过河问题

A*算法解决传教士—野人过河问题

算法原理

1、A算法的基本原理分析;
在或图的一般搜索算法中,如果在搜索过程的步骤⑦利用估价函数f(n)=g(n)+h(n)对open表中的节点进行排序,则该搜索算法为A算法。
g(n):从初始节点到n的实际代价
因为n为当前节点,搜索已达到n点,所以g(n)可计算出。
h(n):启发函数,从n到目标节点的最佳路径的估计代价。
因为尚未找到解路径,所以h(n)仅仅是估计值。
对A算法中的g(n)和h(n)做出限制:
g(n) >= g
(n)(g*(n)为S0到n的最小费用)
-h(n) <= h*(n)(h*(n)为n到Sg的实际最小费用)
则算法被称为A*算法。

2、传教士—野人过河问题的知识表示方法分析;
在这个问题中,需要考虑:
1、两岸的传教士人数和野人人数 2、船在左岸还是在右岸
已知:传教士和野人数:N(两者默认相同),船的最大容量:K
定义:M:左岸传教士人数 C:左岸野人人数 B:左岸船个数
可用一个三元组来表示左岸状态,即S=(M, C, B)。
约束条件:M>=0,C>=0,B=1或0
已知左岸状态,右岸的状态为:
右岸传教士人数:M’=N-M
右岸野人人数:C’=N-C
右岸船数:B’=1-B
满足同样的约束条件

3、针对传教士—野人过河问题的A算法详细分析
(1)A
算法求解传教士和野人过河问题,主要实现过程:
①使用状态空间法将问题的求解抽象为状态空间的搜索。
②根据A算法的思想、A算法的具体步骤、设计估价函数的方法,针对传教士–野人过河问题设计出估价函数f(n),给出条件约束函数。
(2)估价函数设计:f(n)=g(n)+M+C-K*B

  • h(n)=M+C-K*B把状态转换后左岸剩余人数作为启发性信息

合理性分析:
本问题中,在满足条件约束的前提下,总是希望能使左岸的人数最少。当左岸有船时,应当使船每次都满负荷运载,即运KB人过河。
然而,在最大运载量为K的情况下,状态转换后左岸的剩余人数不可能小于M+C-K
B,即从节点n到目标节点的最小代价h*(n)不可能小于h(n) ,因此,满足A算法的条件限制h(n)<=h(n)。
(3)本问题中操作是指用船把传教士或野人从河的左岸运到右岸,或者从河的右岸运到左岸,并且每个操作都应该满足以下3个条件:
①船至少有一个人(M或C)操作,离开岸边的M和C的减少数目等于到达岸边的M和C的增加数目。
②每次操作,船上的人数不得超过K个。
③操作应保证不产生非法状态。

设计思路

1、设计状态空间表示方式
可用一个三元组来表示左岸状态,即S=(M, C, B),将所有扩展的节点和原始节点存放在同一列表中。初始状态为(N,N,1),目标状态为(0,0,0),问题的求解转换为在状态空间中,找到一条从状态(N, N, 1)到状态(0, 0, 0)的最优路径。
例:在3传教士3野人问题中,初始状态为(3,3,1),目标状态为(0,0,0)。
当1野人离开左岸到达右岸后,原状态变为p = [(3,2,0),(3,3,1)],p[0]为当前状态,而列表最后一个为初始状态,只要当p[0] = (0,0,0)则完成搜索。
2、节点拓展方法与合法状态判断
(1)节点拓展:通过减少和增加传教士或野人的数量来拓展节点。
当船在左岸(B=1):①减少野人 ②减少传教士 ③减少野人和传教士
当船在右岸(B=0):①增加野人 ②增加传教士 ③增加野人和传教士
(2)合法状态判断
①左岸传教士数量等于总数或左岸传教士为0:C>=0,C<=N
②左岸传教士数量基于0到N之间时: C >=0 , M >= C , M <= N , C <= N , N-M >= N-C
③其他状态为不合法

3、搜索过程
①建立只含有初始节点S的搜索图G,把S放到OPEN表中;
②建立CLOSED表,其初始值为空表;
③若OPEN表是空表,则失败退出;
④选择OPEN表中第一个节点,把它从OPEN表移出并放进CLOSED表中,称此节点为节点n;
⑤若n为目标节点,则有解并成功退出。
⑥沿指针追踪图G中从n到S这条路径得到解(指针在步骤⑦中设置);
⑦扩展n,生成不是n的祖先的那些后继节点的集合M,把M的这些成员作为n的后继节点添入图G中;
对M中子节点进行如下处理:
-对没在G中出现过的(即没在OPEN或CLOSED表中出现过的)M成员设置一个指向n的指针,把M的这些成员加进OPEN表;
-已在OPEN或CLOSED表中的每个M成员,确定是否需要更改指向n的指针方向;
-已在CLOSED表中的每个M成员,确定是否需要更改图G中它的每个后裔节点指向父节点的指针。
⑧按某种方式或按某个试探值,重排OPEN表;
⑨转步骤③。

完整代码

def GJ(this,k):#估价函数计算 h(n) = M + C - K * B
    return this[0] + this[1] - k * this[2]

def creat(array,M,C,B,N):#判断生成节点是否符合规则、判断是否重复
    P = array[:]
    if M == N :#左岸传教士数量等于总数
        if  C >=0  and  C <= N :
            P.insert(0,[M,C,1-B])
            for i in open:
                if P[0] == i[0]:
                    return False
            for i in closed:
                if P[0] == i[0]:
                    return False
            open.append(P)
            return True
        else:
            return False
    elif M > 0 :#左岸传教士数量基于0到N之间时
        if  C >=0 and M >= C and M <= N and C <= N and N-M >= N-C:
            P.insert(0,[M,C,1-B])
            for i in open:
                if P[0] == i[0]:
                    return False
            for i in closed:
                if P[0] == i[0]:
                    return False
            open.append(P)
            return True
        else:
            return False
    elif M == 0:#左岸传教士为0
        if  C >= 0 and C <= N:
            P.insert(0, [M, C, 1 - B])
            for i in open:
                if P[0] == i[0]:
                    return False
            for i in closed:
                if P[0] == i[0]:
                    return False
            open.append(P)
            return True
        else:
            return False
    else:
        return False

if  __name__ == '__main__':
    N = int(input("传教士和野人的人数(默认相同):"))
    K =int(input("船的最大容量:"))
    open = []  #创建open表
    closed = [] #创建closed表
    sample = [N,N,1] #初始状态
    goal = [0,0,0]#目标状态
    open.append([sample])
    creatpoint = searchpoint = 0
    while(1):
        if sample == goal:
            print("初始状态为目标状态!")
            break
        if len(open) == 0:
            print("未搜索到解!")
            break
        else:
            this = open.pop(0)
            closed.append(this)
            if this[0] == goal:
                print("搜索成功!")
                print('共生成节点数:{},共搜索节点数:{}'.format(creatpoint,searchpoint + 1))
                print('过河方案如下:')
                print('      [M, C, B]')
                for i in this[::-1]:
                    print('---->',i)
                exit()
            #扩展节点
            searchpoint += 1
            if this[0][2] == 1 :#船在左岸时
                for i in range(1,K+1):#只
                    if creat(this,this[0][0]-i,this[0][1],this[0][2],N):
                        creatpoint += 1
                for i in range(1,K+1):
                    if creat(this,this[0][0],this[0][1]-i,this[0][2],N):
                        creatpoint += 1
                for i in range(1,K):
                    for r in range(1,K-i+1):
                        if creat(this,this[0][0] - i,this[0][1] - r, this[0][2],N):
                            creatpoint += 1
            else:#船在右岸时
                for i in range(1,K+1):
                    if creat(this,this[0][0]+i,this[0][1],this[0][2],N):
                        creatpoint += 1
                for i in range(1,K+1):
                    if creat(this,this[0][0],this[0][1]+ i,this[0][2],N):
                        creatpoint += 1
                for i in range(1,K):
                    for r in range(1,K-i+1):
                        if creat(this,this[0][0] + i,this[0][1] + r, this[0][2],N):
                            creatpoint += 1

            #计算估计函数h(n) = M + C - K * B 重排open表
            for x in range(0,len(open)-1):
                m = x
                for y in range(x+1,len(open)):
                    if  GJ(open[x][0],K) >  GJ(open[y][0],K):
                        m = y
                if m != x:
                    open[x],open[m] = open[m],open[x]

运行结果

参数设置1:N = 3,K =2
初始状态:(3,3,1),目标状态:(0,0,0)
实验结果:
在这里插入图片描述
参数设置2:N = 5,K =3
初始状态:(5,5,1),目标状态:(0,0,0)
实验结果:
在这里插入图片描述
参数设置3:N = 10,K =4
初始状态:(10,10,1),目标状态:(0,0,0)
实验结果:
在这里插入图片描述

  • 65
    点赞
  • 301
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
### 回答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*算法。可以使用图的搜索算法或基于状态的搜索算法来实现。在编程过程中,需要定义状态类、评估函数、操作集合以及优先级队列。通过递归或循环结构来实现算法的迭代过程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值