Как хэш-таблицы справляются с коллизиями?


Я слышал в моих классах степени, что a HashTable помещает новую запись в "следующий доступный" ведро, если новая ключевая запись сталкивается с другой.

как бы HashTable все еще возвращает правильное значение, если это столкновение происходит при вызове одного назад с ключом столкновения?

Я предполагаю, что Keys are String тип и элемент hashCode() возвращает значение по умолчанию, сгенерированное, например, Java.

если я реализую свою собственную функцию хэширования и использую ее как часть таблицы поиска (т. е. a HashMap или Dictionary), какие существуют стратегии для борьбы с наездом?

Я даже видел заметки, касающиеся простых чисел! Информация не так понятна из поиска Google.

10 79

10 ответов:

хэш-таблицы имеют дело с коллизиями одним из двух способов.

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

Вариант 2: если все записи хэш-таблицы заполнены, то хэш-таблица может увеличить количество ведер, которые она имеет, а затем перераспределить все элементы в таблице. Хэш-функция возвращает целое число и хэш-таблица должны взять результат хэш-функции и изменить его на размер таблицы, чтобы он мог быть уверен, что он попадет в ведро. Поэтому, увеличивая размер, он будет перефразировать и запускать вычисления по модулю, которые, если Вам повезет, могут отправить объекты в разные ведра.

Java использует оба варианта 1 и 2 в реализации хэш-таблицы.

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


существует несколько стратегий для хэш-таблицы для разрешения коллизии.

первый вид большого метода требует, чтобы ключи (или указатели на них) хранились в таблице вместе с соответствующими значениями, которые далее включает в себя:

  • отдельные цепочки

enter image description here

  • открыть решении

enter image description here

  • срослись хеширования
  • хеширование кукушки
  • хеширование Робин Гуда
  • 2-Выбор хеширования
  • классики хеширование

еще один важный метод для обработки столкновения является динамическое изменение размера, который далее имеет несколько способов:

  • изменение размера путем копирования всех записей
  • инкрементное изменение размера
  • монотонный клавиш

EDIT: выше взяты из wiki_hash_table, куда вы должны пойти, чтобы иметь посмотрите, чтобы получить дополнительную информацию.

Я настоятельно рекомендую вам прочитать этот пост в блоге, который появился на HackerNews недавно: Как работает HashMap в Java

короче, ответ

Что произойдет, если два разных Ключевые объекты HashMap имеют то же самое хэш-код?

Они будут храниться в том же ведре, но нет следующего узла связанного списка. И ключи метод Equals () будет использоваться для определите правильную пару значений ключа в HashMap.

есть несколько методов, доступных для обработки столкновения. Я объясню некоторые из них

цепочки: В цепочке мы используем индексы массива для хранения значений. Если хэш-код второго значения также указывает на тот же индекс, то мы заменяем это значение индекса связанным списком, и все значения, указывающие на этот индекс, хранятся в связанном списке, а фактический индекс массива указывает на главу связанного списка. Но если есть только один хэш-код, указывающий на индекс массива, то значение непосредственно хранится в этом индексе. Такая же логика применяется при получении значений. Это используется в Java HashMap / Hashtable, чтобы избежать столкновений.

линейного пробирования: этот метод используется, когда у нас есть больше индекса в таблице, то значения, которые будут сохранены. Линейная техника зондирования работает над концепцией непрерывного увеличения, пока вы не найдете пустой слот. Псевдо-код выглядит так..

index = h (k)

пока( val (индекс) занят)

index = (index+1) mod n

двойная техника хэширования: в этом методе мы используем две функции хеширования h1(k) и h2 (k). Если слот в h1(k) занят, то вторая функция хеширования h2 (k) используется для увеличения индекса. Псевдо-код выглядит так..

index = h1 (k)

пока(val (индекс) занят)

index = (index + h2 (k)) mod n

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

взяты из http://coder2design.com/hashing/

Я слышал в своих классах степени, что a Hashtable будет разместить новую запись в "следующее доступное" ведро, если новое Ключевая запись сталкивается с другой.

это на самом деле не так, по крайней мере для Oracle JDK (it и деталь реализации, которая может варьироваться между различными реализациями API-интерфейс). Вместо этого каждый блок содержит связанный список записей.

тогда как бы хэш-таблица все еще возвращаться правильное значение, если это столкновение происходит при вызове одного назад с ключом столкновения?

использует equals() чтобы найти фактически соответствующую запись.

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

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

обновление с Java 8: Ява 8 использует собственн-сбалансированное дерево для столкновения-обработка, улучшение худшем случае с O(N) к O(зарегистрируйте N) для поиска. Использование собственн-сбалансированное дерево было введено в Java 8, как улучшение сцепления (используется до Java 7), которая использует связанный список, и в худшем случае за o(n) для поиска (а он должен пройти по списку)

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

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

