数学建模 | 商人们怎样安全过河

 问题背景

        三个商人各带一个随从乘船渡河,这只小船只能容纳两人由他们自行划行。随从们密约,在河的任一岸, 一旦他们的人数比商人们多, 就杀人越货。 但是如何乘船渡河的大权掌握在商人们手中。试为商人们制定一个安全渡河的方案

问题分析

        这是一个状态转移问题,即多步决策过程。(状态变量、决策变量、状态转移规律)

  • 状态:每一步渡河前,此岸或彼岸的人元。
  • 决策:每一步渡河时,此案到彼岸或彼岸到此岸的船上的人员。
  • 要求:再安全的前提下(两岸的随从数不能不商人多),经过有限步骤使得全体人员过河。

模型假设

        记录渡河过程中,此岸的商人数量为x,随从数量为y:

x,y=0,1,2,3

         此岸的状态可以用向量 (x,y) 表示,共有 4 \times 4 = 16 种可能的状态。

        其中,对商人安全的状态有:

  • (3,y) 其中 y=0,1,2,3 ,即商人全在此岸;
  • (0,y) 其中 y=0,1,2,3 ,即商人全在对岸;
  • (x,y) 其中 x=y=1,2 ,表示两岸商人和随从的数量一样多。

        这些状态被称为允许状态集合,记作S,即

S = \{​{(x, y) | x = 3, y = 0, 1, 2, 3; x = 0, y = 0, 1, 2, 3; x = y = 1, 2\}}

        状态转移需要经过状态运算来实现:摆渡一次就可以改变现有状态,这种状态运算正是要选择的策略,也称为决策,用向量 (u,v) 表示,即 u 名商人和 v 名随从乘船。

        因此,允许决策集合

D= \{​{(u, v) | 1 \leq u + v \leq 2\}}

        小船从此岸到遍或从彼岸到此岸的每一次渡河,都造成状态的一次转移。

模型构成

        用 s_1(x, y), s_2(x, y),... 表示此岸的状态变化过程, s_k \in S 。

        用 d_k(x, y) 表示状态 s_k(x, y) 下的决策,d_k \in D 。

        因为 k 为奇数时,小船离开此岸; k 为偶数时,小船划到此岸,所以状态转移满足以下关系:

s_{k+1}=s_k+(-1)^kd_k

        可以看出,这是一个多步决策问题:

         求决策 d_k \in D ,使得状态 s_k \in S ,并按转移律s_{k+1}=s_k+(-1)^kd_k, k=1,2,...,n,由 s_1(3,3) 到达 s_n(0,0) ,n 越小越好。

模型求解

1. 穷举法

        共有 4 种最优解决方案,均为 11 步:

方案 1:
初始状态: 此岸 (3, 3), 彼岸 (0, 0), 船在此岸
步骤 1: 0商人和2随从此岸 → 彼岸
状态: 此岸 (3, 1), 彼岸 (0, 2), 船在 彼岸
步骤 2: 0商人和1随从彼岸 ← 此岸
状态: 此岸 (3, 2), 彼岸 (0, 1), 船在 此岸
步骤 3: 0商人和2随从此岸 → 彼岸
状态: 此岸 (3, 0), 彼岸 (0, 3), 船在 彼岸
步骤 4: 0商人和1随从彼岸 ← 此岸
状态: 此岸 (3, 1), 彼岸 (0, 2), 船在 此岸
步骤 5: 2商人和0随从此岸 → 彼岸
状态: 此岸 (1, 1), 彼岸 (2, 2), 船在 彼岸
步骤 6: 1商人和1随从彼岸 ← 此岸
状态: 此岸 (2, 2), 彼岸 (1, 1), 船在 此岸
步骤 7: 2商人和0随从此岸 → 彼岸
状态: 此岸 (0, 2), 彼岸 (3, 1), 船在 彼岸
步骤 8: 0商人和1随从彼岸 ← 此岸
状态: 此岸 (0, 3), 彼岸 (3, 0), 船在 此岸
步骤 9: 0商人和2随从此岸 → 彼岸
状态: 此岸 (0, 1), 彼岸 (3, 2), 船在 彼岸
步骤 10: 0商人和1随从彼岸 ← 此岸
状态: 此岸 (0, 2), 彼岸 (3, 1), 船在 此岸
步骤 11: 0商人和2随从此岸 → 彼岸
状态: 此岸 (0, 0), 彼岸 (3, 3), 船在 彼岸

