看到了一个题:说的是有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值偏大的时候就可以洗洗睡了。
以上是这几天折腾的东西,欢迎广大基友前来吐槽。