写在前面

在大三专业课——人工智能基础,完成了一个算法作业。不得不说,在这次的作业上,我花了挺多功夫的,但也因此对这个作业涉及的算法很有心得。这次DFS的搜索算法的核心部分是由我自己编写的,仅仅在“船的状态”上参考借鉴了一篇非常具有启发性且通俗易懂的文章——C语言实现野人与传教士过河问题

十分感谢这篇文章给我提供的思路。

问题描述

三个传教士和三个野人在河的一岸,有一条能载一个人或者两个人的船。请设法使所有人都渡到河的另一岸,要求在任何地方野人数都不能多于传教士的人数。

问题抽象

一般的搜索问题,需要五个要素。

  1. 状态(State):出发时的初始状态、期望到达时的目标状态和每一次动作后的当前状态。在本例中用左岸传教士人数、左岸野人人数、右岸传教士人数、右岸野人人数、船的位置组成列表来表示状态:[ML, CL, MR, CR, B],其中人数用数字表示数目,船的位置表示的方式很特殊:左岸为1,右岸为-1。
  2. 动作(Action):指一个状态可以执行的动作。在本例中即每次摆渡时,船载两种人分别的人数,比如说:[2, 1]表示载2个传教士,1个野人。
  3. 转换模型:描述每个动作产生的结果,即Result(State, Action),求出下一个状态。在本例中,[3, 3, 0, 0, 1]在动作[1, 1]造成的结果为[2, 2, 1, 1, -1],所以通过模型Result(),将[3, 3, 0, 0, 1]转换为了[2, 2, 1, 1, -1]。
  4. 目标测试(Goal test):确定当前状态是否为目标状态。在本例中,[0, 0, M, C, -1]为目标状态,当每次经过转换模型后,应该对得到的状态进行目标测试。
  5. 路径代价(Cost):即每条路径的代价。在本例中,假设每次划船的代价是相同的,就可以用状态转移的次数来表示代价,次数越少的解越优。

在对搜索问题五要素进行解释的同时,我们也对本例的问题进行了相应的举例。在下面以N=3, K= 2(N为传教士和野人分别的人数,K为船的最大载客量)为例,再次总结一下问题抽象的思路:

  1. 状态:初始状态为[3, 3, 0, 0, 1],目标状态为[0, 0, 3, 3, -1],中间状态为[ML, CL, MR, CR, B],应该满足:0 ≤ ML, CL, MR, CR ≤ N = 3。
  2. 动作:K = 2的时候动作一共有:[1, 0], [0, 1], [1, 1], [2, 0], [0, 2]。其中[m, c]表示载m个传教士,c个野人。
  3. 转换模型:通过当前状态和动作,得出下个状态为[ML - B*action[0], MR + B*action[0], CL - B*action[1], CR - B*action[1], -B]。从中可以看出,船的位置B决定了两岸人数的增加或是减小。
  4. 目标测试:测试当前状态是不是目标状态[0, 0, 3, 3, -1];
  5. 路径代价:每变换一次状态,记一次。

对于第3点,以状态[3, 3, 0, 0, 1]和动作[0, 2]为例,得到的下个状态应该为:

[3-1*0, 3-1*2, 0+1*0, 0+1*2, -1*1] = [3, 1, 0, 2, -1]

可以看到,船的位置(±1)决定了左岸和右岸到底是增加还是减少,然后下一个状态必然跟上一个状态的船的位置相反,可以按这个规则继续计算。

做作业的时候,顺便画了状态空间:

算法思路

本文将使用递归实现深度优先搜索算法(DFS),使用graph = {}来记录图,键(key)为父节点,值(value)为子节点,一个父节点可以带着很多子节点,一个子节点也可以在很多的父节点的后面,因此非常符合Python字典的特点,使用起来非常方便。算法的思路如下:

输入

用户输入信息,其中包括N和K的值,用以后续的计算。随即生成初始状态[N, K, 0, 0, 1]。

生成动作

生成可行的动作[m, c],其中要求:m + c ≤ K && (m ≥ c || m == 0)。

对m和c从0到K开始遍历,将所有可行的动作加入到列表actions当中:[[m1, c1], [m2, c2], ...]

进入递归

递归形式实现的深度优先搜索是从一个点不断地寻找子节点,直到不满足条件再回来,找另外一个方向。就如同一个人走迷宫,采取一直右转的策略(当然不能走曾经走过的路,不然就一直绕了),然后终于无法右转了,只好退回去左转,然后继续右转试探。

递归当中最重要的是对于return的条件判断,有如下几个条件如果满足就需要回到上一个点:

  1. 到达终点。这时候不需要继续往终点的下一个状态寻找,直接返回到前一个点,寻找另外一条路。
  2. 与前面已经走过的点重复。因为在这个问题下,如果出现重复的点,那么必然没有必要。在上一次就能到这一个状态,走过若干步又到了这个状态,中间若干步根本没有必要走。所以此时退回,找另外的路。
  3. 不符合状态的要求。在这个问题下,要求传教士人数不为零的时候,传教士人数要大于野人人数。

