问题描述
17世纪的法国数学家加斯帕在《数目的游戏问题》中讲到一个故事:15个教徒和15个非教徒在深海上遇险,必须将一半的人投入海中,其余的人才能幸免于难。于是想了一个办法,将30个人围成一个圆圈,从第一个人开始依次报数,每数到第九个人就将他扔入大海,如此循环进行直到仅剩15个人为止。问怎样的排法才能使每次投入大海的都是非教徒?
这个问题的本质就是,30个人排成一排不断的报数,数到9的人会被扔到海里,被扔到海里的人不能再参与到后面的报数中;后面一个人继续从1开始数,数到9的人又会被扔到海里;如此重复直到扔掉15个人后结束。实际操作过程如下图所示:
根据上图我们就可以设计出一个模拟上述操作的程序。
约瑟夫环
我们可以先设计一个用来不断报数的生成器,它的作用就是不断的给我们输出下一个报数的位置。然后再设计一个用来执行约瑟夫环的函数,在函数中使用报数生成器,通过记录报数次数来决定把某个数去除。再把去除的数告诉报数生成器,让报数生成器不要再报已经去除的数。
模拟报数器
首先制作一个模拟报数器,用来不断的从1数到30。我们选择生成器来实现,代码如下:
def cycle_iter(n):
"""
模拟报数器
:param n: 总人数
:return: generator
"""
while True: # 死循环
index = 0 # 每个循环开始给index赋初值为0
while index < n: # 循环n次后结束循环
index += 1 # 每次循环先把index加一
yield index # 当模拟报数器被next函数调用时,返回index的值,并等待下一次被next函数调用
上面的代码就实现了一个模拟报数器,它能连续不断的生成从1到30的整数,直到我们不再需要它。但有一个问题是,当有人被扔下大海后,他所对应的数字就不存在了。所以这里我们需要一个公共变量来控制模拟报数器,让它不要报出已被扔下大海的人所对应的数字。更改后的代码如下:
result_list = [] # 用来存储被扔下大海的人所对应的数字
def cycle_iter(n):
"""
模拟报数器
:param n: 总人数
:return: generator
"""
while True: # 死循环
index = 0 # 每个循环开始给index赋初值为0
while index < n: # 循环n次后结束循环
index += 1 # 每次循环先把index加一
if index in result_list: # 当result_list列表中存在index的值时
continue # 跳出本次循环
yield index # 当模拟报数器被next函数调用时,返回index的值,并等待下一次被next函数调用
我们在这里使用了一个result_list列表来存储被扔下大海的人所对应的数字,每当有人被扔下大海,我们就把他所对应的数字加入到result_list列表中。当模拟报数器数到result_list列表中存在的数字时就不向我们报数。
约瑟夫环执行器
制作一个约瑟夫环执行器,来使用模拟报数器报数,并把每次被扔下大海的人所对应的序号存入result_list列表。代码如下:
def cycle(n, m, x):
"""
约瑟夫环执行器
:param n: 总人数
:param m: 每次数的数
:param x: 要扔下大海的人数
:return: 被扔下大海的人所对应的序号列表
"""
global result_list # 声明公共变量result_list
result_list = [] # 给result_list赋初值
if x > n: # 判断要扔下大海的人数是否大于总人数
return # 扔下大海的人数大于总人数,则不合理,直接结束程序
my_iter = cycle_iter(n) # 创建模拟报数器
for j in range(x): # 要扔x人下大海,则循环x次
for i in range(m - 1): # 让m - 1个人报数
next(my_iter) # 报数
result_list.append(next(my_iter)) # 第m个人报数,把他扔下大海,并存储他的序号到列表
my_iter.close() # 关闭报数器
result_list.sort() # 给列表result_list排序
return result_list # 返回列表result_list
我们调用cycle函数就可以推算出会被扔下大海的人的位置了。
完整代码
把报数器和执行器结合在一起使用就可以解决约瑟夫环问题了,完整代码如下:
result_list = [] # 用来存储被扔下大海的人所对应的数字
def cycle_iter(n):
"""
模拟报数器
:param n: 总人数
:return: generator
"""
while True: # 死循环
index = 0 # 每个循环开始给index赋初值为0
while index < n: # 循环n次后结束循环
index += 1 # 每次循环先把index加一
if index in result_list: # 当result_list列表中存在index的值时
continue # 跳出本次循环
yield index # 当模拟报数器被next函数调用时,返回index的值,并等待下一次被next函数调用
def cycle(n, m, x):
"""
约瑟夫环执行器
:param n: 总人数
:param m: 每次数的数
:param x: 要扔下大海的人数
:return: 被扔下大海的人所对应的序号列表
"""
global result_list # 声明公共变量result_list
result_list = [] # 给result_list赋初值
if x > n: # 判断要扔下大海的人数是否大于总人数
return # 扔下大海的人数大于总人数,则不合理,直接结束程序
my_iter = cycle_iter(n) # 创建模拟报数器
for j in range(x): # 要扔x人下大海,则循环x次
for i in range(m - 1): # 让m - 1个人报数
next(my_iter) # 报数
result_list.append(next(my_iter)) # 第m个人报数,把他扔下大海,并存储他的序号到列表
my_iter.close() # 关闭报数器
result_list.sort() # 给列表result_list排序
return result_list # 返回列表result_list
print(cycle(30, 9, 15)) # [5, 6, 7, 8, 9, 12, 16, 18, 19, 22, 23, 24, 26, 27, 30]
执行结果如下:
从执行结果来看会被扔下大海的人所排的位置为[5, 6, 7, 8, 9, 12, 16, 18, 19, 22, 23, 24, 26, 27, 30]。