约瑟夫环,python递推方法的理解

#! /usr/bin/env python3
#-*- coding:utf-8 -*-

”’约瑟夫环的问题,递推方法的理解”’

n个人循环报数,杀第k个人

def fn(n,k):
    L = list(range(1,n+1))
    if n == 1:
        return
    else:
        index_ = 0
        for i in range(n-1):
            index_ = (index_ + k) % len(L) - 1
            print('出局的人是:',L[index_])
            del L[index_]
            if index_ < 0:
                index_ = 0
        print('最后存活的人:',L[0])
fn(10,6)

 

”’假设有10个人,编号1-10号,按顺序报数到第6个人时出局,然后循环报数,每到第6个人就出局,求此过程中的最后存活者.

先来做个模拟流程:
   第一次报数顺序:L = [1,2,3,4,5,6,7,8,9,10],把此顺序当成原队列顺序
   第一次出局6,原队列顺序变为:[1,2,3,4,5,7,8,9,10],然后从7开始继续数6位,数到10的时候发现人数不够,就从1开始借人继续数2位
   第二次出局2,原队列顺序变为:[1,3,4,5,7,8,9,10],然后从3开始继续数6位
   第三次出局9,原队列顺序变为:[1,3,4,5,7,8,10],然后从10开始继续数6位,因为人数不够,从1开始借人继续数
   第四次出局7,原队列顺序变为:[1,3,4,5,8,10],然后从8开始继续数6位,因为人数不够,从1开始借人继续数
   第五次出局5,原队列顺序变为:[1,3,4,8,10],然后从8开始继续数6位,因为人数不够,从1开始借人继续数
   第六次出局8,原队列顺序变为:[1,3,4,10],然后从10开始继续数6位,因为人数不够,从1开始借人继续数
   第七次出局1,原队列顺序变为:[3,4,10],然后从3开始继续数6位,因为人数不够,从3开始借人继续数
   第八次出局10,原队列顺序变为:[3,4],然后从3开始继续数6位,因为人数不够,从3开始借人继续数
   第九次出局4,原队列顺序变为:[3].最后[3]存活!

   我们现在唯一可以知道的是,最后存活的人在原队列中一定新排在第一位,用索引表示为L[0]

   我们再从头推导一次:
   第一次报数列表:L = [1,2,3,4,5,6,7,8,9,10]
   从1开始数6位,第一次出局的人为6,索引为L[5]
   出局6后,L变为:L = [1,2,3,4,5,7,8,9,10].
   新的报数顺序为:7,8,9,10,1,2,3,4,5
   从7开始数6位,第二次出局的人为2,索引为L[1]

   怎么在两个出局者的索引中找到规律呢?从新报数的第一个人:7 开始突破
   我们发现7在原列表(第一次报数列表)中的索引为L[6],6出局后,因为原报数列表长度的改变,索引变为L[6-1]
   第二次出局的人为2,在原列表(第一次报数列表)中的索引本应该为L[5+6],意思是在6的索引L[5]后,往后数6个人,即为在L[11].但此时从7数到10,也不够数到6个人,只能让先7-10先报完数,再从开头借几个人.怎么算借几个人呢?用求余数的方法.

   求余:
   求余可以理解为:有10个人,但只有0-5的号码牌(6张牌),即:
   10个人按顺序站一起时,手上拿的号码牌显示为:0,1,2,3,4,5,0,1,2,3
   那么:第7个人拿的号码牌是 7 % 6(张牌) == 1 这个1代表的是6张牌的第一张牌,牌值为1-1=0
        第8个人拿的号码牌是 8 % 6(张牌) == 2 这个2代表的是6张牌的第二张牌,牌值为2-1=1
        第9个人拿的号码牌是 9 % 6(张牌) == 3 这个3代表的是6张牌的第三张牌,牌值为3-1=2
        第10个人拿的号码牌是 10 % 6 == 4    这个4代表的是6张牌的第四张牌,牌值为4-1=3
        …
        回头再看1-9号人:
        第1个人拿的号码牌是 1 % 6 == 1 这个1代表的是6张牌的第一张牌,牌值为1-1=0
        第2个人拿的号码牌是 2 % 6 == 2 这个2代表的是6张牌的第二张牌,牌值为2-1=1
        第3个人拿的号码牌是 3 % 6 == 3 这个3代表的是6张牌的第三张牌,牌值为3-1=2
        …
    可以看出规律:第n个人拿的号码牌的牌值是: (n % 有几张牌) – 1

    几张牌就是代表剩余的人数
    号码牌的牌值就是代表索引位置

   知道索引中的规律后,我们再来推导一遍:
   第一次出局的人在L = [1,2,3,4,5,6,7,8,9,10]中索引为L[5],即数字6.把6踢出局后,L变为9个人:L = [1,2,3,4,5,7,8,9,10],下一次从数字7开始数6个人,但7-10只有4人,所以需要从数字1以后接着数2人.那么第二次出局者的索引即为L[(5+6) % 9 – 1],即L[1],即2.2也出局后,此时的L = [1,3,4,5,7,8,9,10].那么第三次出局者的索引即为L[(1+6) % 8 – 1],即L[6],即9.
   第四次出局者的索引为:L[(第三次出局者的索引+6) % 7 – 1]
   …
   第九次出局者的索引为:L[(第八次出局者的索引+6) % 2 – 1]

   而同时我们发现,第一次出局的人的索引为:L[(0+6) % 10 – 1],即L[5]
   可以得出公式:在每次把出局者单纯踢出原队列,同时原队列删除出局者,大体保持不变的前提下,每一次出局者的索引为:
   L[(上一次出局者的索引+6) % 上一次出局后的剩余人数 – 1]
   而第0次出局者的索引为0
   共进行的轮数为总人数-1
   这种后一个结果是由前一个结果传参解答的问题,就是递归了.那么我们是否可以直接做个for或者while循环,让计算机帮我们计算出每一轮被踢出的人呢?
   L = list(range(1,10+1))
   index_ = 0          #因为每次出局人的索引是根据上一次出局的人的索引而变,所以单独设了一个变量
   for i in range(10-1):   #i的每一次遍历,相当于每一轮,从0-8,即为9次
       index_ = (index_+6) % len(L) – 1
       print(‘出局的人是:’,L[index_])
       del L[index_]
       if index_ == -1:#或者写成index_ < 0也可     #要保证index_永远是正数.因为当index_为负数时,比如index_ = -1时,进行+6操作本应该从列表开头开始数6位,得到索引L[(0+6) % 剩余人数 -1].但如果是负数时,就不是单纯的像后数6位,而是得到L[(-1+6) % 剩余人数 – 1],出局者的计算会失误.
           index_ = 0
    print(‘最后的存活者为:’,L)

”’

    原文作者:约瑟夫环问题
    原文地址: https://blog.csdn.net/korosue/article/details/86568220
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