В вычислительной технике хеш -таблица , также известная как хэш-карта или хеш-набор , представляет собой структуру данных , реализующую ассоциативный массив , также называемый словарем, который представляет собой абстрактный тип данных , сопоставляющий ключи со значениями . [2] Хэш-таблица использует хэш-функцию для вычисления индекса , также называемого хэш-кодом , в массиве сегментов или слотов , из которого можно найти желаемое значение. Во время поиска ключ хешируется, и полученный хеш указывает, где хранится соответствующее значение.
В идеале хэш-функция присваивает каждому ключу уникальный сегмент, но в большинстве проектов хеш-таблиц используется несовершенная хеш-функция, что может вызвать хеш- коллизии , когда хеш-функция генерирует один и тот же индекс для более чем одного ключа. Такие коллизии обычно каким-то образом учитываются.
В хорошо размерной хеш-таблице средняя временная сложность каждого поиска не зависит от количества элементов, хранящихся в таблице. Многие конструкции хэш-таблиц также допускают произвольные вставки и удаления пар ключ-значение с амортизированной постоянной средней стоимостью за операцию. [3] [4] [5]
Хеширование является примером компромисса между пространством и временем . Если память бесконечна, весь ключ можно использовать непосредственно как индекс для определения его значения за один доступ к памяти. С другой стороны, если доступно бесконечное время, значения можно хранить без учета их ключей, а для извлечения элемента можно использовать двоичный или линейный поиск . [6] : 458
Во многих ситуациях хеш-таблицы оказываются в среднем более эффективными, чем деревья поиска или любая другая структура поиска в таблицах . По этой причине они широко используются во многих видах компьютерного программного обеспечения , особенно для ассоциативных массивов , индексации баз данных , кэшей и наборов .
Идея хеширования возникла независимо в разных местах. В январе 1953 года Ханс Петер Лун написал внутренний меморандум IBM , в котором использовалось хеширование с цепочкой. Первый пример открытой адресации был предложен А.Д. Линем на основе меморандума Луна. [7] : 15 Примерно в то же время Джин Амдал , Элейн М. МакГроу , Натаниэль Рочестер и Артур Сэмюэл из IBM Research реализовали хеширование для ассемблера IBM 701 . [8] : 124 Открытая адресация с линейным зондированием приписывается Амдалу, хотя ту же идею независимо выдвинул Андрей Ершов . [8] : 124–125 Термин «открытая адресация» был придуман У. Уэсли Петерсоном в его статье, в которой обсуждается проблема поиска в больших файлах. [7] : 15
Первая опубликованная работа по хешированию с цепочкой принадлежит Арнольду Дюми , который обсуждал идею использования остатка по модулю простого числа в качестве хеш-функции. [7] : 15 Слово «хеширование» впервые было опубликовано в статье Роберта Морриса. [8] : 126 Теоретический анализ линейного зондирования первоначально был представлен Конхеймом и Вайсом. [7] : 15
Ассоциативный массив хранит набор пар (ключ, значение) и допускает вставку, удаление и поиск (поиск) с ограничением уникальных ключей . В реализации ассоциативных массивов в виде хэш-таблицы массив длины частично заполняется элементами, где . Значение сохраняется в индексном месте , где — хэш-функция, и — . [7] : 2 При разумных предположениях хеш-таблицы имеют лучшие ограничения по времени сложности операций поиска, удаления и вставки по сравнению с самобалансирующими двоичными деревьями поиска . [7] : 1
Хэш-таблицы также часто используются для реализации наборов, опуская сохраненное значение для каждого ключа и просто отслеживая, присутствует ли ключ. [7] : 1
Коэффициент загрузки является критической статистикой хеш-таблицы и определяется следующим образом: [1]
Производительность хеш-таблицы ухудшается в зависимости от коэффициента загрузки . [7] : 2
Для поддержания хорошей производительности программное обеспечение следит за тем, чтобы коэффициент загрузки никогда не превышал некоторой константы . [9]
Поэтому размер хеш-таблицы изменяется или перехешируется всякий раз, когда коэффициент загрузки достигает . [9]
Размер таблицы также изменяется, если коэффициент загрузки падает ниже . [9]
При использовании отдельных цепочек хеш-таблиц каждый слот массива сегментов хранит указатель на список или массив данных. [10]
Производительность отдельных цепочек хеш-таблиц постепенно снижается по мере роста коэффициента загрузки, и нет фиксированной точки, после которой абсолютно необходимо изменение размера. [9]
При раздельной цепочке значение, обеспечивающее наилучшую производительность, обычно составляет от 1 до 3. [9]
При открытой адресации каждый слот массива сегментов содержит ровно один элемент. Поэтому хеш-таблица с открытым адресом не может иметь коэффициент загрузки больше 1. [10]
Производительность открытой адресации становится очень плохой, когда коэффициент загрузки приближается к 1. [9]
Поэтому хеш-таблица, использующая открытую адресацию, должна быть изменена или перехеширована , если коэффициент загрузки приближается к 1. [9]
При открытой адресации допустимые значения максимального коэффициента загрузки должны находиться в диапазоне от 0,6 до 0,75. [11] [12] : 110
Хэш - функция сопоставляет вселенную ключей с индексами массива или слотами в таблице для каждого из и . Обычные реализации хэш-функций основаны на предположении целочисленной вселенной , что все элементы таблицы происходят из вселенной , где длина в битах ограничена размером слова компьютерной архитектуры . [7] : 2
Идеальная хэш-функция определяется как инъективная функция , в которой каждый элемент сопоставляется с уникальным значением в . [13] [14] Идеальную хэш-функцию можно создать, если все ключи известны заранее. [13]
Схемы хеширования, используемые в предположении целочисленной вселенной, включают хеширование делением, хеширование умножением, универсальное хеширование , динамическое идеальное хеширование и статическое идеальное хеширование . [7] : 2 Однако широко используемой схемой является хеширование делением. [15] : 264 [12] : 110
Схема хеширования делением следующая: [7] : 2
Схема хеширования умножением следующая: [7] : 2–3.
Равномерное распределение хеш-значений является фундаментальным требованием хеш-функции. Неравномерное распределение увеличивает количество коллизий и стоимость их разрешения. Единообразие иногда трудно обеспечить при проектировании, но его можно оценить эмпирически с использованием статистических тестов, например критерия хи-квадрат Пирсона для дискретных равномерных распределений. [16] [17]
Распределение должно быть равномерным только для размеров таблиц, встречающихся в приложении. В частности, если используется динамическое изменение размера с точным удвоением и уменьшением размера таблицы вдвое, то хеш-функция должна быть однородной только тогда, когда размер равен степени двойки . Здесь индекс можно вычислить как некоторый диапазон битов хэш-функции. С другой стороны, некоторые алгоритмы хеширования предпочитают, чтобы размер был простым числом . [18]
Для открытых схем адресации хеш-функция также должна избегать кластеризации — сопоставления двух или более ключей с последовательными слотами. Такая кластеризация может привести к резкому увеличению затрат на поиск, даже если коэффициент загрузки низкий и коллизии нечасты. Утверждается, что популярный мультипликативный хеш имеет особенно плохое поведение при кластеризации. [18] [4]
K-независимое хеширование предлагает способ доказать, что определенная хеш-функция не имеет плохих наборов ключей для данного типа хеш-таблицы. Ряд результатов K-независимости известен для схем разрешения коллизий, таких как линейное зондирование и хеширование с кукушкой. Поскольку K-независимость может доказать, что хеш-функция работает, можно сосредоточиться на поиске максимально быстрой такой хеш-функции. [19]
Алгоритм поиска, использующий хеширование, состоит из двух частей. Первая часть — вычисление хэш-функции , которая преобразует ключ поиска в индекс массива . В идеальном случае никакие два ключа поиска не совпадают с одним и тем же индексом массива. Однако это не всегда так, и невозможно гарантировать невидимые данные. [20] : 515 Следовательно, вторая часть алгоритма — разрешение коллизий. Двумя распространенными методами разрешения коллизий являются отдельная цепочка и открытая адресация. [6] : 458
При раздельной цепочке процесс включает в себя создание связанного списка с парой ключ-значение для каждого индекса массива поиска. Конфликтующие элементы объединяются в единый связанный список, по которому можно получить доступ к элементу с помощью уникального ключа поиска. [6] : 464 Разрешение коллизий посредством связывания со связанным списком — распространенный метод реализации хеш-таблиц. Пусть и — хеш-таблица и узел соответственно, операция включает в себя следующее: [15] : 258
Chained-Hash-Insert( T , k ) вставляет x в начало связанного списка T [ h ( k )]Chained-Hash-Search( T , k ) поиск элемента с ключом k в связанном списке T [ h ( k )]Chained-Hash-Delete( T , k ) удалить x из связанного списка T [ h ( k )]
Если элемент сопоставим численно или лексически и вставлен в список с сохранением общего порядка , это приводит к более быстрому прекращению безуспешных поисков. [20] : 520–521.
Если ключи упорядочены , было бы эффективно использовать концепции « самоорганизации », такие как использование самобалансирующегося двоичного дерева поиска , с помощью которого теоретический наихудший случай может быть сведен к , хотя это и вносит дополнительные сложности. [20] : 521
В динамическом идеальном хешировании используются двухуровневые хеш-таблицы, чтобы снизить сложность поиска и гарантировать ее в худшем случае. В этом методе сегменты записей организованы как идеальные хеш-таблицы со слотами, обеспечивающими постоянное время поиска в худшем случае и малое амортизированное время для вставки. [21] Исследование показывает, что раздельное связывание на основе массива на 97% более производительно по сравнению со стандартным методом связанного списка при большой нагрузке. [22] : 99
Такие методы, как использование дерева слияния для каждого сегмента, также приводят к постоянному времени для всех операций с высокой вероятностью. [23]
Связанный список реализации отдельной цепочки может не учитывать кэш из-за пространственной локальности — локальности ссылки — когда узлы связанного списка разбросаны по памяти, таким образом, обход списка во время вставки и поиска может повлечь за собой неэффективность кэша ЦП . [22] : 91
В вариантах разрешения коллизий с учетом кэша посредством отдельной цепочки динамический массив , который оказался более удобным для кэша , используется в том месте, где обычно развертывается связанный список или самобалансирующиеся двоичные деревья поиска, поскольку шаблон непрерывного размещения массива могут быть использованы устройствами предварительной выборки аппаратного кэша , такими как резервный буфер трансляции , что приводит к сокращению времени доступа и потребления памяти. [24] [25] [26]
Открытая адресация — это еще один метод разрешения коллизий, при котором каждая запись записи сохраняется в самом массиве сегментов, а разрешение хэша выполняется посредством зондирования . Когда необходимо вставить новую запись, сегменты проверяются, начиная с хешированного слота и продолжая некоторую тестовую последовательность , пока не будет найден незанятый слот. При поиске записи сегменты сканируются в одной и той же последовательности, пока не будет найдена либо целевая запись, либо не найден неиспользуемый слот массива, что свидетельствует о неудачном поиске. [27]
Хорошо известные последовательности зондов включают:
Производительность открытой адресации может быть медленнее по сравнению с раздельной цепочкой, поскольку последовательность проверки увеличивается, когда коэффициент нагрузки приближается к 1. [9] [22] : 93 Зондирование приводит к бесконечному циклу , если коэффициент загрузки достигает 1, в случае полностью заполненный стол. [6] : 471 Средняя стоимость линейного зондирования зависит от способности хэш-функции равномерно распределять элементы по таблице, чтобы избежать кластеризации , поскольку формирование кластеров приведет к увеличению времени поиска. [6] : 472
Поскольку слоты расположены в последовательных местах, линейное зондирование может привести к лучшему использованию кэша ЦП за счет локальности ссылок, что приведет к снижению задержки памяти . [28]
Объединенное хеширование — это гибрид отдельной цепочки и открытой адресации, при которой сегменты или узлы связаны внутри таблицы. [30] : 6–8 Алгоритм идеально подходит для фиксированного выделения памяти . [30] : 4 Коллизия при объединенном хешировании разрешается путем идентификации пустого слота с наибольшим индексом в хеш-таблице, затем в этот слот вставляется конфликтующее значение. Бакет также связан со слотом вставленного узла, который содержит его конфликтующий хеш-адрес. [30] : 8
Хеширование с кукушкой — это форма метода разрешения коллизий открытой адресации, которая гарантирует сложность поиска в наихудшем случае и постоянное амортизированное время для вставок. Конфликт разрешается путем поддержки двух хеш-таблиц, каждая из которых имеет свою собственную хеш-функцию, при этом конфликтующий слот заменяется заданным элементом, а занятый элемент слота перемещается в другую хеш-таблицу. Процесс продолжается до тех пор, пока каждый ключ не займет свое место в пустых ячейках таблиц; если процедура входит в бесконечный цикл , который определяется посредством поддержания счетчика порогового цикла, обе хеш-таблицы перехэшируются с использованием новых хэш-функций, и процедура продолжается. [31] : 124–125
Хеширование Hopscotch — это алгоритм, основанный на открытой адресации, который сочетает в себе элементы хеширования с кукушкой , линейного зондирования и цепочки посредством понятия окрестности сегментов — последующих сегментов вокруг любого данного занятого сегмента, также называемого «виртуальным» контейнером. [32] : 351–352 Алгоритм предназначен для обеспечения более высокой производительности, когда коэффициент загрузки хеш-таблицы превышает 90%; он также обеспечивает высокую пропускную способность при параллельных настройках , поэтому хорошо подходит для реализации параллельной хэш-таблицы изменяемого размера . [32] : 350 Окрестность, характерная для хеширования в классиках, гарантирует свойство, заключающееся в том, что стоимость поиска желаемого элемента в любой заданной корзине внутри окрестности очень близка к стоимости его поиска в самой корзине; алгоритм пытается стать элементом в своем окружении — с возможными затратами, связанными с вытеснением других элементов. [32] : 352
Каждый сегмент в хеш-таблице включает дополнительную «информацию о переходе» — битовый массив H - бит для указания относительного расстояния до элемента, который изначально был хеширован в текущем виртуальном сегменте в пределах записей H -1. [32] : 352 Пусть и будут ключом, который будет вставлен, и сегментом, в который хешируется ключ соответственно; В процедуре вставки участвует несколько случаев, так что свойство соседства алгоритма гарантировано: [32] : 352–353, если пусто, элемент вставляется, а самый левый бит растрового изображения устанавливается в 1; если он не пуст, для поиска пустого слота в таблице используется линейное зондирование, растровое изображение сегмента обновляется с последующей вставкой; если пустой слот не находится в пределах диапазона окрестности , т.е. H -1, последующая манипуляция массивом битов обмена и информации о переходе для каждого сегмента выполняется в соответствии с его инвариантными свойствами окрестности . [32] : 353
Хеширование Робин Гуда — это алгоритм разрешения коллизий на основе открытой адресации; коллизии разрешаются за счет смещения самого дальнего элемента (или самой длинной пробной последовательности (PSL)) от его «исходного местоположения», т. е. корзины, в которую элемент был хеширован. [33] : 12 Хотя хеширование Робин Гуда не меняет теоретическую стоимость поиска , оно существенно влияет на дисперсию распределения элементов по корзинам, [34] : 2 т.е. имеет дело с формированием кластеров в хеш-таблице. [35] Каждый узел в хеш-таблице, использующий хеширование Робин Гуда, должен быть дополнен для хранения дополнительного значения PSL. [36] Пусть — вставляемый ключ, — (инкрементная) длина PSL , — хеш-таблица, а — индекс, процедура вставки следующая: [33] : 12–13 [37] : 5
Повторные вставки приводят к увеличению количества записей в хеш-таблице, что, следовательно, увеличивает коэффициент загрузки; Чтобы сохранить амортизированную производительность операций поиска и вставки, размер хеш-таблицы динамически изменяется, а элементы таблиц повторно хэшируются в сегменты новой хеш-таблицы, [9] поскольку элементы не могут быть скопированы в результате изменения размеров таблицы. в другом значении хеш-функции из-за операции по модулю . [38] Если хеш-таблица становится «слишком пустой» после удаления некоторых элементов, можно выполнить изменение размера, чтобы избежать чрезмерного использования памяти . [39]
Как правило, новая хеш-таблица, размер которой вдвое превышает размер исходной хеш-таблицы, выделяется в частном порядке, и каждый элемент исходной хеш-таблицы перемещается во вновь выделенную путем вычисления хэш-значений элементов с последующей операцией вставки. Перефразирование — это просто, но вычислительно дорого. [40] : 478–479.
Некоторые реализации хэш-таблиц, особенно в системах реального времени , не могут заплатить цену за увеличение хэш-таблицы сразу, поскольку это может прервать критичные по времени операции. Если невозможно избежать динамического изменения размера, решение состоит в том, чтобы выполнять изменение размера постепенно, чтобы избежать провалов в памяти (обычно на 50 % от размера новой таблицы) во время перехеширования и во избежание фрагментации памяти , которая вызывает уплотнение кучи из-за освобождения больших блоков памяти, вызванных старая хэш-таблица. [41] : 2–3 . В таком случае операция перехеширования выполняется постепенно путем расширения предыдущего блока памяти, выделенного для старой хеш-таблицы, так что сегменты хеш-таблицы остаются неизменными. Общий подход к амортизированному перефразированию предполагает сохранение двух хэш-функций и . Процесс перехэширования элементов корзины в соответствии с новой хэш-функцией называется очисткой , которая реализуется с помощью шаблона команды путем инкапсуляции таких операций , как следует: [41] : 3
Линейное хеширование — это реализация хеш-таблицы, которая обеспечивает динамический рост или сжатие таблицы по одному блоку за раз. [42]
Производительность хеш-таблицы зависит от способности хеш-функции генерировать квазислучайные числа ( ) для записей в хеш-таблице, где , и обозначает ключ, количество сегментов и хеш-функцию, такую что . Если хеш-функция генерирует одно и то же для разных ключей ( ), это приводит к коллизии , которая решается различными способами. Постоянная временная сложность ( ) операции в хеш-таблице предполагается при условии, что хеш-функция не генерирует конфликтующие индексы; таким образом, производительность хеш-таблицы прямо пропорциональна способности выбранной хеш-функции распределять индексы . [43] : 1 Однако построение такой хэш-функции практически неосуществимо , поэтому реализации зависят от методов разрешения коллизий в конкретных случаях для достижения более высокой производительности. [43] : 2
Хэш-таблицы обычно используются для реализации многих типов таблиц в памяти. Они используются для реализации ассоциативных массивов . [29]
Хэш-таблицы также могут использоваться в качестве дисковых структур данных и индексов базы данных (например, в dbm ), хотя в этих приложениях более популярны B-деревья . [44]
Хэш-таблицы могут использоваться для реализации кэшей , вспомогательных таблиц данных, которые используются для ускорения доступа к данным, которые в основном хранятся на более медленных носителях. В этом приложении коллизии хэшей можно обрабатывать, отбрасывая одну из двух конфликтующих записей — обычно удаляя старый элемент, который в данный момент хранится в таблице, и перезаписывая его новым элементом, поэтому каждый элемент в таблице имеет уникальное значение хеш-функции. [45] [46]
Хэш-таблицы могут использоваться при реализации заданной структуры данных , которая может хранить уникальные значения без какого-либо определенного порядка; set обычно используется при проверке членства значения в коллекции, а не при поиске элементов. [47]
Таблица преобразования в сложную хэш-таблицу, в которой хранится информация о каждом разделе, по которому выполнялся поиск. [48]
Многие языки программирования предоставляют функциональность хеш-таблиц либо в виде встроенных ассоциативных массивов, либо в виде стандартных библиотечных модулей.
В JavaScript «объект» — это изменяемая коллекция пар ключ-значение (называемых «свойствами»), где каждый ключ является либо строкой, либо гарантированно уникальным «символом»; любое другое значение, используемое в качестве ключа, сначала преобразуется в строку. За исключением семи «примитивных» типов данных, каждое значение в JavaScript является объектом. [49] В ECMAScript 2015 также добавлена Map
структура данных, которая принимает произвольные значения в качестве ключей. [50]
C++11 включает unordered_map
в свою стандартную библиотеку для хранения ключей и значений произвольных типов . [51]
Встроенная функция Gomap
реализует хэш-таблицу в виде типа . [52]
Язык программирования Java включает коллекции HashSet
, HashMap
, LinkedHashSet
и LinkedHashMap
generic . [53]
Встроенная функция Pythondict
реализует хэш-таблицу в виде типа . [54]
Встроенная система RubyHash
использует модель открытой адресации, начиная с Ruby 2.4. [55]
Язык программирования RustHashMap
включает в себя HashSet
как часть стандартной библиотеки Rust. [56]
Стандартная библиотека .NETHashSet
включает в себя и Dictionary
, [57] [58], поэтому ее можно использовать из таких языков, как C# и VB.NET . [59]
{{cite web}}
: CS1 maint: bot: исходный статус URL неизвестен ( ссылка )