如果上面的要求都符合,就不必return,将该点加入到用于存储图的字典当中。紧接着进行下一状态的建立(通过与action进行运算),然后再次进入递归。如果所有的action都试完了,return。

输出路径

输出路径使用的方法是递归的方法。从初始状态开始,在graph中进行深度优先搜索。

以初始状态为key值,遍历每个value,将value继续作为key值,然后同样遍历其value值。在此过程中记录下路径。

找到终点后,将该路径输出,然后return上一层,继续找。直到最后搜索完毕return。

结束

递归彻底结束后,输出一下总共找到多少条路径。

代码(Python)

"""
作者:Zhanyu_Guo
创建日期:2020.10.25
更新日期:2020.11.03
文件名:CrossRiverDFS.py
"""
# 用以测试运行时间
import time
"""globals"""
n = 0       # 传教士与野人各自的人数N
path = []   # 递归查找的单次路径
paths = []  # 存放多个路径
graph = {}  # 图# 已走过状态的列表
# 表示方式[ML, CL, MR, CR, B](左传教士、左野人、右传教士、右野人、船)
# 船的位置(1:左岸,-1:右岸)决定了数目的增减
stateList = []# 动作,即变化的方式,表示方式[M, C](传教士、野人)
actions = []# 判断状态是否满足条件,同时建图
def ok(state):# 判断是否都不小于0if state[0] < 0 or state[1] < 0 or state[2] < 0 or state[3] < 0:return False# 判断是否都满足不被吃的条件if (state[0] < state[1] and state[0] != 0) or (state[2] < state[3] and state[2] != 0):return False# 满足上述两个条件是有效节点,将其加入到图中if len(stateList) - 1:state_b = stateList[-2][:]if tuple(state_b) in graph.keys() and tuple(state) not in graph[tuple(state_b)]:graph[tuple(state_b)].append(tuple(state))else:graph[tuple(state_b)] = [tuple(state)]passpass# 判断是否与前面状态重复for p in stateList[:-1]:if p[0] == state[0] and p[1] == state[1] and p[4] == state[4]:return Falsepassreturn Truedef mapping(state):# 判断并建图if not ok(state):return# 到达目标状态[0, 0, n, n, -1]if state[0] == 0 and state[1] == 0:return# 执行动作,找到下一个有效点tmp = [0] * 5for action in actions:# 船的位置(1:左岸,-1:右岸)决定了数目的增减tmp[0] = state[0] - action[0] * state[4]tmp[1] = state[1] - action[1] * state[4]tmp[2] = state[2] + action[0] * state[4]tmp[3] = state[3] + action[1] * state[4]tmp[4] = -state[4]stateList.append(tmp[:])mapping(tmp)stateList.pop()passreturn# 深度搜索寻找路径
def find_path(state):global n# 走到重复的状态if state in path:path.append(state)return# 到达终点状态,记录路径if state == (0, 0, n, n, -1):path.append(state)paths.append(path[:])returnpath.append(state)# 逐个探索for i in range(len(graph[state])):find_path(graph[state][i])path.pop()passpass# 主函数
def main():global n# 输入n = int(input("输入各人数N:"))k = int(input("输入载客量K:"))# 初始状态s = [n, n, 0, 0, 1]stateList.append(s)# 生成动作# i:移动传教士和野人之和,从1到kfor i in range(1, k + 1):# j:传教士的数目,从0到ifor j in range(i + 1):# 如果满足传教士不少于野人或传教士为0,动作有效if (j >= i - j) or (j == 0):actions.append([j, i - j])passpasspass# 生成完毕# 记录起始时间start = time.perf_counter()# 进入递归mapping(s)# 计算总时长total = time.perf_counter() - startprint(total)# 搜索路径find_path(tuple(s))# 路径条数num = 0# 输出路径for p in paths:num += 1print("第%d条路径:" % num)str1 = "{:^6}{:^6}{:^6}{:^6}{:^6}"print(str1.format("ML", "CL", "MR", "CR", "B"))for i in p:print(str1.format(i[0], i[1], i[2], i[3], i[4]))passpass# 结束print("总共有%d条路径" % num)if __name__ == '__main__':try:main()passexcept Exception as e:print(e)pass