方案 2:
初始状态: 此岸 (3, 3), 彼岸 (0, 0), 船在此岸
步骤 1: 0商人和2随从此岸 → 彼岸
状态: 此岸 (3, 1), 彼岸 (0, 2), 船在 彼岸
步骤 2: 0商人和1随从彼岸 ← 此岸
状态: 此岸 (3, 2), 彼岸 (0, 1), 船在 此岸
步骤 3: 0商人和2随从此岸 → 彼岸
状态: 此岸 (3, 0), 彼岸 (0, 3), 船在 彼岸
步骤 4: 0商人和1随从彼岸 ← 此岸
状态: 此岸 (3, 1), 彼岸 (0, 2), 船在 此岸
步骤 5: 2商人和0随从此岸 → 彼岸
状态: 此岸 (1, 1), 彼岸 (2, 2), 船在 彼岸
步骤 6: 1商人和1随从彼岸 ← 此岸
状态: 此岸 (2, 2), 彼岸 (1, 1), 船在 此岸
步骤 7: 2商人和0随从此岸 → 彼岸
状态: 此岸 (0, 2), 彼岸 (3, 1), 船在 彼岸
步骤 8: 0商人和1随从彼岸 ← 此岸
状态: 此岸 (0, 3), 彼岸 (3, 0), 船在 此岸
步骤 9: 0商人和2随从此岸 → 彼岸
状态: 此岸 (0, 1), 彼岸 (3, 2), 船在 彼岸
步骤 10: 1商人和0随从彼岸 ← 此岸
状态: 此岸 (1, 1), 彼岸 (2, 2), 船在 此岸
步骤 11: 1商人和1随从此岸 → 彼岸
状态: 此岸 (0, 0), 彼岸 (3, 3), 船在 彼岸

方案 3:
初始状态: 此岸 (3, 3), 彼岸 (0, 0), 船在此岸
步骤 1: 1商人和1随从此岸 → 彼岸
状态: 此岸 (2, 2), 彼岸 (1, 1), 船在 彼岸
步骤 2: 1商人和0随从彼岸 ← 此岸
状态: 此岸 (3, 2), 彼岸 (0, 1), 船在 此岸
步骤 3: 0商人和2随从此岸 → 彼岸
状态: 此岸 (3, 0), 彼岸 (0, 3), 船在 彼岸
步骤 4: 0商人和1随从彼岸 ← 此岸
状态: 此岸 (3, 1), 彼岸 (0, 2), 船在 此岸
步骤 5: 2商人和0随从此岸 → 彼岸
状态: 此岸 (1, 1), 彼岸 (2, 2), 船在 彼岸
步骤 6: 1商人和1随从彼岸 ← 此岸
状态: 此岸 (2, 2), 彼岸 (1, 1), 船在 此岸
步骤 7: 2商人和0随从此岸 → 彼岸
状态: 此岸 (0, 2), 彼岸 (3, 1), 船在 彼岸
步骤 8: 0商人和1随从彼岸 ← 此岸
状态: 此岸 (0, 3), 彼岸 (3, 0), 船在 此岸
步骤 9: 0商人和2随从此岸 → 彼岸
状态: 此岸 (0, 1), 彼岸 (3, 2), 船在 彼岸
步骤 10: 0商人和1随从彼岸 ← 此岸
状态: 此岸 (0, 2), 彼岸 (3, 1), 船在 此岸
步骤 11: 0商人和2随从此岸 → 彼岸
状态: 此岸 (0, 0), 彼岸 (3, 3), 船在 彼岸