Collission-транспортная обработка приносит в худшем случае производительность вставки и поиска с O(1) в случае отсутствия collission-транспортная обработка до o(n) для цепочки (связанного списка используется как вторичная структура данных) и o(зарегистрируйте N) для собственн-сбалансированное дерево.

ссылки:

Java 8 поставляется со следующими улучшениями / изменениями HashMap объектов в случае столкновения.

  • альтернативная строковая хэш-функция, добавленная в Java 7, была удалена.

  • ведра, содержащие большое количество сталкивающихся ключей, будут хранить свои записи в сбалансированном дереве вместо связанного списка после определенный порог достигнут.

вышеуказанные изменения обеспечивают производительность O (log (n)) в худших сценариях (https://www.nagarro.com/en/blog/post/24/performance-improvement-for-hashmap-in-java-8)

поскольку существует некоторая путаница в отношении того, какой алгоритм использует Хэшмап Java (в реализации Sun / Oracle / OpenJDK), здесь соответствующие фрагменты исходного кода (из OpenJDK, 1.6.0_20, на Ubuntu):

/**
 * Returns the entry associated with the specified key in the
 * HashMap.  Returns null if the HashMap contains no mapping
 * for the key.
 */
final Entry<K,V> getEntry(Object key) {
    int hash = (key == null) ? 0 : hash(key.hashCode());
    for (Entry<K,V> e = table[indexFor(hash, table.length)];
         e != null;
         e = e.next) {
        Object k;
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k))))
            return e;
    }
    return null;
}

этот метод (cite-от строк 355 до 371) вызывается при поиске записи в таблице, например из get(),containsKey() и некоторые другие. Цикл for здесь проходит через связанный список, сформированный объектами ввода.

вот код для входа объекты (строки 691-705 + 759):

static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next;
    final int hash;

    /**
     * Creates new entry.
     */
    Entry(int h, K k, V v, Entry<K,V> n) {
        value = v;
        next = n;
        key = k;
        hash = h;
    }

  // (methods left away, they are straight-forward implementations of Map.Entry)

}

сразу после этого идет addEntry() способ:

/**
 * Adds a new entry with the specified key, value and hash code to
 * the specified bucket.  It is the responsibility of this
 * method to resize the table if appropriate.
 *
 * Subclass overrides this to alter the behavior of put method.
 */
void addEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
    if (size++ >= threshold)
        resize(2 * table.length);
}

это добавляет новую запись на передней части ведра, со ссылкой на старый первый запись (или null, если такой нет). Аналогичным образом removeEntryForKey() метод проходит через список и заботится об удалении только одной записи, позволяя остальной части списка нетронутыми.

Итак, вот связанный список записей для каждого ведра, и я очень сомневаюсь, что это изменилось от _20 до _22, так как это было так с 1.2.

(этот код (c) 1997-2007 Sun Microsystems, и доступен под GPL, но для копирования лучше использовать исходный файл, содержащийся в src.zip в каждом JDK от Sun / Oracle, а также в OpenJDK.)

Он будет использовать метод equals, чтобы увидеть, присутствует ли ключ даже и особенно если в одном ведре есть более одного элемента.

существуют различные методы разрешения коллизий.Некоторые из них-это отдельная цепочка,Открытая адресация,хеширование Робин Гуда,хеширование кукушки и т. д.

Java использует отдельную цепочку для разрешения коллизий в хэш-таблицах.Вот отличная ссылка на то, как это происходит: http://javapapers.com/core-java/java-hashtable/

вот очень простая реализация хэш-таблицы в Java. в только реализует put() и get(), но вы можете легко добавить все, что вам нравится. он полагается на java hashCode() метод, который реализуется всеми объектами. вы можете легко создать свой собственный интерфейс,

interface Hashable {
  int getHash();
}

и заставить его быть реализован с помощью клавиш, Если вам нравится.

public class Hashtable<K, V> {
    private static class Entry<K,V> {
        private final K key;
        private final V val;

        Entry(K key, V val) {
            this.key = key;
            this.val = val;
        }
    }

    private static int BUCKET_COUNT = 13;

    @SuppressWarnings("unchecked")
    private List<Entry>[] buckets = new List[BUCKET_COUNT];

    public Hashtable() {
        for (int i = 0, l = buckets.length; i < l; i++) {
            buckets[i] = new ArrayList<Entry<K,V>>();
        }
    }

    public V get(K key) {
        int b = key.hashCode() % BUCKET_COUNT;
        List<Entry> entries = buckets[b];
        for (Entry e: entries) {
            if (e.key.equals(key)) {
                return e.val;
            }
        }
        return null;
    }

    public void put(K key, V val) {
        int b = key.hashCode() % BUCKET_COUNT;
        List<Entry> entries = buckets[b];
        entries.add(new Entry<K,V>(key, val));
    }
}