Python笔记01--猴子出圈问题

看到了一个题:说的是有N只猴子围成一个圈坐着,从第一个猴子开始报数依次从1报到M,报到M的猴子将被踢出圈,并从下一个猴子开始重新从报数(从1到M)。。。求最后剩下K只猴子的序号。
看到这题我首先想到的是用列表解决,建立一个N只猴子的列表,然后逐个踢出圈,题出后重置列表(将下一个猴子放置列表第一位),于是就有了如下代码:

def joseph_1(n,m,k):
    start_time = time.time()
    lists = [i for i in range(1,n+1)]#建立n只猴子的列表
    times = 0
    while len(lists)>k:
        times += 1
        t = m % len(lists)#m可能大于n,所以这里通过取余的方式获得第t个猴子
        lists.pop(t-1)#第t只猴子是列表的第t-1个元素

        if t == 0:#在t等于0 的时候说明出局的是最后一只猴子,不需要重置列表
            continue
        else:
            lists = lists[t-1:]+lists[:t-1]#重置列表
    end_time = time.time()
    print(lists)
    print('joseph_1用时:%.5f,while次数:%d'%((end_time-start_time),times))

但是问题来了,如果有1w只猴子且只留下最后一只猴子的话,就意味着这里得重置列表9999次,这个地方应该可以优化。比如有10只猴子,报数为2的话,很明显在第一圈的时候出局的都是偶数为上的猴子(2,4,6,8,10),那可以在这个地方做一下优化:在m

def joseph_2(n,m,k):
    if m ==1:#m为1的情况不用程序计算,肯定是倒数的那几个留下来
        lists = [i for i in range(n-k+1,n+1)]
        print(lists)
        return
    times = 0
    start_time = time.time()
    lists = [i for i in range(1, n + 1)]
    while len(lists) > k:
        times +=1
        len_list = len(lists)
        num = len_list//m
        # 判断剩余猴子数与m的关系,剩余猴子数大于m则用优化方法,如果小于就老老实实跑吧。
        #同时取得要踢出去的猴子数目
        t = m % len_list
        if not num:
            lists.pop(t - 1)
            if t == 0:
                continue
            else:
                lists = lists[t - 1:] + lists[:t - 1]
        else:
            for i in range(num,0,-1):#当时不想算偏移为就从后面的猴子开始踢
                lists.pop(i*t-1)
            if num*t == 0:
                continue
            else:
                lists = lists[num*(t-1):] + lists[:num*(t-1)]
    end_time = time.time()
    print(lists)
    print('joseph_2用时:%.5f,while次数:%d'%((end_time-start_time),times))

后来又想解决m大于n的情况,主要是下标要如何求得?这里直接上代码了:

def get_index(n,t,d=1):
    if n ==1:
        return t
    else:
        return int(t + (t+d)*(n-1) + d*(n-1)*(n-2)/2 )

这里基本上是通过找规律的方式得到的:
比如n=10 m=11 的时候,被踢的是(1,3,6,10)
好吧草稿纸不见了,稍后补上具体推到过程,an = a1 +以a2为首项项数为n-1的一个等差数列的和,这个地方公差就是d也是上面的num(可以理解为转了几圈的意思)
所以改进后的代码如下:

def joseph_3(n,m,k):
    if m == 1:
        lists = [i for i in range(n - k + 1, n + 1)]
        print(lists)
        return
    times = 0
    start_time = time.time()
    lists = [i for i in range(1, n + 1)]
    len_list = len(lists)
    while len_list > k:
        times +=1
        num = len_list//m
        t = m % len_list
        if t ==0:
            lists.pop(t-1)
            len_list = len(lists)
        elif not num :
            num = m//len_list
            i = 1
            while True:
                index = get_index(i,t,num)
                temp = index - i
                #这地方没有倒着踢了,第i个踢的元素下标为index-1-(i-1) 减去(i-1)是因为踢出i个元素之后后面的元素下标会向前移动(i-1)个,为什么-1?因为i=1的时候没有影响啊!
                i += 1
                if index > len_list:#这个地方不要用for确定循环次数了
                    break
                new_index = temp
                lists.pop(new_index)
            lists = lists[new_index:] + lists[:new_index]
            len_list = len(lists)
        else:
            for i in range(num,0,-1):
                lists.pop(i*t-1)
                len_list = len(lists)
            if num*t == 0:
                continue
            else:
                lists = lists[num*(t-1):] + lists[:num*(t-1)]
                len_list = len(lists)
    end_time = time.time()
    print(lists)
    print('joseph_3用时:%.5f,while次数:%d'%((end_time-start_time),times))

当时废了老大劲推算下标,发现这个和之前的第二个比并没有很大区别,甚至在有些时候比第二种还要慢,但是觉得如果对下标在研究一下应该可以得出一个通解就是直接通过公式计算出最后的结果,写完这个我就去查查约瑟夫环问题。
所以这个地方我并没有写完(一句废话)
后来网上看视频讲的了链表结构的时候,说可以用列表实现链表的功能,于是自己又试验了一下,不废话了直接上代码:

def next_cur(cur,lists):#找到下一个地址
    return lists[cur][1]
def del_next_cur(cur,lists,len_lists):#删除下一个地址的数据
    old_cur = lists[cur][1]
    new_cur = lists[old_cur][1]
    lists[cur][1] = new_cur
    len_lists -= 1
    return len_lists

def joseph_4(n,m,k):
    times = 0
    start_time = time.time()
    lists = [[i,i+1] for i in range(n+1)]
    lists[-1][1] = 1#改写最后一个的下标,是它指向第一个元素
    len_lists = n
    cur = 0
    while True:
        times += 1
        for i in range(m-1):
            cur = next_cur(cur,lists)
        #找到第m-1个并且删除下一个
        len_lists = del_next_cur(cur,lists,len_lists)
        if len_lists == k:
            for i in range(k):
                print(lists[cur][0],end=' ')
                cur = next_cur(cur,lists)
            end_time = time.time()
            print()
            print('joseph_4用时:%.5f,while次数:%d' % ((end_time - start_time), times))
            break

这个方法嘛….m小的时候很舒服,当m值偏大的时候就可以洗洗睡了。
以上是这几天折腾的东西,欢迎广大基友前来吐槽。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值