传教士与野人问题深度优先搜索算法(DFS)-Python实现相关推荐

  1. 算法简介:不撞南墙不回头----深度优先搜索算法(DFS)

    算法简介:不撞南墙不回头----深度优先搜索算法(DFS) 算法简介 算法简介 DFS算法简介 略 DFS算法思想 首先以一个未被访问过的顶点作为起始顶点,沿当前顶点的边走到未访问过的顶点;当没有未访 ...

  2. php mysql搜索算法_PHP实现深度优先搜索算法(DFS,Depth First Search)详解

    本文实例讲述了PHP实现深度优先搜索算法.分享给大家供大家参考,具体如下: 深度优先搜索的实现原理: 实现代码: class Search_Method { //无向图的数组描述 private $d ...

  3. 简单易懂的深度优先搜索算法(DFS)

    在我们遇到的一些问题当中,有些问题我们不能够确切的找出数学模型,即找不出一种直接求解的方法,解决这一类问题,我们一般采用搜索的方法解决.搜索就是用问题的所有可能去试探,按照一定的顺序.规则,不断去试探 ...

  4. 深度优先搜索及python实现围棋“吃子”

    文章目录 前言 一."吃子"和"气" 1."吃子"和"气"的概念 2.问题转化 二.深度优先搜索 1.表示方法 2.深度 ...

  5. python 围棋按照坐标查找棋子_深度优先搜索及python实现围棋“吃子”

    前言 "吃子"是围棋最基本的规则之一,但在编写围棋游戏要如何实现?深度优先搜索可以解决这个问题.本文分享的是个人使用深度优先搜索算法及python语言实现"吃子" ...

  6. C语言版,传教士与野人渡河问题,使用深度优先搜索法求解(DFS),变态版,随便输入人数和船的最大载人数,人工智能经典题目,简单易懂,注释到位,没有bug

    目录 一.问题描述 二.迟来的代码 运行截图 三.简单分析 一.问题描述 有n个传教士和n个野人准备渡河,但只有一条能容纳c个人的小船,为了防止野人侵犯传教士,要求无论在何处,传教士的人数不得少于野人 ...

  7. python深度优先搜索传教士和野人_传教士和野人问题解题思路

    传教士和野人渡河问题 刘宪国050422023 野人过河问题描述如下:有三个传教士和三个野人过河,只有一条能装下两个人的船,在河的任何一方或者船上,如果野人的人数大于传教士的人数,那么传教士就会有危险 ...

  8. Python实现 宽度/广度优先搜索算法, 深度优先搜索算法

    Python实现 宽度/广度优先搜索算法, 深度优先搜索算法 1. 二叉树图 2. 宽度/广度优先搜索算法(Breadth First Search,BSF) 3. 深度优先搜索算法 4. 宽度/广度 ...

  9. 深度优先搜索 python_黄哥Python:图深度优先算法(dfs)

    深度优先搜索算法(英语:Depth-First-Search,DFS)是一种用于遍历或搜索树或图的算法.沿着树的深度遍历树的节点,尽可能深的搜索树的分支.当节点v的所在边都己被探寻过,搜索将回溯到发现 ...

  10. DFS深度优先搜索算法/BFS广度优先搜索算法(c/c++)

    深度优先搜索算法(DFS) 深度优先搜索算法思路:(有点贪心算法的意思) 1,从某个给定结点a出发,访问它 2,查找关于a的邻接点,查找到a的第一个邻接点b之后,对b结点进行DFS搜索,也就是对b结点 ...

最新文章

  1. matlab数据的拼接
  2. AppStore 拒绝审核原因:PLA 2.3
  3. 商淘多b2b2c商城系统怎么在个人电脑上安装_b2b2c商城系统免费模板怎么用?
  4. java调用reader的nextInt_Java中如何从键盘输入内容: import java.util.Scanner; .nextLine(); .hasNextInt();...
  5. Markdown 基础学习
  6. 静态多态之泛型编程(模板)
  7. 易买网HTML静态页面,易买网(前台+后台的静态页面)
  8. [unity3d]再次修改socket聊天,完美的服务器端
  9. 动态规划练习合集(c++)
  10. Javascript特效代码大全(420个)
  11. mysql选课时间冲突_选课常见问题解答
  12. Kopernio插件+SCI-HUB最新可用网址
  13. 最短路径算法,Dijkstra算法,floyd算法 07-图4 哈利·波特的考试 (25 分)
  14. 【信息安全】EDR、HIDS、NDR、MDR、XDR 区别与联系
  15. 双十一到了,当我用Python采集了电商平台所有商品后发现....
  16. SolidWorks2016软件,SW2010-2016.Activator.GUI.SSQ激活闪退解决办法:
  17. 数据库学习笔记(SQL server语句)
  18. 自然数,有理数,无理数,实数,整数
  19. 【python作品分享】密码锁
  20. js的遍历器(Iterator)

热门文章

  1. 硬盘柱面损坏怎么办_硬盘坏道屏蔽工具,详细教您如何修复硬盘坏道
  2. 3Dmax与BIM模型的区别
  3. 怎样抢注到一个刚过期不久的域名?
  4. 【C语言】动态内存的分配
  5. K650c + Ubuntu 15.04无法正常关机,重启
  6. 旅行商问题(回溯算法)
  7. android取消输入法联想,输入法联想功能,怎么清除输入法联想
  8. request+BeautifulSoup:下载《笔趣看》网小说《第九特区》
  9. COSClient上传图片失败
  10. 在字节跳动的实习经历分享 | 万字求职指南