问题背景
三个商人各带一个随从乘船渡河,这只小船只能容纳两人由他们自行划行。随从们密约,在河的任一岸, 一旦他们的人数比商人们多, 就杀人越货。 但是如何乘船渡河的大权掌握在商人们手中。试为商人们制定一个安全渡河的方案。
问题分析
这是一个状态转移问题,即多步决策过程。(状态变量、决策变量、状态转移规律)
- 状态:每一步渡河前,此岸或彼岸的人元。
- 决策:每一步渡河时,此案到彼岸或彼岸到此岸的船上的人员。
- 要求:再安全的前提下(两岸的随从数不能不商人多),经过有限步骤使得全体人员过河。
模型假设
记录渡河过程中,此岸的商人数量为x,随从数量为y:
此岸的状态可以用向量 表示,共有
种可能的状态。
其中,对商人安全的状态有:
其中
,即商人全在此岸;
其中
,即商人全在对岸;
其中
,表示两岸商人和随从的数量一样多。
这些状态被称为允许状态集合,记作,即
状态转移需要经过状态运算来实现:摆渡一次就可以改变现有状态,这种状态运算正是要选择的策略,也称为决策,用向量 表示,即
名商人和
名随从乘船。
因此,允许决策集合为
小船从此岸到遍或从彼岸到此岸的每一次渡河,都造成状态的一次转移。
模型构成
用 表示此岸的状态变化过程,
。
用 表示状态
下的决策,
。
因为 为奇数时,小船离开此岸;
为偶数时,小船划到此岸,所以状态转移满足以下关系:
可以看出,这是一个多步决策问题:
求决策 ,使得状态
,并按转移律
,由
到达
,
越小越好。
模型求解
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. 图解法
状态 表示
个格点,其中允许状态
包含10个点;允许决策
表示移动1或2格,
为奇数则左下移,
为偶数则右上移,最终给出安全渡河方案如下:




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)



5354

被折叠的 条评论
为什么被折叠?



