Почему разница в скорости между этими двумя функциями так велика? - PullRequest
5 голосов
/ 05 января 2011

Я читал некоторые тесты MIT opencourseware, и у них возник вопрос, который звучит так:

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

Одно из основных отличий состоит в том, что мое решение является итеративным, а решение учителя - рекурсивным.Я рассчитал 1000 запусков обеих наших функций с maxVal = 1 000 000.Вот фрагмент времени:

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-х годов.Я скопировал и вставил функцию решения прямо из документа, предоставленного MIT.Есть идеи?

Ответы [ 5 ]

7 голосов
/ 05 января 2011

Самая очевидная вещь, которую я вижу, состоит в том, что каждый раз, когда вы вызываете функцию учителя, она создает список из 1 000 000 целых чисел (при условии Python 2.x), а затем, когда он возвращает, он снова уничтожает этот список.

Это займет некоторое время.

4 голосов
/ 05 января 2011

Вы сказали, что тестировали оба сценария, чтобы убедиться, что они дают одинаковый ответ, но это не так. Сценарий учителя, как вы его написали, всегда будет возвращать последний элемент. Это из-за этих строк:

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 на сайте MIT. Во-вторых, скрипт учителя все равно не будет работать правильно (он всегда будет возвращать последнее число), потому что его двоичный поиск идет в неправильном направлении с результатом cmp. Это ошибка решения учителя в соответствии со спецификацией, которую легко исправить, изменив строку

    if cmp(s[mid]) == -1:

до

    if cmp(s[mid]) == 1:

На самом деле это не был ответ на вопрос о медлительности, который, очевидно, (как было указано) из-за выделения О (N) памяти (это большая часть), рекурсии, просмотра списка, потенциально вызывающий cmp дважды для каждого вызова bsearch, а не один раз и сохраняющий результат, и необходимость явно проверять, является ли (last - first) < 2 для каждого вызова bsearch, потому что он использует переменную last как входящую в диапазон возможных значений а не на 1 больше, чем максимально возможное значение. Кстати, ваш собственный код можно сделать немного быстрее, изменив строку:

            floor = med

до

            floor = med + 1

так что вы исключаете med из диапазона поиска. Вы уже знаете, что это не значение, основанное на результате cmp. Кстати, я бы не стал использовать cmp в качестве собственного имени функции, потому что это встроенная функция Python (я знаю, что в спецификации это cmpGuess).

3 голосов
/ 05 января 2011

Есть три вещи не так с версией учителя, все они были исправлены в версии ОП.

  1. s = range(maxVal) создает список в Python 2. Использование xrange () экономит время на создание списка и его уничтожение. Однако сама идея использования s бессмысленна, потому что s[i] == i для всех соответствующих i, поэтому s можно просто выбросить, сохранив поиск.

  2. Рекурсия: глубина рекурсии составляет около math.log (1000000.0, 2.0) ... около 20. Таким образом, это примерно 20 вызовов функций на вызов findNumber (), и вызовы функций очень дороги по сравнению с переходом вернуться к началу цикла while.

  3. Он вызывает cmp(s[mid]) дважды за предположение, а не один раз, так что это еще 20 потраченных впустую вызовов функций на вызов findNumber ().

3 голосов
/ 05 января 2011

Позвольте мне повторить мой комментарий в форме ответа: range(maxval) выделяет весь список, поэтому алгоритм учителя имеет Θ(maxval) сложность пространства и, следовательно, Θ(maxval) сложность времени (создание такого списка требует времени).Поэтому решение учителя не имеет «минимально возможную сложность по времени».

Когда вместо этого используется xrange(maxval), весь список больше не выделяется.Как у вас, так и у решения учителя есть Θ(log(maxval)) сложность времени.

Более того, ваше решение имеет Θ(1) сложность пространства, в то время как (оптимизированное) решение учителя имеет Θ(log(maxval)) сложность пространства - рекурсивные вызовы занимают стек памяти.

0 голосов
/ 05 января 2011

У меня нет установленного Python в данный момент, так что на первый взгляд может быть, потому что учитель использовал рекурсию (в bsearch)?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...