Skip to content

Commit f0e8913

Browse files
agorinenkoAnton Gorinenko
andauthored
Hash table
Co-authored-by: Anton Gorinenko <[email protected]>
1 parent 7cf11cb commit f0e8913

File tree

2 files changed

+129
-1
lines changed

2 files changed

+129
-1
lines changed

img/hash_map.jpg

16.3 KB
Loading

tutorial/hash_table.md

Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,129 @@
1-
# Хеш таблицы
1+
# Хеш таблицы
2+
3+
![Хеш таблицы](../img/hash_map.jpg)
4+
5+
Хеш таблица - структура данных, которая использует хеш функцию для определения порядка хранения элементов. Различают два
6+
вида хеш таблиц:
7+
8+
1. Множества - неупорядоченная, изменяемая последовательность уникальных элементов.
9+
2. Словари - изменяемая последовательность элементов, содержащих в себе ключ и сами данные.
10+
11+
Основные преимущества хеш таблиц - это быстрая вставка и поиск элементов, временная сложность этих операций
12+
составляет **O(1)**.
13+
14+
Ключевой идеей в реализации хеш таблиц является хеш функция, которая определяет индекс хранения элемента. При вставке
15+
элемента хеш функция вычисляет индекс ячейки, куда будет вставлен новый элемент. При поиске элемента та же самая функция
16+
вычисляет его расположение.
17+
18+
## Хеш функция
19+
20+
Если говорить простым языком, хеш-таблица – это массив, индекс которого вычисляется на основе хеш-функции по ключу. Если
21+
для разных ключей хеш-функция возвращает одинаковый результат своего выполнения, то говорят о том, что произошла
22+
коллизия, поэтому основная задача хеш-функции избегать коллизий и компактно «укладывать» объекты в таблицу.
23+
24+
Основной массив хранит набор так называемых корзин или сегментов. Каждый сегмент также можно представить в виде массива
25+
элементов, хеш функция для которых возвращает одинаковое значение. На рисунке выше схематически представлено устройство
26+
хеш-таблицы. Так для ключа ``0``, хеш функция ``key % 5`` вернула нулевой индекс, а для ключей ``1987`` и ``2`` индекс
27+
совпал. В таком случае в связанную с индексом ``2`` корзину добавятся 2 элемента. Из этого следует вывод, что
28+
29+
```
30+
Хеш-функция будет зависеть от диапазона значений ключа и общего количества сегментов.
31+
```
32+
33+
Основная задача хеш функции - равномерно заполнять основной массив и делать размер корзин как можно меньше. Идеальная
34+
хеш функция та, при которой ячейка основного массива однозначно соотносится с элементом. Однако в большинстве случаев
35+
хеш-функция не идеальна и всегда приходится искать компромисс между количеством сегментов и их емкостью.
36+
37+
Обратите внимание, что хеш-функция возвращает одно и то же значение только для неизменяемых объектов. Если в список было
38+
добавлено новое значение, функция вернет новый результат. Именно поэтому в хеш таблице хранить(или использовать в
39+
качестве ключа) изменяемые типы данных запрещено. Иначе при изменении значения, должен измениться его индекс, а это
40+
недопустимо.
41+
42+
## Разрешение коллизий
43+
44+
Идеальная кеш функция не подвержена коллизиям - ячейка основного массива однозначно соотносится с элементом.
45+
46+
Алгоритм разрешения коллизий должен решать следующие вопросы:
47+
48+
1. Как реализовать хранение значений в одной корзине?
49+
2. Что делать, если одной корзине назначено слишком много значений?
50+
3. Как производить поиск элемента в корзине?
51+
52+
Все эти вопросы связаны с текущей длиной и общей вместимостью корзины. Если количество ключей корзины небольшое можно
53+
использовать простой массив. Если количество большое - сбалансированное по высоте бинарное дерево.
54+
55+
## Оценка эффективности хеш таблиц
56+
57+
Если мы имеем ``M`` ключей в таблице, то **пространственная** сложность составляет **O(M)**.
58+
59+
Временная сложность тесно связана с устройством корзин. Если принять, что это массивы с достаточно малым размером ``N``,
60+
то можно допустить, что временная сложность поиска и вставки константная - **O(1)**. В худшем случае при плохой хеш
61+
функции и большом ``N``, временная сложность для массива составит **O(N)** для поиска и **O(1)** для вставки. Если
62+
корзина реализована как сбалансированное по высоте бинарное дерево, то временная сложность поиска и вставки равна
63+
**O(log(N))**.
64+
65+
## Множества
66+
67+
Множества - неупорядоченная, изменяемая последовательность уникальных элементов. Помимо уникальности, все объекты
68+
множества должны быть хешируемыми. Отличительной особенностью множества являются математические операции над ними:
69+
объединение, пересечение, разность и симметрическая разность.
70+
71+
Временная сложность операций над множествами:
72+
73+
Добавление элемента - **O(1)**
74+
75+
Удаление элемента - **O(1)**
76+
77+
Проход по множеству - **O(M)**
78+
79+
Проверка вхождения элемента - **O(1)**
80+
81+
Получение длины последовательности - **O(1)**
82+
83+
Хотя все три операции (поиск, вставка, удаление элементов) в среднем выполняются за время O(1), требуется дополнительное
84+
время на вычисление хеш-функции, и если её вычисление будет неэффективным, то и все операции над множеством будут иметь
85+
увеличенную временную сложность.
86+
87+
```
88+
Основной сценарий использования множеств - это проверка того, встречался ли элемент ранее.
89+
```
90+
91+
## Словари
92+
93+
Словарь – это изменяемая последовательность элементов, содержащих в себе ключ и сами данные. Если в списках и кортежах
94+
мы могли взять значение по индексу, то в словарях используется ключ. Объект dict не хешируемый, однако в качестве ключей
95+
можно использовать только хешируемые типы данных.
96+
97+
Временная сложность операций над словарями:
98+
99+
Добавление элемента - **O(1)**
100+
101+
Удаление элемента - **O(1)**
102+
103+
Проход по словарю - **O(M)**
104+
105+
Проверка вхождения ключа - **O(1)**
106+
107+
Получение длины последовательности - **O(1)**
108+
109+
Взятие элемента по ключу - **O(1)**
110+
111+
```
112+
Основной сценарий использования словарей - это хранение дополнительной информации, связанной с ключем.
113+
```
114+
115+
К примеру дан массив целых чисел, верните индексы двух чисел таким образом, чтобы в сумме они ровнялись определенному
116+
числу. ``array = [1, 3, 7, 9]``, ``target = 12``. Если бы требовалось проверить только наличие двух чисел, мы могли бы
117+
пробегаться по array, вычислять разницу ``diff = target - current_value`` и, используя дополнительный объект set,
118+
проверять, входит ли в него diff. Однако нам нужно вернуть индексы элементов, поэтому помимо diff следует также хранить
119+
индекс.
120+
121+
```
122+
Другой частый сценарий - это агрегация некой информации по ключу.
123+
```
124+
125+
Дана строка. Найдите в ней любой неповторяющийся символ и верните его индекс. Если он не существует, верните -1. Самый
126+
простой способ решить эту задачу — это подсчитать для каждого символа количество его появлений в строке. А затем
127+
пройтись по полученным результатам, для определения первого уникального символа. Например, для слова ``собака`` получим
128+
``{'с': 1, 'о': 1, 'б': 1, 'а': 2, 'к', 1}``. Первый символ, который встречается один раз - ``с``.
129+

0 commit comments

Comments
 (0)