#python #algorithm #dictionary #hash #time-complexity
#python #алгоритм #словарь #хэш #временная сложность
Вопрос:
Мы все знаем, что временная сложность вставки чего-либо в хэш-набор составляет в среднем O (1) . Тем не менее, я сосредоточен на наихудшем поведении. Я имею в виду, что должна существовать определенная последовательность интегральных ключей, которая может вызвать множество коллизий хэшей при последовательной вставке элементов, и это «наихудший случай», о котором я говорил.
Более конкретно:
- Какова наихудшая сложность вставки пары ключ-значение в словарь относительно N, количества существующих элементов в словаре?
- Каков соответствующий состязательный ввод?
Вот моя работа до сих пор
from time import time
from math import log2
dummy = {}
elapsed = float('inf')
for k in range(20, 38):
tic = time()
dummy[1<<(1<<k)] = k
elapsed, last = time() - tic, elapsed
print(f"{k=}, {elapsed=:.4f}, {elapsed/last=:.4f}")
Приведенный выше состязательный ввод, по-видимому, приводит к временной сложности O (N ^ 2)… или, может быть, это O(N ^ 2 * ln (N))? Я не могу сказать.
k=20, elapsed=0.0002, elapsed/last=0.0000
k=21, elapsed=0.0004, elapsed/last=2.1508
k=22, elapsed=0.0007, elapsed/last=1.8340
k=23, elapsed=0.0013, elapsed/last=1.7360
k=24, elapsed=0.0024, elapsed/last=1.9031
k=25, elapsed=0.0048, elapsed/last=1.9879
k=26, elapsed=0.0086, elapsed/last=1.8034
k=27, elapsed=0.0182, elapsed/last=2.1088
k=28, elapsed=0.0361, elapsed/last=1.9787
k=29, elapsed=0.0730, elapsed/last=2.0242
k=30, elapsed=0.1419, elapsed/last=1.9442
k=31, elapsed=0.2844, elapsed/last=2.0040
k=32, elapsed=0.5733, elapsed/last=2.0154
k=33, elapsed=1.1388, elapsed/last=1.9865
k=34, elapsed=3.5476, elapsed/last=3.1152
k=35, elapsed=8.3034, elapsed/last=2.3405
k=36, elapsed=16.0347, elapsed/last=1.9311
k=37, elapsed=35.1934, elapsed/last=2.1948
Комментарии:
1. Вы тратите все свое время на построение и хеширование абсурдно огромных чисел, а не на вставку dict.
1<<(1<<k)
быстро становится абсурдно огромным.2. Было бы очень легко создать класс с постоянным хэшем.
3. @PaulHankin: только для нескольких конкретных типов. Многие классы по-прежнему имеют чрезвычайно уязвимые хэши, в том числе
int
.4. @user2357112supportsMonica AFAIK хэш целого числа — это само целое число. См. github.com/python/cpython/blob/master/Objects/dictobject.c#L140
5. @nalzok: Да (для небольших целых чисел), что крайне уязвимо. (Шаблон для больших целых чисел также статичен, регулярен и чрезвычайно уязвим, хотя это не то, что вызывает замедление ваших тестов.)
Ответ №1:
Для вставки Dict требуется O (n) сравнений элементов в худшем случае, где n
— количество занятых записей в таблице. Кроме того, для отдельной вставки может потребоваться перестроение хэш-таблицы, что может привести к повторному столкновению каждого элемента с каждым другим элементом и к сравнению элементов O (n ^ 2).
Однако замедление ваших тестов вызывает не столкновение хэшей — ваши тесты тратят все свое время на построение и хеширование абсурдно огромных целых чисел. 1<<(1<<33)
например, целое число размером в целый гигабайт.
Довольно легко создать состязательный ввод. Например,
In [4]: for i in range(20):
...: print(hash(i*(2**61-1)))
...:
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
вы можете просто взять кратные 2**61-1
.
Комментарии:
1. Просто чтобы добавить к этому, когда я попробовал исходный код, я нажал a
MemoryError
. В моем случае индивидуальное хеширование, похоже, не требовало слишком долгого тестированияhash(1<<(1<<30))
, но сохранение значения было.2. Кратные значения
2*61-1
показывают сложность O (1) на моей машине (я просто изменил свой код для использованияdummy[k*(2**61-1)] = k
вместо этого). Выглядит ли результат квадратичным по вашему? Кстати, я использовал1<<n
для противодействия «другой половине стратегии» и решилn = 1<<k
лучше наблюдать за тенденцией. Оглядываясь назад, я действительно потратил много процессорного времени на построение огромных целых чисел, делая это.3. @nalzok: Вы неправильно рассчитываете время. Вероятно, он работает недостаточно долго. Вероятно, вы также неправильно интерпретируете выходные данные, учитывая, что вы думали, что первоначально опубликованные вами тайминги были O (N ^ 2) — первоначально опубликованные вами тайминги экспоненциальны, а не квадратичны.
4. @user2357112supportsMonica первоначально опубликованные вами тайминги являются экспоненциальными, а не квадратичными. Упс, вы правы, и это действительно экспоненциально с соответствующим
k
. Позвольте мне перепроверить…