传教士与野人过河问题

代码模块参考文章:传教士与野人过河问题(numpy、pandas)_python过河问题_醉蕤的博客-CSDN博客

问题描述

一般的传教士和野人问题(Missionaries and  Cannibals):有N个传教士和C个野人来到河边准 备渡河。河岸有一条船,每次至多可供K人乘渡。 问传教士为了安全起见,应如何规划摆渡方案,使 得任何时刻,在河的两岸以及船上的野人数目总是 不超过传教士的数目,但允许在河的某一岸只有野 人而没有传教士。

这里讨论基于N=3,C=3,k=2

状态表示:(m,w,B);初始状态: (3, 3, 1);目标状态: (0,0,0)

启发性规则

操作符(产生式规则)
左岸往右岸运载产生式
L10  if(M,W,1) then (M-1,W,0)     (左往右运1传教士)
L01  if(M,W,1) then (M,W-1,0)     (左往右运1野人)
L11  if(M,W,1) then (M-1,W-1,0)  (左往右运1传教士和1野人)
L20  if(M,W,1) then (M-2,W,0)     (左往右运2传教士)
L02  if(M,W,1) then (M,W-2,0)     (左往右运2野人)

右岸往左岸运载产生式
R10  if(M,W,0) then (M+1,W,1)    (右往左运1传教士)
R01  if(M,W,0) then (M,W+1,1)    (右往左运1野人)
R11  if(M,W,0) then (M+1,W+1,1) (右往左运1传教士和1野人)
R20  if(M,W,0) then (M+2,W,1)    (右往左运2传教士)
R02  if(M,W,0) then (M,W+2,1)    (右往左运2野人)

路径方案展示

总共有四种解决方案

传教士与野人过河问题_第1张图片

实现代码(含具体解释)

import numpy as np

M = int(input("请输入传教士的数量:"))  # 传教士数量
C = int(input("请输入野人的数量:"))  # 野人数量
K = int(input("请输入船的最大容量:"))  # 船的最大容量

child = []  # child:用于存储所有扩展节点
open_list = []  # open list
closed_list = []  # closed list


class State:
    def __init__(self, m, c, b):
        self.m = m  # 左岸传教士的数量
        self.c = c  # 左岸野人的数量
        self.b = b  # b为1时船在左岸;b为0时船在右岸
        self.g = 0  # 该结点所在的层级
        self.f = 0  # 该结点的启发性层度f = g + h
        # father这个属性是State
        self.father = None
        self.node = np.array([m, c, b])


init = State(M, C, 1)  # 初始节点
goal = State(0, 0, 0)  # 目标节点


# 判断状态是否合法
def safe(s):
    # 下面是要排除的条件,并且给出了为什么要排除的理由
    # s.m > M or s.m < 0 题目要求传教士的数量
    # s.c < 0 or (s.m != 0 and s.m < s.c) 有教士时,野兽的数量不能大于教士
    # s.m != M and M - s.m < C - s.c 对岸不全是怪兽时,对岸上的怪兽不能大于对岸上的教士
    if s.m > M or s.m < 0 or s.c > C or s.c < 0 or (s.m != 0 and s.m < s.c) or (s.m != M and M - s.m < C - s.c):
        return False
    else:
        # 状态合法
        return True


# 启发函数
def h(s):
    # 传教士数量+野人数量-船的位置与船的最大容量的乘积
    # 我们希望这个值越越小越好
    # 他的含义:岸上传教士数量+岸上野人数量之后
    # 如果这船在对岸(这是我们希望的,说明顺利渡河),于是我们就减上K*s.b
    # 如果船不在对岸,这不是我们希望的,所以不进行任何减数的操作(K=0)
    return s.m + s.c - K * s.b


# 判断两个状态是否相同
def equal(a, b):
    if np.array_equal(a.node, b.node):
        return 1, b
    else:
        return 0, b


# 判断当前状态是否与父状态一致
# new 是新结点,s是它的父节点
def back(new, s):
    if s.father is None:
        return False
    c = b = s.father
    while True:
        # 不断回溯,去找他的父结点是否与new结点相同
        a, c = equal(new, b)
        if a:
            return True
        b = c.father
        if b is None:
            # 此时找到的b是根结点,停止搜索
            return False


# 根据f值对open_list进行排序
def open_sort(l):
    l.sort(key=lambda x: x.f)


# 在open_list和closed_list中查找具有相同MCB属性的节点
def in_list(new, l):
    for item in l:
        if np.array_equal(new.node, item.node):
            return True, item
    return False, None


# A*算法
def A_star(s):
    A = []
    global open_list, closed_list
    open_list = [s]
    closed_list = []

    while True:  # open_list不为空
        for i in open_list:
            if np.array_equal(i.node, goal.node):  # 判断是否为目标节点
                A.append(i)
                open_list.remove(i)
        if not open_list:
            break
        get = open_list[0]
        open_list.remove(get)  # 从open_list中移除get节点
        closed_list.append(get)  # 将get节点加入closed_list

        # 获取get的子节点并考虑是否将其加入open_list
        for i in range(M + 1):  # 船上传教士数量
            for j in range(C + 1):  # 船上野人数量
                # 船上人数非法: 1:船上无人或者2:船上的人大于规定的载客数或者3:船上有教士并且野怪的数量大于教士
                if i + j == 0 or i + j > K or (i != 0 and i < j):
                    continue

                # 找到满足要求的相对于刚遍历完的结点get的下一个状态
                if get.b == 1:  # 当前船在左岸,下一个状态船在右岸
                    new = State(get.m - i, get.c - j, 0)
                    child.append(new)
                else:  # 当前船在右岸,下一个状态船在左岸
                    new = State(get.m + i, get.c + j, 1)
                    child.append(new)

                # safe(new)判断是否非法
                # back(new, get)这个相对于get结点的孩子回到父状态? TODO
                if not safe(new) or back(new, get):  # 状态非法或已经回到父状态
                    child.pop()
                else:
                    # 定义它的上一个状态的结点
                    new.father = get
                    # 孩子结点层级加1
                    new.g = get.g + 1
                    # 父亲的结点层级+父亲的启发性层度
                    new.f = get.g + h(get)
                    open_list.append(new)
                    open_sort(open_list)
    return A


# 递归打印路径
def print_path(f):
    if f is None:
        return
    # 先递归打印父结点的,再打印自己的结点
    print_path(f.father)
    print(f.node)
    # print(f.f)


if __name__ == '__main__':
    print('传教士数量:%d, 野人数量:%d, 船的最大容量:%d' % (M, C, K))
    final = A_star(init)
    print("共有%d种方案" % len(final))
    if final:
        c = 1
        for i in final:
            print('第%d个方案如下' % c)
            print_path(i)
            c += 1
    else:
        print('没有找到解决方案!')

你可能感兴趣的:(算法,启发式算法)