我一直在阅读麻省理工学院的一些开放式课件,他们有一个问题是这样的:
6)考虑下面指定的两个用于播放“猜数字游戏”的函数。
def cmpGuess(guess):
"""Assumes that guess is an integer in range(maxVal). returns -1 if guess is < than the magic number, 0 if it is equal to the magic number and 1 if it is greater than the magic number."""
def findNumber(maxVal):
"""Assumes that maxVal is a positive integer. Returns a number, num, such that cmpGuess(num) == 0."""
Write a Python implementation of findNumber that guesses the magic number defined by cmpGuess. Your program should have the lowest time complexity possible. """
这是我的实施:
def find(maxVal):
ceiling = maxVal
floor = 0
while 1:
med = ((ceiling + floor) / 2)
res = cmp(med)
if res == 1:
ceiling = med
elif res == -1:
floor = med
else:
return med
以下是老师提供的答题纸实施:
def findNumber(maxVal):
""" Assumes that maxVal is a positive integer. Returns a number, num, such that cmpGuess(num) == 0 """
s = range(0, maxVal)
return bsearch(s, 0, len(s) -1)
def bsearch(s, first, last):
if (last-first) < 2:
if cmp(s[first]) == 0:
return first
else:
return last
mid = first + (last -first)/2
if cmp(s[mid]) == 0:
return s[mid]
if cmp(s[mid]) == -1:
return bsearch(s, first, mid -1)
return bsearch(s, mid + 1, last)
这是我们的两个函数使用的cmp函数,根据规范:
def cmp(guess):
if guess > num:
return 1
elif guess < num:
return -1
else: return 0
一个主要的区别是我的解决方案是迭代的,而教师的解决方案是递归的。我使用maxVal = 1,000,000对两个函数进行了1000次运行。这是时间片段:
t = timeit.Timer('find(1000000)', 'from __main__ import find,cmp')
t1 = timeit.Timer('findNumber(1000000)', 'from __main__ import findNumber,bsearch')
print str(t.timeit(1000))
print str(t1.timeit(1000))
我的跑步:0.000621605333677s
老师们跑了: 29.627s
这可能不对。我连续几次计时,并且在所有情况下,第二个功能都带来了荒谬的30秒结果。我直接从麻省理工学院提供的文件中复制粘贴解决方案功能。有任何想法吗?
答案 0 :(得分:7)
我能看到的最明显的事情是,每当你调用老师的函数时,它会创建一个包含1,000,000个整数的列表(假设是Python 2.x),然后当它返回时它再次销毁该列表。
这需要一段时间。
答案 1 :(得分:4)
你说你测试过两个脚本以确保它们给出相同的答案,但它们没有。正如您所写的那样,教师的脚本将始终返回最后一个元素。这是因为以下几行:
def bsearch(s, first, last):
if (last-first) < 2:
if cmp(s[first]) == 0:
return first
else:
return last
应该是
def bsearch(s, first, last):
if (last-first) < 2:
if cmp(s[first]) == 0:
return first
else:
return last
换句话说,有一个缩进错误,我意识到它也在麻省理工学院网站上的pdf中。其次,教师的脚本仍然无法正常工作(它将始终返回最后一个数字),因为他的二进制搜索方向与cmp的结果方向错误。根据规范,这是教师解决方案的错误,并通过更改行
轻松修复 if cmp(s[mid]) == -1:
到
if cmp(s[mid]) == 1:
由于O(N)内存的分配(这是一个很大的内存),递归,列表查找,这显然不是一个缓慢问题的答案(正如已经指出的那样)。为每个bsearch调用调用cmp可能两次而不是一次并存储结果,并且每次调用(last - first) < 2
时必须明确检查bsearch
是否因为他正在使用变量last
在可能值的范围内,而不是比最高可能值多1。顺便说一句,通过更改行可以使您自己的代码更快一些:
floor = med
到
floor = med + 1
这样您就可以从搜索范围中排除med。您已经知道它不是基于cmp结果的值。顺便说一句,我不会使用cmp
作为我自己的函数名,因为它是一个Python内置函数(我知道它在规范中是cmpGuess
)。
答案 2 :(得分:3)
让我以答案的形式重复我的评论:range(maxval)
分配整个列表,因此教师的算法具有Θ(maxval)
空间复杂度,因此Θ(maxval)
时间复杂度(创建这样的列表需要时间)。因此,教师的解决方案不
“可能的时间复杂度最低。”
当使用xrange(maxval)
时,不再分配整个列表。您和教师的解决方案都有Θ(log(maxval))
时间复杂度。
此外,您的解决方案的空间复杂度为Θ(1)
,而(优化的)教师解决方案的空间复杂度为Θ(log(maxval))
- 递归调用会占用堆栈内存。
答案 3 :(得分:3)
教师的版本有三个问题,所有这些都已在OP的版本中修复。
s = range(maxVal)
在Python 2中创建一个列表。使用xrange()可以节省创建列表并销毁它的时间。然而,使用s的整个想法是无稽之谈,因为s[i] == i
对于所有相关的i,所以s可以被丢弃,保存查找。
递归:递归深度大约是math.log(1000000.0,2.0)......大约20个。所以每次调用findNumber()大约有20个函数调用,而且函数调用与跳转相比非常昂贵回到while循环的开始。
每次猜测时调用cmp(s[mid])
两次而不是一次,这样每次调用findNumber()时又会浪费20次函数调用。
答案 4 :(得分:0)
我目前没有安装Python,所以一眼就可能因为老师使用了递归(在bsearch中)?