Что такое коллизия в хеш-таблице?

Ответ

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

Пример возникновения (псевдокод):

def simple_hash(key, table_size):
    return len(key) % table_size

# Обе строки дают одинаковый хеш для table_size = 10
index1 = simple_hash("John", 10)  # 4 % 10 = 4
index2 = simple_hash("Anna", 10)  # 4 % 10 = 4 -> КОЛЛИЗИЯ

Основные методы разрешения коллизий:

  1. Метод цепочек (Separate Chaining): Каждая ячейка массива содержит связный список (или другую структуру) всех элементов, попавших в неё.

    // Упрощенное представление
    HashMap<Integer, LinkedList<Entry>> table;
    // table[hash].add(new Entry(key, value));
    • + Простота, неограниченное количество элементов.
    • - Дополнительные затраты памяти на указатели, в худшем случае поиск O(n).
  2. Открытая адресация (Open Addressing): При коллизии элемент помещается в другую, свободную ячейку таблицы, согласно выбранному алгоритму проб.

    • Линейное пробирование: index = (hash + i) % size
    • Квадратичное пробирование: index = (hash + i^2) % size
    • Двойное хеширование: index = (hash1 + i * hash2) % size
    • + Лучшая локальность кэша, нет дополнительных структур.
    • - Сложнее удаление, требуется контроль коэффициента заполнения.

На практике: В Java HashMap до определённого размера использует открытую адресацию, затем переходит на цепочки. В Python dict использует открытую адресацию. Выбор метода влияет на производительность в конкретных сценариях.

Ответ 18+ 🔞

А, ну это же классика, ёпта! Сейчас объясню на пальцах, как это работает, а то некоторые думают, что хеш-таблица — это какая-то магия, а там, блядь, обычная коллизия поджидает.

Представь себе, есть у тебя массив, ящики такие. И есть волшебная функция — хеш-функция. Кидаешь в неё ключ (например, имя "John"), а она тебе выдает номер ящика, куда положить твое значение. Всё бы ничего, но ящиков-то ограниченное количество, а ключей — овердохуища!

И вот случается пиздец, который и зовётся коллизией. Это когда двум разным ключам наша функция выдает один и тот же номер ящика. Она, блядь, не справляется, понимаешь? Как будто два разных человека приходят на вечеринку, а им говорят: "Ребята, у вас на двоих один стул".

Вот смотри, простейший пример, прямо в лоб:

def simple_hash(key, table_size):
    return len(key) % table_size

# Обе строки дают одинаковый хеш для table_size = 10
index1 = simple_hash("John", 10)  # 4 % 10 = 4
index2 = simple_hash("Anna", 10)  # 4 % 10 = 4 -> КОЛЛИЗИЯ

Вот тебе и приехали. И "John", и "Anna" лезут в ящик номер 4. И что делать? А делать-то надо, иначе вся структура данных накрывается медным тазом.

Инженеры, эти хитрожопые, придумали два основных подхода, как из этой жопы вылезти.

Первый способ — метод цепочек (Separate Chaining). Суть проще пареной репы. Ты в каждый ящик кладёшь не одно значение, а, например, связный список. Пришёл "John" — положили в список ящика №4. Припёрся следом "Anna" — и его туда же, в этот же список, добавили. Получается такая цепочка в одном ящике.

  • Плюс: Простота хуёвая, и теоретически можно запихнуть сколько угодно элементов, хоть весь словарь Ожегова.
  • Минус: Памяти жрёт на указатели, а в худшем случае, если все ключи в один ящик попали, поиск превращается в долбёжку по списку — O(n), то есть полный пиздец.

Второй способ — открытая адресация (Open Addressing). Тут философия другая. Ящик занят? Ну и хуй с ним, пошёл искать свободный! Есть куча алгоритмов, куда именно идти искать:

  • Линейное пробирование: Пошёл в следующий по порядку ящик. index = (hash + i) % size
  • Квадратичное пробирование: Начинаешь прыгать по ящикам с ускорением. index = (hash + i^2) % size
  • Двойное хеширование: Самый крутой метод. У тебя вторая, запасная хеш-функция, которая говорит, на сколько шагов отпрыгнуть. index = (hash1 + i * hash2) % size
  • Плюс: Всё лежит в одном массиве, локальность кэша отличная, быстрее работает часто.
  • Минус: Удалять элементы — та ещё головная боль, и надо следить, чтобы таблица не заполнилась под завязку, а то поиск свободного ящика превратится в квест.

А на практике как? Да по-разному, чувак! В Java HashMap, пока таблица маленькая, юзает открытую адресацию, а как разрастётся — переключается на цепочки. В Python dict вообще на открытой адресации стоит. Выбор метода — это всегда компромисс, и от него дико зависит, будет твоя программа летать или еле ползать. Вот такая, блядь, наука.