'''
@Description: In User Settings Edit
@Author: your name
@Date: 2019-08-14 10:59:05
@LastEditTime: 2019-08-14 16:47:34
@LastEditors: Please set LastEditors
给定n个球,其中只有1个球比其他球轻,请问至少比较多少次,可以找出该轻球?
两种思路:
算法1:
易知当n=1时,比较次数为0;当n=2或者3时,比较次数为1。
故可以将n个球尽可能平分成3堆,然后比较其中数量相等的2堆,若其中有一堆较轻,则轻球在其中;
若参与比较的两堆等重,则轻球在第3堆中。
按照相同的规则继续判断存在轻球的那一堆,直到找到轻球为止。
可以用递归函数实现本算法,每次递归时,取3堆球中数量最多的一堆,其值为((n + 2) // 3)。
算法2:
设比较次数为m,分析可知当n=1时,m=0;当n<=3^1时,m=1;当n<=3^2时,m=2;
以此类推,当3^(i-1) < n <= 3^i时,m=i。
可以构造一个3^i的列表,然后将n的值与列表元素比较,找出对应的m值。
具体的操作方法是将n个球分成3堆,当3^(m-1) < n <= 3^m时,先取出3^(m-1)个球作为第一堆,剩下的球若能平分成2堆则直接比较;
否则从第一堆中取出1个放到较少的那堆,使得第2、3堆球数量相等,然后比较第2、3堆球。
若轻球在第1堆,则可直接判断比较次数为1+(m-1);否则轻球在第2或3堆,因为第2、3堆球的数量不多于第1堆,
故最大比较次数不会大于m次。
'''
# -*- coding: utf-8 -*-
import random
def fun_1(n):
if n <= 1:
return 0
elif n in (2, 3):
return 1
else:
return 1 + fun_1((n + 2) // 3)
def fun_2(n):
a = [3**i for i in range(20)]
i = 0
while n > a[i]:
i += 1
return i
#模拟算法1的比较过程,并输出轻球的序号
def select_1(a, L, R):
print(f'分析区间:{L} : {R}')
# 不多于3个球,可以直接给出解
if L == R:
print('无需比较')
return L
global count
count += 1
if L + 1 == R:
if a[L] < a[R]:
return L
else:
return R
elif L + 2 == R:
if a[L] == a[L+1]:
return R
elif a[L] < a[L+1]:
return L
else:
return L+1
n = (R-L+1+2) // 3 # 第一堆球的数量
m = R - L + 1 - n # 剩下两堆球的数量
if m % 2 == 1: # 从第一堆球中拿出一个以便后两堆球能平分
n -= 1
m += 1 # 剩下两堆球的数量增1
r1 = (L, L+n-1) # 第一堆球所在区域
r2 = (L+n, L+n+m//2-1) # 第二堆球所在区域
r3 = (L+n+m//2, R) # 第三堆球所在区域
print(f'第{count}次比较,分成区间:{r1}, {r2}, {r3}')
if sum(a[r2[0]:r2[1]+1]) == sum(a[r3[0]:r3[1]+1]):
return select_1(a, r1[0], r1[1])
elif sum(a[r2[0]:r2[1]+1]) < sum(a[r3[0]:r3[1]+1]):
return select_1(a, r2[0], r2[1])
else:
return select_1(a, r3[0], r3[1])
#模拟算法2的比较过程,并输出轻球的序号
def select_2(a, L, R):
def num(n):
i = 0
while n > 3**i:
i += 1
return 3**(i-1)
print(f'分析区间:{L} : {R}')
# 不多于3个球,可以直接给出解
if L == R:
print('无需比较')
return L
global count
count += 1
if L + 1 == R:
if a[L] < a[R]:
return L
else:
return R
elif L + 2 == R:
if a[L] == a[L+1]:
return R
elif a[L] < a[L+1]:
return L
else:
return L+1
n = num(R-L+1) # 第一堆球的数量
m = R - L + 1 - n # 剩下两堆球的数量
if m % 2 == 1: # 从第一堆球中拿出一个以便后两堆球能平分
n -= 1
m += 1 # 剩下两堆球的数量增1
r1 = (L, L+n-1) # 第一堆球所在区域
r2 = (L+n, L+n+m//2-1) # 第二堆球所在区域
r3 = (L+n+m//2, R) # 第三堆球所在区域
print(f'第{count}次比较,分成区间:{r1}, {r2}, {r3}')
if sum(a[r2[0]:r2[1]+1]) == sum(a[r3[0]:r3[1]+1]):
return select_2(a, r1[0], r1[1])
elif sum(a[r2[0]:r2[1]+1]) < sum(a[r3[0]:r3[1]+1]):
return select_2(a, r2[0], r2[1])
else:
return select_2(a, r3[0], r3[1])
# for i in range(1, 30):
# print(i, fun_1(i), fun_2(i))
# s = 0
# for i in range(1, 300000):
# if fun_1(i) != fun_2(i):
# s += 1
# print(s)
n = 30
a = [2] * n
p = random.randrange(n)
a[p] = 1
print(p, a)
count = 0
print(f'轻球是:{select_1(a, 0, n-1)}, 比较次数:{count}')
count = 0
print(f'轻球是:{select_2(a, 0, n-1)}, 比较次数:{count}')