方案 4:
初始状态: 此岸 (3, 3), 彼岸 (0, 0), 船在此岸
步骤 1: 1商人和1随从此岸 → 彼岸
状态: 此岸 (2, 2), 彼岸 (1, 1), 船在 彼岸
步骤 2: 1商人和0随从彼岸 ← 此岸
状态: 此岸 (3, 2), 彼岸 (0, 1), 船在 此岸
步骤 3: 0商人和2随从此岸 → 彼岸
状态: 此岸 (3, 0), 彼岸 (0, 3), 船在 彼岸
步骤 4: 0商人和1随从彼岸 ← 此岸
状态: 此岸 (3, 1), 彼岸 (0, 2), 船在 此岸
步骤 5: 2商人和0随从此岸 → 彼岸
状态: 此岸 (1, 1), 彼岸 (2, 2), 船在 彼岸
步骤 6: 1商人和1随从彼岸 ← 此岸
状态: 此岸 (2, 2), 彼岸 (1, 1), 船在 此岸
步骤 7: 2商人和0随从此岸 → 彼岸
状态: 此岸 (0, 2), 彼岸 (3, 1), 船在 彼岸
步骤 8: 0商人和1随从彼岸 ← 此岸
状态: 此岸 (0, 3), 彼岸 (3, 0), 船在 此岸
步骤 9: 0商人和2随从此岸 → 彼岸
状态: 此岸 (0, 1), 彼岸 (3, 2), 船在 彼岸
步骤 10: 1商人和0随从彼岸 ← 此岸
状态: 此岸 (1, 1), 彼岸 (2, 2), 船在 此岸
步骤 11: 1商人和1随从此岸 → 彼岸
状态: 此岸 (0, 0), 彼岸 (3, 3), 船在 彼岸
 

2. 图解法

        状态 s = (x, y) 表示 4 \times 4 = 16 个格点,其中允许状态 S 包含10个点;允许决策 D 表示移动1或2格, k 为奇数则左下移, k 为偶数则右上移,最终给出安全渡河方案如下:

 

3. 代码

import matplotlib.pyplot as plt
import networkx as nx
from collections import deque

plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['axes.labelsize'] = 12
plt.rcParams['axes.titlesize'] = 15

ALLOWED_STATES = [(3, y) for y in range(4)] + [(0, y) for y in range(4)] + [(1, 1), (2, 2)]

def is_valid(state):
    """检查状态是否有效"""
    x, y = state
    return 0 <= x <= 3 and 0 <= y <= 3

def is_safe(state):
    """检查状态是否安全"""
    if not is_valid(state):
        return False
    
    x, y = state
    other_x, other_y = 3 - x, 3 - y
    return not ((x > 0 and y > x) or (other_x > 0 and other_y > other_x))

def get_moves(state, boat_left):
    """获取所有可能的移动"""
    moves = []
    x, y = state
    
    if boat_left:
        for m in range(min(2, x) + 1):
            for s in range(min(2, y) + 1):
                if 1 <= m + s <= 2:
                    new_state = (x - m, y - s)
                    if is_safe(new_state):
                        moves.append((new_state, (m, s), "→"))
    else:
        for m in range(min(2, 3 - x) + 1):
            for s in range(min(2, 3 - y) + 1):
                if 1 <= m + s <= 2:
                    new_state = (x + m, y + s)
                    if is_safe(new_state):
                        moves.append((new_state, (m, s), "←"))
    return moves

def find_solutions():
    """查找所有最优解决方案"""
    start = (3, 3)
    target = (0, 0)
    
    queue = deque([(start, True, [])])
    visited = {}
    visited[(start, True)] = 0
    solutions = []
    min_steps = float('inf')
    
    while queue:
        state, boat_left, path = queue.popleft()
        steps = len(path)
        
        if state == target and not boat_left:
            if steps < min_steps:
                min_steps = steps
                solutions = [path]
            elif steps == min_steps:
                solutions.append(path)
            continue
        
        if steps > min_steps:
            continue
            
        for new_state, move, direction in get_moves(state, boat_left):
            new_boat = not boat_left
            new_steps = steps + 1
            
            if (new_state, new_boat) not in visited or visited[(new_state, new_boat)] >= new_steps:
                visited[(new_state, new_boat)] = new_steps
                queue.append((new_state, new_boat, path + [(move, direction)]))
    
    return solutions

