Python 为什么这两个功能之间的速度差异如此之大?
我一直在阅读麻省理工学院的一些开放式课件问答,他们有一个问题是这样的:Python 为什么这两个功能之间的速度差异如此之大?,python,function,performance,Python,Function,Performance,我一直在阅读麻省理工学院的一些开放式课件问答,他们有一个问题是这样的: 6)考虑下面所用的两个函数,用来玩“猜数字游戏”。 以下是老师提供的答题纸: def findNumber(maxVal): """ Assumes that maxVal is a positive integer. Returns a number, num, such that cmpGuess(num) == 0 """ s = range(0, maxVal) return bsea
6)考虑下面所用的两个函数,用来玩“猜数字游戏”。 以下是老师提供的答题纸:
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)
我的跑步时间:0.000621605333677s
教师跑步时间:29.627s这不可能是对的。我连续计时了好几次,在所有情况下,第二个函数的结果都是荒谬的30秒。我直接从MIT提供的文档中复制粘贴了解决方案函数。有什么想法吗 我现在还没有安装Python,所以从一看可能是因为老师使用了递归(在b搜索中)?我能看到的最明显的事情是,每次调用老师的函数时,它都会创建一个包含1000000个整数的列表(假设Python 2.x),然后当它返回时,它会再次销毁该列表
这需要一段时间。让我以回答的形式重复我的评论:
range(maxval)
分配整个列表,因此教师的算法具有Θ(maxval)
空间复杂度,因此Θ(maxval)
时间复杂度(创建这样的列表需要时间)。因此,教师的解决方案不
具有“尽可能低的时间复杂度”
当使用xrange(maxval)
时,将不再分配整个列表。您和老师的解决方案都具有时间复杂性
此外,您的解决方案具有
Θ(1)
空间复杂度,而(优化)教师的解决方案具有Θ(log(maxval))
空间复杂度-递归调用消耗堆栈内存。教师的版本有三个问题,所有这些问题都已在OP的版本中修复
s=range(maxVal)
在Python 2中创建一个列表。使用xrange()可以节省创建和销毁列表的时间。然而,使用s的整个想法都是胡说八道,因为对于所有相关的i,s[i]==i
,因此s可以被扔掉,省去了查找cmp(s[mid])
两次,而不是一次,因此每次调用findNumber()又浪费了20次函数调用您说您测试了两个脚本以确保它们给出相同的答案,但它们没有。正如您编写的那样,教师脚本将始终返回最后一个元素。这是因为以下几行:
def bsearch(s, first, last):
if (last-first) < 2:
if cmp(s[first]) == 0:
return first
else:
return last
到
这并不是真正解决缓慢问题的答案,这显然是(正如已经指出的)由于O(N)内存的分配(这是一个大内存)、递归、列表查找、每次bsearch调用可能调用cmp两次而不是一次并存储结果,以及必须显式检查是否(last-first)<2
用于每次调用b搜索
,因为他使用的变量last
包含在可能值的范围内,而不是比可能的最高值多1。顺便说一下,通过更改行,您自己的代码可以稍微快一点:
floor = med
到
因此,您将med排除在搜索范围之外。您已经知道它不是基于cmp结果的值。顺便说一句,我不会使用
cmp
作为我自己的函数名,因为它是Python内置函数(我知道它在规范中是cmpGuess
)第一个问题(我假设您先检查了它,但让我们确定一下):两者都给出了正确的答案吗?俗话说:如果它不一定是正确的,我可以把它降到0。s=range(0,maxVal)
???老师的解决方案需要O(maxval)
space。是的,两者都是正确的,一个比另一个慢几个数量级。@阿比纳夫:第二个建议是否定的,len
在列表(以及大多数其他集合和序列,包括dicts、set和string)上是O(1).我无法使用time.time()
为教师版本重现较长的时间。你能测试一下吗?您还可以检查您是否正在交换吗?这可以解释明显的减速或超过了最大递归深度,但不是47662x减速。@delnan:最大递归深度是关于math.log(1000000.0,2.0)。。。大约20。啊,是的,那确实需要一段时间。完全忘记了range
创建一个列表(Python3range
创建了一个智能迭代器,它甚至提供了-便宜的,因为它很聪明,可以从参数-索引计算第n项)。您可以在早期版本的Python中使用xrange惰性地生成迭代器。事实上,有没有不使用xrange的理由?@pisswillis,是的,但不经常。例如,您不能对xrange(或Python 3.x中的range
)进行切片。只是想知道,您是否参加了该课程,或者是否也在学习开放式课程?不,我以前没有参加过该课程,也没有在OCW上看过,但我看过其他一些课程。当我注意到第一个错误(缩进)时,我决定看看原始解决方案。我给开放式课程发了一封关于这些问题的电子邮件,希望解决方案能够得到纠正。
def cmp(guess):
if guess > num:
return 1
elif guess < num:
return -1
else: return 0
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))
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
if cmp(s[mid]) == -1:
if cmp(s[mid]) == 1:
floor = med
floor = med + 1