约瑟夫问题
约瑟夫问题,简而言之,即N个人围成一圈,从第一个开始报数,第M个出局,然后下一个人重新报数。
例如N=6,M=5:
初始座位:1、2、3、4、5、6
第一轮:从左往右数,5出局,序列重置为:1、2、3、4、6;
第二轮:从6开始数,4出局,序列重置为:1、2、3、6;
第三轮:从6开始数,6出局,序列重置为:1、2、3;
.....
如此循环直到最后一个,则:
出局的顺序是:5,4,6,2,3,1。
约瑟夫问题的实现
约瑟夫问题的实现,即通过程序产生约瑟夫序列,本文将通过下面两种方式实现:
- python中的list(伪数组)结构实现;
- python的链结构实现;
python的list(伪数组)结构实现
采用list来实现,暂且理解为数组,由于每一轮都有call到的位置要出局,下一轮的报数里不会有出局者。
因此有两种考虑:
- 通过在list中删除(remove)每一轮的出局者,区别于C语言的数组的是,python中的list在调用remove()方法后,删除的位置后面的索引值也会向前缩减;
- 通过给初始的list中的每一个值添加flag(True or False),出局的赋值为False,只有当Flag值为True的才进行报数操作
在这里,我们使用第2种方式来实现
主要逻辑上:
通过使用while造成list的循环圈,通过num作为报数指标,从而不受到list坐标的影响,而只被每次报数成功时影响到值(如果
要以list坐标作为参考,就会受到前面几轮已经出局的位置的影响).比如,当一开始进行到call=5时,第五个被置为False,此时
num=1,而for循环未结束,因此第6个是报1,而6号(也就是list的最后一位)报完数后,num=2,此时for循环结束,但while循环
又导致他们继续for循环,从1号开始检查是否应该报数,而num值仍是接着list的上一个循环结束时报到的数进行下去。
在判断循环何时结束的条件上:
使用python内置函数any(),当所有玩家都出局的时候,那么list中每一位的值都将被置为False,而any()函数是只要有一真值就返回真。
# -*- coding: utf-8 -*-
"""
Created on Thu Sep 27 12:04:57 2018
Description:约瑟夫问题的列表实现,基于python
Version:1.0
@author: HJY
"""
#控制参数:
nums = 41
call = 3
#参数定义:
peoples = []
for _ in range(nums):
peoples.append(True)
result = []
num =1
while(any(peoples)):
for index,people in enumerate(peoples):
if people:
if num == call:
peoples[index] = False
result.append(index+1)
# print(index+1)#每轮的出局者
# print(peoples)#每次的队列状态
num = 1
else:
num += 1
print('-'* 25)
print('\n总数为%d,报数为%d' % (nums,call))
print('约瑟夫序列为:\n%s\n' % result)
print('-'* 25)
python的链结构实现
根据约瑟夫序列构造链,其实不用这么复杂,只是笔者当时也顺便写了链的方法,一般来说,实现约瑟夫的功能只需要下面这个类的"__init__"函数即可,只用这个函数也可以构造链。
# -*- coding: utf-8 -*-
"""
Created on Sun Sep 30 10:10:09 2018
Description:约瑟夫问题的链表实现,基于python3.x
Version:v1.0
@author: HJY
"""
#构建链节点结构
class Node():
def __init__(self,Node=None,index = None):
self.data = index
self.flag = 1
self.next = Node
def __str__(self):
return 'node-' + str(self.data)
def link_print(self):
#循环链表打印
p = self
links = ''
flags = ''
while 1:
links += '-->' + str(p.data)
flags += '-->' + str(p.flag)
p = p.next
if p == self:
break
print(links)
print(flags)
print('#'*20)
def create_clink(self,nodes,nextNode_create = 0):
'''该函数用于创建循环列表,nodes为创建的节点数(包括头结点),nextNode_create
参数为后续扩展用,主要用于避免调用的节点本身就已经在链中或循环链的情况,暂时
没有补充该功能。'''
#如果调用该函数的节点存在在一链上,那么拒绝调用,或者访问下一节点
if self.next != None and nextNode_create == 0:
return
else:
p = self
for i in range(nodes-1):
p.next = Node(index = self.data + i + 1)
p = p.next
p.next = h
def delete_node_incLink(self,header):
'''删除调用者自身,并返回其上一个节点的指针,其中header参数为链表初始的指针'''
#寻找待删除节点的前一个节点
p = header
while 1:
if p.next == self:
break
else:
p = p.next
p.next = self.next
return p
同样的有两种方案:
- 链表以节点值的方式标识是否已出局
- 链表直接以删除操作将每一轮的call出局
方案一的代码实现:
#---------------------方案1--------------------------
#1、链表以节点值的方式标识是否已出局
#---------------------------------------------------
def solveby1(header,nums,call):
p = header
cursor = 1
out = 0
while out<nums:
if cursor == call:
p.flag = 0
out +=1
print(p)
# Node.link_print()#该语句可以查看每一轮的运作情况
cursor = 0
p = p.next
if p.flag:
cursor +=1
方案2的代码实现:
此处需要修改,暂不提供
主调用代码:
if __name__ == '__main__':
nums = 41#参与者总数
call = 3#报数
h = Node(index = 1)#头结点
h.create_clink(nodes=nums)
#链表打印
h.link_print()
solveby1(h,nums,call)
# print('*'*10+'方案2'+ '*'*10)
# solveby2(h,nums,call)