def visualize_solution(solution, solution_num):
    """可视化方案"""
    fig, ax = plt.subplots(figsize=(12, 12))  # 增大图形尺寸
    
    for x in range(4):
        for y in range(4):
            color = '#c1f0c1' if (x, y) in ALLOWED_STATES else '#ffb6b6'
            ax.add_patch(plt.Rectangle((x-0.5, y-0.5), 1, 1, 
                        fill=True, color=color, alpha=0.6))
    
    ax.set_xticks(range(4))
    ax.set_yticks(range(4))
    ax.set_xticklabels(['商人:0', '商人:1', '商人:2', '商人:3'], 
                      rotation=45, fontsize=14)  # 增大坐标轴标签
    ax.set_yticklabels(['随从:0', '随从:1', '随从:2', '随从:3'], 
                      fontsize=14)
    ax.tick_params(axis='both', which='both', length=0)
    
    path = [(3, 3)]
    boat_left = True
    for move, direction in solution:
        last = path[-1]
        m, s = move
        if direction == "→":
            new = (last[0]-m, last[1]-s)
            boat_left = False
        else:
            new = (last[0]+m, last[1]+s)
            boat_left = True
        path.append(new)
    
    G = nx.DiGraph()
    for i, state in enumerate(path):
        G.add_node(state, pos=state)
    
    pos = nx.get_node_attributes(G, 'pos')
    nx.draw_networkx_nodes(G, pos, node_size=1800,
                         node_color='#6fb1e4', edgecolors='white', 
                         linewidths=3, alpha=0.8) 
    
    for state, (x, y) in pos.items():
        ax.text(x, y, f"{state[0]}商人\n{state[1]}随从", 
               ha='center', va='center', 
               fontsize=18, color='black', weight='bold',
               bbox=dict(facecolor='white', alpha=0.7,
                        edgecolor='none', boxstyle='round,pad=0.5'))
    
    for i in range(len(path)-1):
        start, end = path[i], path[i+1]
        
        curve_dir = 0.3 if i % 2 == 0 else -0.3
        
        ax.annotate("",
                   xy=end, xycoords='data',
                   xytext=start, textcoords='data',
                   arrowprops=dict(arrowstyle="->", color="#006400", 
                                 lw=4, shrinkA=20, shrinkB=20,
                                 connectionstyle=f"arc3,rad={curve_dir}"))
        
        label_pos_x = start[0]*0.7 + end[0]*0.3
        label_pos_y = start[1]*0.7 + end[1]*0.3
        ax.text(label_pos_x, label_pos_y, str(i+1), 
               ha='center', va='center',
               fontsize=18, weight='bold',
               bbox=dict(facecolor='white', edgecolor='#006400', 
                        boxstyle='circle,pad=0.5'))
    
    ax.set_title(f"安全渡河方案 {solution_num} (共{len(solution)}步)", 
                pad=25, fontsize=20)
    ax.grid(True, linestyle=':', alpha=0.5)
    ax.set_xlim(-0.8, 3.8)
    ax.set_ylim(-0.8, 3.8)
    ax.set_aspect('equal')
    
    plt.tight_layout()
    plt.show()

def print_solutions(solutions):
    """打印解决方案"""
    print(f"共有 {len(solutions)} 种最优解决方案,均为 {len(solutions[0])} 步:")
    
    for i, solution in enumerate(solutions, 1):
        left = (3, 3)
        right = (0, 0)
        
        print(f"\n方案 {i}:")
        print(f"初始状态: 此岸 {left}, 彼岸 {right}, 船在此岸")
        
        for step, ((m, s), dir) in enumerate(solution, 1):
            if dir == "→":
                left = (left[0]-m, left[1]-s)
                right = (right[0]+m, right[1]+s)
                print(f"步骤 {step}: {m}商人和{s}随从此岸 → 彼岸")
            else:
                left = (left[0]+m, left[1]+s)
                right = (right[0]-m, right[1]-s)
                print(f"步骤 {step}: {m}商人和{s}随从彼岸 ← 此岸")
            
            print(f"状态: 此岸 {left}, 彼岸 {right}, 船在 {'此岸' if dir == '←' else '彼岸'}")
        
        visualize_solution(solution, i)

solutions = find_solutions()
print_solutions(solutions)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值