过河问题(图、BFS)

过河问题(图、BFS)

描述:

原题:3只羊和3只狮子过河,有1艘船只能容纳2只动物,当河岸上狮子数大于羊数,羊就会被吃掉,找到运输方法,让所有动物都过河。

类似推广:野人传教士过河;羊狼过河;有些问题描述时会加一个农夫(渡船人),但农夫往往不是影响因素。


思路:

看到类似问题,想到状态搜索,搜索方式一般有两种:DFSBFS,其都是对所有状态的一种搜索,直到搜索到目标状态


定义状态
1)题目意思是,原来岸边有3狮3羊,最后要安全渡河,变成0狮0羊,那么状态必有狮子数和羊数;
2)什么导致状态发生变化,是用船运送,那船在此岸还是彼岸?一开始船肯定在此岸,最后运输完成后,船必然是在彼岸,故船在哪个岸应该也要作为状态;
于是,定义状态state:

  • state = (m, n, k)
  • m: 羊数,0 <= m <= 3
  • n: 狮子数,0 <= n <= 3
  • k: 船在哪个岸,k = {0, 1}, 1表示在A岸(也就是此岸);0表示在B岸(也就是在彼岸)

那么,最终就是搜索A岸从(3,3,1)—> (0,0,0)
:理论上可能的状态一共有32种,即 4 * 4 * 2 = 32


定义决策(让状态发生变化的行为):
根据题意,船运输的羊和狮子的数量满足:
1)总数不超过2:m + n <= 2
2)羊数不小于狮子数:m >= n
那么,策略一共有以下5种:

(1,0), (0,1), (1,1), (2,0), (0,2)

例如,(1,0)表示运输羊1只狮子0只


BFS
就是从当前状态开始,遍历所有可能的决策,进行状态跳转,如下图:
在这里插入图片描述
1)搜索在向广度拓展,所以是典型的BFS搜索(如果是DFS,就是向深度拓展);
2)可以根据记忆(空间换时间),将已经发现过的状态剪枝,不重复搜索;
3)判断状态是否合法:

  • A岸 和 B岸的狮子数不大于羊数;
  • A岸 或 B岸的狮子数或羊数不能超过边界;

4)根据船在A岸还是B岸,修改状态跳转逻辑:

  • 船在A岸,则A岸狮子、羊的数量减少,B岸狮子、羊的数量增加;
  • 船在B岸,则A岸狮子、羊的数量增加,B岸狮子、羊的数量减少;

show the code

以下是python编写的算法,基于pythonds
详细可看注释,个人认为还是比较清晰的

from pythonds.graphs import Graph
from pythonds.basic import Queue
def solution():
    '''
    3只羚羊和3只狮子过河问题:
        1艘船只能容纳2只动物
        当河岸上狮子数大于羚羊数,羚羊就会被吃掉
        找到运输方法,让所有动物都过河
    '''

    # 定义合法的运输操作(i, j) , 例如(1, 0)表示运送羚羊1只,狮子0只
    opt = [(1, 0), (0, 1), (1, 1), (2, 0), (0, 2)]

    # 定义状态state(m, n, k), m表示羚羊数,n表示狮子数,k表示船在此岸还是彼岸
    # stateA 表示A岸(此岸)的状态;stateB 表示B岸(彼岸)的状态;

    # 初始状态
    stateA = (3, 3, 1)
    stateB = (0, 0, 0)

    # BFS搜索
    mygraph = Graph()
    myqueue = Queue()
    myqueue.enqueue((stateA, stateB))
    sequence = []  # 剪枝记录(最后发现,有效状态只有15种)
    sequence.append((stateA))
    while True:
        stateA, stateB = myqueue.dequeue()

        if stateA == (0, 0, 0):
            break
        for o in opt:
            # 一次从某岸到另一岸的运输
            if stateA[2] == 1:
                stateA_ = (stateA[0] - o[0], stateA[1] - o[1], stateA[2] - 1)
                stateB_ = (stateB[0] + o[0], stateB[1] + o[1], stateB[2] + 1)
            else:
                stateB_ = (stateB[0] - o[0], stateB[1] - o[1], stateB[2] - 1)
                stateA_ = (stateA[0] + o[0], stateA[1] + o[1], stateA[2] + 1)

            # 运输后
            if stateA_[0] and stateA_[0] < stateA_[1]:  # 此岸在有羊的情况下,如果狼大于羊,则吃掉
                continue
            elif stateB_[0] and stateB_[0] < stateB_[1]:  # 彼岸在有羊的情况下,如果狼大于羊,则吃掉
                continue
            elif stateA_[0] < 0 or stateA_[0] > 3 or stateA_[1] < 0 or stateA_[1] > 3:  # 边界
                continue
            else:
                # 剪枝
                if stateA_ in sequence:
                    continue
                else:
                    sequence.append(stateA_)
                    myqueue.enqueue((stateA_, stateB_))
                    mygraph.addEdge(stateA, stateA_, o)

    return mygraph, sequence
