Сложность времени путаница - PullRequest
1 голос
/ 05 февраля 2010

Я всегда был немного смущен этим, возможно, из-за моего непонимания в компиляторах. Но давайте использовать Python в качестве примера. Если бы у нас был большой список чисел, называемый numlist, и мы хотели избавиться от любых дубликатов, мы могли бы использовать оператор set в списке, например, set (numlist). Взамен у нас будет набор наших номеров. Насколько мне известно, эта операция будет выполнена за O (n) раз. Хотя, если бы я создал свой собственный алгоритм для выполнения этой операции, то самое лучшее, на что я мог надеяться, это O (n ^ 2).

Чего я не понимаю, так это того, что внутренняя операция, такая как set (), выполняется намного быстрее, чем внешняя по отношению к языковому алгоритму. Проверка еще должна быть сделана, не так ли?

Ответы [ 6 ]

3 голосов
/ 05 февраля 2010

Вы можете сделать это за Θ(n) среднее время, используя хеш-таблицу. Поиск и вставка в хеш-таблицу в среднем Θ(1). Таким образом, вы просто просматриваете элементы n и проверяете, есть ли они в хэш-таблице, и не вставляют ли они элемент.

Чего я не понимаю, так это того, что внутренняя операция, такая как set (), выполняется намного быстрее, чем внешняя по отношению к языковому алгоритму. Проверка еще должна быть сделана, не так ли?

Асимптотическая сложность алгоритма не изменяется, если реализуется реализаторами языка, по сравнению с реализацией пользователем языка. Поскольку оба они реализованы на полном языке Тьюринга с моделями памяти с произвольным доступом, они имеют одинаковые возможности, и алгоритмы, реализованные в каждом, будут иметь одинаковую асимптотическую сложность. Если алгоритм теоретически O(f(n)), то не имеет значения, будет ли он реализован на ассемблере, C # или Python для него все равно будет O(f(n)).

1 голос
/ 05 февраля 2010

Вы можете сделать это в O (n) на любом языке, в основном как:

# Get min and max values O(n).

min = oldList[0]
max = oldList[0]
for i = 1 to oldList.size() - 1:
    if oldList[i] < min:
        min = oldList[i]
    if oldList[i] > max:
        max = oldList[i]

# Initialise boolean list O(n)

isInList = new boolean[max - min + 1]
for i = min to max:
    isInList[i] = false

# Change booleans for values in old list O(n)

for i = 0 to oldList.size() - 1:
    isInList[oldList[i] - min] = true

# Create new list from booleans O(n) (or O(1) based on integer range).

newList = []
for i = min to max:
    if isInList[i - min]:
        newList.append (i)

Я предполагаю, что append - это операция O (1), которой она должна быть, если только у разработчика нет мозгов. Таким образом, при k шагах каждого O (n) у вас все еще есть операция O (n).

Неважно, выполняются ли шаги явно в вашем коде или они выполняются под покровом языка. В противном случае вы могли бы утверждать, что C qsort была одной операцией, и теперь у вас есть Святой Грааль процедуры сортировки O (1): -)

Как многие люди обнаружили, вы часто можете обменять сложность пространства на сложность времени. Например, вышеприведенное работает только потому, что нам разрешено вводить переменные isInList и newList. Если это не было разрешено, следующим лучшим решением может быть сортировка списка (вероятно, не лучше O (n log n)) с последующей операцией O (n) (я думаю) для удаления дубликатов.

Крайний пример: вы можете использовать тот же метод лишних пробелов для сортировки произвольного числа 32-разрядных целых чисел (скажем, у каждого из них только 255 или менее дубликатов) за O (n) времени, при условии, что вы можете выделить около четырех миллиардов байт для хранения счетчиков .

Просто инициализируйте все счета до нуля и пробегайте каждую позицию в вашем списке, увеличивая количество на основе числа в этой позиции. Это O (n).

Затем начните с начала списка и пробежитесь по массиву count, поместив столько правильных значений в список. Это O (1), где 1 составляет около четырех миллиардов конечно, но все еще постоянное время: -)

Это также O (1) сложность пространства, но очень большая «1». Обычно компромиссы не настолько серьезны.

1 голос
/ 05 февраля 2010

Взятие списка и превращение его в набор с помощью set() - O (n).

Это потому, что set реализован как хэш-набор. Это означает, что для проверки наличия чего-либо в наборе или для добавления чего-либо в набор требуется только O (1), постоянное время. Таким образом, чтобы создать набор из итерируемого (например, списка), вы просто начинаете с пустого набора и добавляете элементы итерируемого по одному. Поскольку имеется n элементов и каждая вставка занимает O (1), общее время преобразования итерируемого в набор составляет O (n).

Чтобы понять, как работает реализация хеша, см. Википедию artcle на хеш-таблицах

1 голос
/ 05 февраля 2010

Граница сложности алгоритма совершенно не связана с тем, реализован ли он «внутренне» или «внешне»

0 голосов
/ 05 февраля 2010

Здесь есть два вопроса.

Сложность по времени (выражаемая в больших обозначениях O) - это формальная мера того, сколько времени потребуется алгоритму для работы с заданным размером набора. Это больше о том, насколько хорошо масштабируется алгоритм, чем об абсолютной скорости.

Фактическая скорость (скажем, в миллисекундах) алгоритма - это сложность времени, умноженная на постоянную (в идеальном мире).

Два человека могут реализовать один и тот же алгоритм удаления дубликатов со сложностью O (log (n) * n), но если один из них запишет его в Python, а другой - в оптимизированном C, тогда программа на C будет работать быстрее.

0 голосов
/ 05 февраля 2010

От руки я не могу придумать, как это сделать в O (n), но вот классная вещь:

Разница между n ^ 2 и n настолько велика, что разница между вашей реализацией и реализацией Python крошечная по сравнению с алгоритмом, используемым для его реализации. n ^ 2 всегда хуже, чем O (n), даже если n ^ 2 находится в C, а O (n) в python. Вы никогда не должны думать, что такого рода различия происходят из-за того, что вы не пишете на низкоуровневом языке.

Тем не менее, если вы хотите реализовать свой собственный, вы можете сделать сортировку, а затем удалить дубликаты. сортировка n * ln (n) и удаление дубликатов в O (n) ...

...