1问题描述
1.1假设
农夫需要把狼、羊、菜和自己运到河对岸去。
只有农夫能够划船,而且船比较小,除农夫之外每次只能运一种东西。
还有一个棘手问题,就是如果没有农夫看着,羊会偷吃菜,狼会吃羊。
1.2决策目标
让农夫能够安全地安排这些东西和他自己过河。
找到⼀个能够将农夫、狼、⽺、菜安全地从左岸移动到右岸的最短路径。
1.3变量
农夫、狼、⽺、菜的位置(0: 左岸, 1: 右岸)
2状态构造
2.1状态表示
使⽤四元组 (farmer, wolf, goat, cabbage)来表⽰状态,每个元素为0 或1,表⽰相应物品的位置(0: 左 岸,1: 右岸)
2.2转移规则
每⼀步只能让农夫带着⼀个物品过河或⾃⼰⼀个⼈过河。且必须确保每次转移后,狼和⽺、⽺和菜不单独留下, 也就是: 如果农夫不在场,且狼和⽺在同⼀岸边,步骤⽆效 如果农夫不在场,且⽺和菜在同⼀岸边,步骤⽆效
2.3状态空间图
每个状态代表⼀个图中的节点,状态转移代表边。每次有效的转移构成从⼀个状态到另⼀个状态的路径。
3 Dijkstra算法
3.1步骤
1. 初始化起始状态为(0, 0, 0, 0)(所有物品在左岸)。
2. ⽬标状态为(1, 1, 1, 1)(所有物品都到右岸)。
3. 使⽤优先队列来选择最短路径。每次选择⼀个当前最短路径的状态进⾏扩展。
4. 对于当前状态,计算所有可能的转移(即合法的neighbor状态)。
5. 如果neighbor状态尚未访问过或找到了更短路径,则更新路径。
6. 重复步骤 3-5,直到找到⽬标状态或⽆法找到路径。
3.2伪代码
初始化起点距离为 0,其他点距离为无限大
初始化结点为 None
未访问 = {起点}
已访问 = {}
当未访问不为空:
找到未访问中距离最小的点
如果当前点是目标点,停止
遍历当前点的所有合法neighbor:
如果neighbor未访问过:
计算新距离
如果新距离更短,更新neighbor的距离和结点
将neighbor加入未访问集合
重建路径: 从目标点回溯到起点
返回路
4.python代码
# Dijkstra类 修改自 示例代码7 单起点最短路径的Dijkstra's算法
class Dijkstra():
def __init__(self):
self.distances = {}
self.previous = {}
def is_valid_state(self, state):
"""
判断能不能过河
"""
farmer, wolf, goat, cabbage = state
# 狼会吃羊、羊会吃菜
if (wolf == goat and farmer != wolf) or (goat == cabbage and farmer != goat):
return False
return True
def get_neighbors(self, state):
"""
获取当前状态的所有合法neighbor状态
"""
farmer, wolf, goat, cabbage = state
neighbors = []
# 尝试过河
possible_moves = [
(farmer ^ 1, wolf, goat, cabbage), # 农夫自己过河
(farmer ^ 1, wolf ^ 1, goat, cabbage), # 农夫带着狼过河
(farmer ^ 1, wolf, goat ^ 1, cabbage), # 农夫带着羊过河
(farmer ^ 1, wolf, goat, cabbage ^ 1), # 农夫带着菜过河
]
# 添加所有合法的neighbor状态
for move in possible_moves:
if self.is_valid_state(move):
neighbors.append(move)
return neighbors
def find_min_distance(self, unvisited):
"""
找最小距离
"""
min_distance = float('inf')
min_state = None
for state in unvisited:
if self.distances.get(state, float('inf')) < min_distance:
min_distance = self.distances[state]
min_state = state
return min_state
def dijkstra(self, start, goal):
"""
Dijkstra算法
"""
# 初始化所有距离
self.distances[start] = 0
self.previous[start] = None
unvisited = {start}
visited = set()
while unvisited:
# 找最小距离
current_state = self.find_min_distance(unvisited)
unvisited.remove(current_state)
visited.add(current_state)
# 到达目标后停止搜索
if current_state == goal:
break
# 遍历
for neighbor in self.get_neighbors(current_state):
if neighbor in visited:
continue
# 计算距离
new_distance = self.distances[current_state] + 1
if neighbor not in self.distances or new_distance < self.distances[neighbor]:
self.distances[neighbor] = new_distance
self.previous[neighbor] = current_state
unvisited.add(neighbor)
# 重建路径
path = []
current_state = goal
while current_state is not None:
path.append(current_state)
current_state = self.previous.get(current_state)
return path[::-1] # 反转路径
# 格式化,更易读
def format_state(state):
farmer, wolf, goat, cabbage = state
# 河
river = "~~~~~~"
left_side = []
right_side = []
if farmer == 0:
left_side.append("农夫")
else:
right_side.append("农夫")
if wolf == 0:
left_side.append("狼")
else:
right_side.append("狼")
if goat == 0:
left_side.append("羊")
else:
right_side.append("羊")
if cabbage == 0:
left_side.append("菜")
else:
right_side.append("菜")
# 输出到左右两侧
left_output = " ".join(left_side) if left_side else ""
right_output = " ".join(right_side) if right_side else ""
return f"{left_output} | {river} | {right_output}"
# 起始状态
start_state = (0, 0, 0, 0) # 农夫、狼、羊、菜都在左边
# 目标状态
goal_state = (1, 1, 1, 1) # 农夫、狼、羊、菜都到右边
# 执行代码
dijkstra_solver = Dijkstra()
solution_path = dijkstra_solver.dijkstra(start_state, goal_state)
if solution_path:
for state in solution_path:
print(state,format_state(state))
else:
print("没有找到可用的路径")
运行结果
5总结
在求解此问题过程中,我使⽤了Dijkstra算法来找到从起始状态到⽬标状态的最短路径。利⽤Dijkstra算法可以保证 在合法状态(狼不吃⽺、⽺不吃菜)下通过最少的步骤完成从起始状态到⽬标状态的任务。
⾸先进⾏数学建模,将农夫、狼、⽺和菜的状态视作⼀个四元组(farmer, wolf, goat, cabbage),其中每个 元素的值为0或1,表⽰该物品是否在左边0或右边1。起始状态为(0, 0, 0, 0),⽬标状态为(1, 1, 1, 1),即所有物品都从左边移到右边。
通过对状态空间建模,可以将每⼀种合法的状态转化为图中的⼀个节点,状态间的合法转换则对应图中的边。再 利⽤Dijkstra算法通过求解图中从起点(0, 0, 0, 0)到⽬标(1, 1, 1, 1)的最短路径,找出最优⽅案。
Dijkstra算法通⽤性很强。但考虑到状态空间不⼤(共2^4=16种状态),深度优先搜索DFS和⼴度优先搜索BFS 也可以⽤于此问题的求解;且状态空间增⼤时,Dijkstra算法可能会变得很慢,这时候可以利⽤A-star算法等启发 式函数加速搜索过程。