为什么这两个功能之间的速度差异如此之大?

时间:2011-01-05 08:55:14

标签: python function performance

我一直在阅读麻省理工学院的一些开放式课件,他们有一个问题是这样的:

  

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秒结果。我直接从麻省理工学院提供的文件中复制粘贴解决方案功能。有任何想法吗?

5 个答案:

答案 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的版本中修复。

  1. s = range(maxVal)在Python 2中创建一个列表。使用xrange()可以节省创建列表并销毁它的时间。然而,使用s的整个想法是无稽之谈,因为s[i] == i对于所有相关的i,所以s可以被丢弃,保存查找。

  2. 递归:递归深度大约是math.log(1000000.0,2.0)......大约20个。所以每次调用findNumber()大约有20个函数调用,而且函数调用与跳转相比非常昂贵回到while循环的开始。

  3. 每次猜测时调用cmp(s[mid])两次而不是一次,这样每次调用findNumber()时又会浪费20次函数调用。

答案 4 :(得分:0)

我目前没有安装Python,所以一眼就可能因为老师使用了递归(在bsearch中)?