Как хэш-таблицы справляются с коллизиями?
Я слышал в моих классах степени, что a HashTable
помещает новую запись в "следующий доступный" ведро, если новая ключевая запись сталкивается с другой.
как бы HashTable
все еще возвращает правильное значение, если это столкновение происходит при вызове одного назад с ключом столкновения?
Я предполагаю, что Keys
are String
тип и элемент hashCode()
возвращает значение по умолчанию, сгенерированное, например, Java.
если я реализую свою собственную функцию хэширования и использую ее как часть таблицы поиска (т. е. a HashMap
или Dictionary
), какие существуют стратегии для борьбы с наездом?
Я даже видел заметки, касающиеся простых чисел! Информация не так понятна из поиска Google.
10 ответов:
хэш-таблицы имеют дело с коллизиями одним из двух способов.
Вариант 1: при наличии каждого ведра содержат связанный список элементов, которые хэшируются в этом ведре. Вот почему плохая хэш-функция может сделать поиск в хэш-таблицах очень медленным.
Вариант 2: если все записи хэш-таблицы заполнены, то хэш-таблица может увеличить количество ведер, которые она имеет, а затем перераспределить все элементы в таблице. Хэш-функция возвращает целое число и хэш-таблица должны взять результат хэш-функции и изменить его на размер таблицы, чтобы он мог быть уверен, что он попадет в ведро. Поэтому, увеличивая размер, он будет перефразировать и запускать вычисления по модулю, которые, если Вам повезет, могут отправить объекты в разные ведра.
Java использует оба варианта 1 и 2 в реализации хэш-таблицы.
когда вы говорили о "хэш-таблице", новая запись будет помещена в "следующее доступное" ведро, если новая ключевая запись столкнется с другой.- ты говоришь о Открытая стратегия адресации разрешения коллизий хэш-таблицы.
существует несколько стратегий для хэш-таблицы для разрешения коллизии.
первый вид большого метода требует, чтобы ключи (или указатели на них) хранились в таблице вместе с соответствующими значениями, которые далее включает в себя:
- отдельные цепочки
- открыть решении
- срослись хеширования
- хеширование кукушки
- хеширование Робин Гуда
- 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()
, но вы можете легко добавить все, что вам нравится. он полагается на javahashCode()
метод, который реализуется всеми объектами. вы можете легко создать свой собственный интерфейс,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)); } }