python 回溯法 子集树模板 系列 —— 19、野人与传教士问题

简介: 问题在河的左岸有N个传教士、N个野人和一条船,传教士们想用这条船把所有人都运过河去,但有以下条件限制:(1)修道士和野人都会划船,但船每次最多只能运M个人;(2)在任何岸边以及船上,野人数目都不能超过修道士,否则修道士会被野人吃掉。

问题

在河的左岸有N个传教士、N个野人和一条船,传教士们想用这条船把所有人都运过河去,但有以下条件限制:

(1)修道士和野人都会划船,但船每次最多只能运M个人;

(2)在任何岸边以及船上,野人数目都不能超过修道士,否则修道士会被野人吃掉。

假定野人会服从任何一种过河安排,请规划出一个确保修道士安全过河的计划。

分析

百度一下,网上全是用左岸的传教士和野人人数以及船的位置这样一个三元组作为状态,进行考虑,千篇一律。

我换了一种考虑,只考虑船的状态。

船的状态:(x, y) x表示船上x个传教士,y表示船上y个野人,其中 |x|∈[0, m], |y|∈[0, m], 0<|x|+|y|<=m, x*y>=0, |x|>=|y|

船从左到右时,x,y取非负数。船从右到左时,x,y取非正数

解的编码:[(x0,y0), (x1,y1), ..., (xp,yp)] 其中x0+x1+...+xp=N, y0+y1+...+yp=N

解的长度不固定,但一定为奇数

开始时左岸(N, N), 右岸(0, 0)。最终时左岸(0, 0), 右岸(N, N)

由于船的合法状态是动态的、二维的。因此,使用一个函数get_states()来专门生成其状态空间,使得主程序更加清晰。

代码

n = 3  # n个传教士、n个野人
m = 2  # 船能载m人

x = [] # 一个解,就是船的一系列状态
X = [] # 一组解

is_found = False # 全局终止标志


# 计算船的合法状态空间(二维)
def get_states(k): # 船准备跑第k趟
    global n, m, x
    
    if k%2==0:  # 从左到右,只考虑原左岸人数
        s1, s2 = n - sum(s[0] for s in x), n - sum(s[1] for s in x)
    else:       # 从右到左,只考虑原右岸人数(将船的历史状态累加可得!!!)
        s1, s2 = sum(s[0] for s in x), sum(s[1] for s in x)
    
    for i in range(s1 + 1):
        for j in range(s2 + 1):
            if 0 < i+j <= m and (i*j == 0 or i >= j):
                yield [(-i,-j), (i,j)][k%2==0]   # 生成船的合法状态


# 冲突检测
def conflict(k): # 船开始跑第k趟
    global n, m, x
    
    # 若船上载的人与上一趟一样(会陷入死循环!!!!)
    if k > 0 and x[-1][0] == -x[-2][0] and x[-1][1] == -x[-2][1]:
        return True
        
    # 任何时候,船上传教士人数少于野人,或者无人,或者超载(计算船的合法状态空间时已经考虑到了。)
    #if 0 < abs(x[-1][0]) < abs(x[-1][1]) or x[-1] == (0, 0) or abs(sum(x[-1])) > m:
    #    return True
    
    # 任何时候,左岸传教士人数少于野人
    if 0 < n - sum(s[0] for s in x) < n - sum(s[1] for s in x):
        return True
    
    # 任何时候,右岸传教士人数少于野人
    if 0 < sum(s[0] for s in x) < sum(s[1] for s in x):
        return True
    
    return False # 无冲突
    
    
    
# 回溯法
def backtrack(k): # 船准备跑第k趟
    global n, m, x, is_found
    
    if is_found: return  # 终止所有递归
    
    if n - sum(s[0] for s in x) == 0 and n - sum(s[1] for s in x) == 0: # 左岸人数全为0
        print(x)
        is_found = True
    else:
        for state in get_states(k): # 遍历船的合法状态空间
            x.append(state)
            if not conflict(k):
                backtrack(k+1) # 深度优先
            x.pop()   # 回溯


# 测试
backtrack(0)

效果图

img_d61dd7bbd505e09713c4c0f0f5631f3b.jpg

解的解释,从上往下看:
img_118371eccde25ce7027707255f85f42e.jpg

一个结论

貌似只有满足m = n-1,此问题才有解

目录
相关文章
|
Python 数据可视化
python 回溯法 子集树模板 系列 —— 1、8 皇后问题
问题 8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。 分析 为了简化问题,考虑到8个皇后不同行,则每一行放置一个皇后,每一行的皇后可以放置于第0、1、2、...、7列,我们认为每一行的皇后有8种状态。
1094 0
|
7月前
|
数据采集 机器学习/深度学习 人工智能
Python:现代编程的首选语言
Python:现代编程的首选语言
988 102
|
7月前
|
数据采集 机器学习/深度学习 算法框架/工具
Python:现代编程的瑞士军刀
Python:现代编程的瑞士军刀
429 104
|
7月前
|
人工智能 自然语言处理 算法框架/工具
Python:现代编程的首选语言
Python:现代编程的首选语言
336 103
|
7月前
|
机器学习/深度学习 人工智能 数据挖掘
Python:现代编程的首选语言
Python:现代编程的首选语言
280 82
|
6月前
|
Python
Python编程:运算符详解
本文全面详解Python各类运算符,涵盖算术、比较、逻辑、赋值、位、身份、成员运算符及优先级规则,结合实例代码与运行结果,助你深入掌握Python运算符的使用方法与应用场景。
420 3
|
6月前
|
数据处理 Python
Python编程:类型转换与输入输出
本教程介绍Python中输入输出与类型转换的基础知识,涵盖input()和print()的使用,int()、float()等类型转换方法,并通过综合示例演示数据处理、错误处理及格式化输出,助你掌握核心编程技能。
635 3
|
6月前
|
并行计算 安全 计算机视觉
Python多进程编程:用multiprocessing突破GIL限制
Python中GIL限制多线程性能,尤其在CPU密集型任务中。`multiprocessing`模块通过创建独立进程,绕过GIL,实现真正的并行计算。它支持进程池、队列、管道、共享内存和同步机制,适用于科学计算、图像处理等场景。相比多线程,多进程更适合利用多核优势,虽有较高内存开销,但能显著提升性能。合理使用进程池与通信机制,可最大化效率。
441 3
|
6月前
|
Java 调度 数据库
Python threading模块:多线程编程的实战指南
本文深入讲解Python多线程编程,涵盖threading模块的核心用法:线程创建、生命周期、同步机制(锁、信号量、条件变量)、线程通信(队列)、守护线程与线程池应用。结合实战案例,如多线程下载器,帮助开发者提升程序并发性能,适用于I/O密集型任务处理。
592 0
|
7月前
|
数据采集 机器学习/深度学习 人工智能
Python:现代编程的多面手
Python:现代编程的多面手
285 0

推荐镜像

更多