约瑟夫环问题(简单解法和递归解法)(python)

相关题:剑指offer 46 - 孩子们的游戏

简单解法

思路:刚开始把所有的人放到一个列表里面去,报的数字不是k就把这个人放到列表的最后一个位置上面去,如果是k就把这个数字从列表中去掉,直到列表剩下1个人为止。

使用双向队列(deque) 函数

#deque函数简介
from collections import deque 
#deque是一种双向队列,底层据说也是同样用双链表实现的

deque.rotate(2) #循环右移2次
deque.popleft() #返回并删除队列最左端元素
deque.pop() #返回并删除队列最右端元素
'''
双向队列
循环左移k-1次
删除队列最左端(k)元素,直到剩下1个人为止
'''

from collections import deque

def onlyone(n, k):
    q = deque(range(n))
    while len(q) > 1:
        q.rotate(-(k-1)) #左移k-1次
        q.popleft()      #删除第k元素
    return q[0]
print(onlyone(5, 3))

使用列表加指针

'''
注意这里是从1开始报数的,因此需要 index == k, 且相等时,删除
'''
def josephus(n,k):
    #n代表总人数,k代表报数的数字
    List = list(range(n))
    index = 0
    while len(List)>1:
        temp = List.pop(0)
        index += 1
        if index == k:
            index = 0
            continue
        List.append(temp)
#        print(List)
    return List[0]

print(josephus(5,3))

双层遍历

'''
将头前m-1个元素放到列表尾,然后删除头一个元素,将其放到resultl里面。
'''
def only1(n,k):
    lst=list(range(n))
    result=[]
    for i in range(n):
        for j in range(k-1):
            lst.append(lst.pop(0))
        result.append(lst.pop(0))
    print(result)
    return result[-1]

print(only1(5,3))

递归解法

约瑟夫环问题递归解法的一点理解
参考这篇博客个人理解
关键是建立一种有确定规则的映射,且可以将在新环中继续按原规则报数得到的结果逆推出在旧环中的对应数字。

'''
以总人数 n=10,报数为 k=3 的人淘汰,为例:

原始   0   1   2   3   4   5   6   7   8   9
旧环   0   1   2       4   5   6   7   8   9   #淘汰(index == k)后
新环   6   7   8       0   1   2   3   4   5   #映射后


新环   6   7   8       0   1   2   3   4   5  #新环第一次淘汰(index == k)
                                   ^
旧环   0   1   2       4   5   6   7   8   9  #对应旧环位置
                                   ^

如何由新环中的 3 得到旧环中的 7 呢  ?  

使用逆推方法(注意新旧环编号之间的对应关系):
新环编号 = (旧环编号 - 最大报数值)%旧总人数                              
旧环编号 = (新环编号 + 最大报数值)%旧总人数     

也就是说在:
原序列 n 中第二次淘汰的编号可以由新序列 n-1 第一次淘汰编号通过特定的逆推运算得出;
而新序列 n-1 也是(从0开始)连续的,它的第二次淘汰编号由可以由 n-2 的第一次淘汰编号通过特定的逆推得到;并且它的第二次淘汰的编号又与原序列中的第三次淘汰的编号是有对应关系的。

也就是说新旧有以下推出关系:
n-2 环的第1次出环编号 >>> n-1 环的第2次出环编号 >>> n 环的第3次出环编号
即:
在以 k 为淘汰报数值的约瑟夫环中,n人环中的第m次淘汰编号可以由 (n-1) 人环中的第 (m-1) 次出环编号通过特定运算推出。
而第一次淘汰编号是可以直接求出的,也就是说,对于任意次出环的编号,我们可以将之一直降到第一次淘汰编号问题,再一一递推而出。 
比如10人环的最后一次(第十次),可以由9人环的第9次推出,可由8人环的第8次推出。。。第一人环的第一次推出。   

n=10,k=3 时,推导过程图:
在这里插入图片描述
由图知,10人环中最后淘汰的是4号,现由其在1人环中的对应编号0来向上递归求解。
在这里插入图片描述

def only11(n,k):
    ans = 0 # n=1时,淘汰人编号
    for i in range(1, n+1):
        ans = (ans + k) % i # 旧环编号 = (新环编号 + 最大报数值)%旧总人数
        print("环总人数:%d, 每次出列的人喊的号数:%d, 出列的人的序号:%d\n"%(i,k,ans))
    return ans

print(only11(5,3))

输出如下:
环总人数:1, 淘汰人临死前的呼唤:3, 淘汰人的编号:0
环总人数:2, 淘汰人临死前的呼唤:3, 淘汰人的编号:1
环总人数:3, 淘汰人临死前的呼唤:3, 淘汰人的编号:1
环总人数:4, 淘汰人临死前的呼唤:3, 淘汰人的编号:0
环总人数:5, 淘汰人临死前的呼唤:3, 淘汰人的编号:3
3
  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值