if __name__ == '__main__':
    g, sq = solution()

    # 建立父子关系
    for v_n in sq:
        v = g.getVertex(v_n)

        for nbr in v.getConnections():
            if nbr.getColor() == 'white':
                nbr.setPred(v)
                nbr.setColor('gray')
        v.setColor('black')
    target = g.getVertex(sq[-1])

    # 回溯,显示决策路径
    while target.getPred():
        predv = target.getPred()
        print(target.id, '<--', predv.getWeight(target), '--', predv.id)
        target = predv


编译结果:

(0, 0, 0) <-- (1, 1) -- (1, 1, 1)
(1, 1, 1) <-- (1, 0) -- (0, 1, 0)
(0, 1, 0) <-- (0, 2) -- (0, 3, 1)
(0, 3, 1) <-- (0, 1) -- (0, 2, 0)
(0, 2, 0) <-- (2, 0) -- (2, 2, 1)
(2, 2, 1) <-- (1, 1) -- (1, 1, 0)
(1, 1, 0) <-- (2, 0) -- (3, 1, 1)
(3, 1, 1) <-- (0, 1) -- (3, 0, 0)
(3, 0, 0) <-- (0, 2) -- (3, 2, 1)
(3, 2, 1) <-- (1, 0) -- (2, 2, 0)
(2, 2, 0) <-- (1, 1) -- (3, 3, 1)

查看sequence后,可以发现,BFS搜索的有效状态其实只有15种,然而在其他博客中(以下)描述有16种有效状态,其中,A岸状态**(0,1,1)**是唯一的不同,why?

sequence记录的状态如下:

(3, 3, 1)
(3, 2, 0)
(2, 2, 0)
(3, 1, 0)
(3, 2, 1)
(3, 0, 0)
(3, 1, 1)
(1, 1, 0)
(2, 2, 1)
(0, 2, 0)
(0, 3, 1)
(0, 1, 0)
(1, 1, 1)
(0, 2, 1)
(0, 0, 0)

原因:BFS搜索到(0,0,0)就停止了,而(0,1,1)是(0,0,0)的跳转状态,所采取的动作为(0,1),即从B岸又送了一只狮子过来,所以理论上这个状态是合法的,但实际搜索时是搜不到的。

参考博客:

https://www.cnblogs.com/guanghe/p/5485800.html

  • 3
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
农夫过河问题是一个经典的人工智能问题,可以用来介绍搜索算法。问题描述如下: 一个农夫带着一只狼、一只羊和一筐菜过河,只有一条小船,小船最多只能装下农夫和另外一只动物或者一筐菜。当农夫不在场时,狼会吃羊,羊会吃菜。请问农夫如何能够安全地把这三样东西都带过河? 下面是使用Matlab实现的代码: ```matlab % 农夫过河问题 % 1表示农夫、2表示狼、3表示羊、4表示菜 % 初始状态为[1,2,3,4,0],目标状态为[0,0,0,0,1] % 状态表示为[a,b,c,d,e],其中a,b,c,d表示四个位置上的状态,e表示小船的位置 % 0表示该位置上无人或无物 function main() start = [1,2,3,4,0]; goal = [0,0,0,0,1]; path = bfs(start, goal); if isempty(path) disp('无解'); else disp('解为:'); disp(path); end end function path = bfs(start, goal) queue = [start, 0]; visited = [start]; path = []; while ~isempty(queue) node = queue(1, :); queue(1, :) = []; if isequal(node(1:5), goal) path = [node(6); path]; while node(6) ~= 0 node = visited(node(6), :); path = [node(6); path]; end return; end for i = 1:4 if node(5) == 0 && node(i) == 1 next = node; next(i) = 0; next(5) = 1; if ~ismember(next, visited, 'rows') queue = [queue; next, size(visited, 1) + 1]; visited = [visited; next]; end elseif node(5) == 1 && node(i) == 0 next = node; next(i) = 1; next(5) = 0; if ~ismember(next(1:5), visited, 'rows') && is_valid(next) queue = [queue; next, size(visited, 1) + 1]; visited = [visited; next(1:5)]; end elseif node(5) == 0 && node(i) ~= 1 && node(i) ~= 0 next = node; next(i) = 0; next(5) = 1; if ~ismember(next(1:5), visited, 'rows') && is_valid(next) queue = [queue; next, size(visited, 1) + 1]; visited = [visited; next(1:5)]; end elseif node(5) == 1 && node(i) ~= 1 && node(i) ~= 0 next = node; next(i) = 1; next(5) = 0; if ~ismember(next(1:5), visited, 'rows') && is_valid(next) queue = [queue; next, size(visited, 1) + 1]; visited = [visited; next(1:5)]; end end end end end function valid = is_valid(node) if node(2) == 3 && node(3) == 4 && node(5) == 0 valid = false; elseif node(2) == 0 && node(3) == 4 && node(5) == 1 valid = false; elseif node(2) == 3 && node(3) == 0 && node(5) == 1 valid = false; elseif node(2) == 0 && node(3) == 0 && node(5) == 0 valid = false; else valid = true; end end ``` 其中,bfs函数是使用宽搜算法实现的,is_valid函数用来判断当前状态是否合法。程序运行后会输出解的路径,如果无解则会输出“无解”。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值