数学建模练习小题目
题目A
有三名商人各带一名仆人过河,船最多能载两人。在河的任何一岸,若仆人数超 过商人数,仆人会杀商人越货。如何乘船由商人决定,问是否有安全过河方案,若有,最少需要几步?
定义变量
商人和仆人的状态:使用状态 (M, S) 来表示某一岸上的商人数和仆人数。
船的状态:由于船只能在两岸之间移动,使用一个二元变量来表示船的位置,1
表示船在左岸(起始岸),0
表示船在右岸(目标岸)。
因此,系统的一个状态可以表示为三元组 (M, S, B)
,其中,M 表示左岸的商人数,S 表示左岸的仆人数,B 表示船的位置(左岸或右岸),定义初始状态为 (3, 3, 1)
,目标状态为 (0, 0, 0)
,即所有人都在右岸。
状态转换
在问题的解决过程中,船一次可以带1或2人过河,因此允许的操作包括一名商人和一名仆人一起过河 (1, 1)
,两名商人一起过河 (2, 0)
,两名仆人一起过河 (0, 2)
,一名商人过河 (1, 0)
,一名仆人过河 (0, 1)
。
基于当前船所在的位置,商人和仆人要么从左岸到右岸(如果船在左岸),要么从右岸到左岸(如果船在右岸)。
合法状态条件:
如果左岸有商人和仆人,必须满足左岸的商人数 >= 左岸的仆人数。
如果右岸有商人和仆人,也必须满足右岸的商人数 >= 右岸的仆人数。
如果某岸没有商人,则无需考虑仆人数量。
目标函数
问题的目标是找到一种安全的过河策略,使得所有商人和仆人安全过河,并且最少步数完成过河过程。步数是需要最小化的目标函数。
约束条件
-
每次船的载重不能超过两人。
-
每次过河之后,任何一岸的仆人数不能超过商人数。
-
商人决定乘船方案,仆人不能独立决定过河。
BFS求解
广度优先搜索(BFS)是一种常用来寻找最短路径的算法,适合用来解决这种问题。
具体步骤如下:
-
从初始状态
(3, 3, 1)
开始,加入队列。 -
对于队列中的每个状态,尝试所有可能的合法过河方式,生成新状态。
-
如果新状态满足安全条件并且没有被访问过,将其加入队列。
-
当某个状态到达目标状态
(0, 0, 0)
时,停止搜索,返回路径。 -
如果所有状态都搜索完毕还没有找到解,则说明问题无解。
from collections import deque
# 定义初始状态 (左岸商人数量, 左岸仆人数量, 船所在岸 1表示左岸, 0表示右岸)
initial_state = (3, 3, 1)
goal_state = (0, 0, 0)
# 检查状态是否合法
def is_valid(state):
left_m, left_s, _ = state
right_m = 3 - left_m
right_s = 3 - left_s
# 检查两岸的商人数和仆人数比例
if (left_m >= 0 and left_s >= 0 and left_m >= left_s) or left_m == 0:
if (right_m >= 0 and right_s >= 0 and right_m >= right_s) or right_m == 0:
return True
return False
# 使用BFS算法来搜索最优解
def bfs():
queue = deque([(initial_state, [])]) # 队列保存当前状态和走过的路径
visited = set() # 记录已经访问过的状态
visited.add(initial_state)
while queue:
current_state, path = queue.popleft()
# 如果到达目标状态,返回路径
if current_state[:2] == goal_state[:2] and current_state[2] == goal_state[2]:
return path + [current_state]
left_m, left_s, boat = current_state
new_boat = 1 - boat # 切换船所在的岸
# 定义所有可能的船的移动情况
moves = [
(1, 0), (0, 1), (1, 1), (2, 0), (0, 2) # (商人移动数量, 仆人移动数量)
]
for move_m, move_s in moves:
if boat == 1: # 如果船在左岸
new_state = (left_m - move_m, left_s - move_s, new_boat)
else: # 如果船在右岸
new_state = (left_m + move_m, left_s + move_s, new_boat)
# 检查新状态是否合法
if is_valid(new_state) and new_state not in visited:
visited.add(new_state)
queue.append((new_state, path + [current_state]))
return None
# 运行BFS算法
result = bfs()
if result:
print("找到安全过河方案,最少步骤为:", len(result) - 1)
for step in result:
print(step)
else:
print("没有找到安全过河方案。")
求解结果
找到安全过河方案,最少步骤为: 11 (3, 3, 1) (2, 2, 0) (3, 2, 1) (3, 0, 0) (3, 1, 1) (1, 1, 0) (2, 2, 1) (0, 2, 0) (0, 3, 1) (0, 1, 0) (1, 1, 1) (0, 0, 0)
得到方案如下:
商人和仆人最少经过11步安全渡河。首先,一名商人和一名仆人过河,两岸各有2名商人和2名仆人。接着,一名仆人回到左岸,左岸有3名商人和2名仆人。然后,两名仆人过河,左岸剩下3名商人,右岸有3名仆人和2名商人。接着一名仆人返回左岸,左岸有3名商人和1名仆人。随后,两名商人过河,左岸剩下1名商人和1名仆人。接着一名商人和一名仆人返回,左右岸再次各有2名商人和2名仆人。然后两名商人过河,左岸只剩下仆人,右岸有4名商人和2名仆人。接着一名仆人回到左岸,左岸有1名仆人,右岸有4名商人和1名仆人。随后两名仆人过河,右岸所有人到达。最后,一名仆人回到左岸,并带着最后的商人和仆人安全到达右岸,完成全部渡河过程。
问题C
四个著名的音乐人受邀成为一场音乐比赛的评委,评委席的座次安排一他们互相谦让,最后组委会不得不让他们投票选出自己心中的座次安排,如果你是组委会拿到了如下的投票结果,请问你将如何安排座次?(注:排在最前面的坐首席)
甲:乙丙甲丁
乙:丙甲丁乙
丙:甲丙丁乙
丁:甲丙乙丁
解:根据每个人的投票顺序,为每个评委进行排序打分。排名越靠前,得分越高。假设排名第一得 3 分,排名第二得 2 分,排名第三得 1 分,排名第四得 0 分;然后将所有投票结果进行加总,得出每位评委的总分,分数高者安排在靠前的位置。
编写代码如下:
# 定义每位评委的投票顺序
votes = {
'甲': ['乙', '丙', '丁', '甲'],
'乙': ['丙', '甲', '丁', '乙'],
'丙': ['甲', '丙', '丁', '乙'],
'丁': ['甲', '丙', '乙', '丁']
}
# 初始化得分表
scores = {'甲': 0, '乙': 0, '丙': 0, '丁': 0}
# 给每个评委根据投票顺序打分
for voter, ranking in votes.items():
# 第一名得3分,第二名得2分,第三名得1分,第四名得0分
for i, candidate in enumerate(ranking):
scores[candidate] += 3 - i
# 按得分从高到低排序
sorted_scores = sorted(scores.items(), key=lambda x: x[1], reverse=True)
# 输出最终座次安排
print("最终座次安排:")
for i, (name, score) in enumerate(sorted_scores, 1):
print(f"第{i}名: {name},得分: {score}")
求得结果:
最终座次安排: 第1名: 丙,得分: 9 第2名: 甲,得分: 8 第3名: 乙,得分: 4 第4名: 丁,得分: 3
故应将座位安排为丙甲乙丁。