Рубрики

Реализация нашей собственной хеш-таблицы с раздельным построением цепочек в Java

Каждая структура данных имеет свои особые характеристики, например, BST используется, когда требуется быстрый поиск элемента (в log (n)). Куча или приоритетная очередь используется, когда минимальный или максимальный элемент должен быть выбран в постоянное время. Точно так же хеш-таблица используется для извлечения, добавления и удаления элемента за постоянное время. Перед переходом к аспекту реализации необходимо, чтобы кто-нибудь ознакомился с работой хэш-таблицы. Итак, вот краткая справочная информация о работе хеш-таблицы, а также следует отметить, что мы будем использовать терминологию Hash Map и Hash Table взаимозаменяемо, хотя в Java HashTables потокобезопасны, а HashMaps — нет.

Код, который мы собираемся реализовать, доступен по ссылкам 1 и 2

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

Фон

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

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

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

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

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

Эта картина перестает быть такой радужной и идеальной, когда вводится понятие коллизии хэшей. Представьте, что для разных значений ключа теперь выделен один и тот же блок хеш-таблицы, куда идут ранее сохраненные значения, соответствующие некоторому другому предыдущему ключу. Мы, конечно, не можем заменить его. Это будет иметь катастрофические последствия! Чтобы решить эту проблему, мы будем использовать метод раздельной цепочки. Обратите внимание, что есть и другие методы открытой адресации, такие как двойное хеширование и линейное зондирование, эффективность которых почти такая же, как и при раздельной цепочке, и вы можете узнать больше о них в Link 1 Link 2 Link3

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

Теперь может возникнуть сценарий, когда все ключи сопоставляются одному и тому же сегменту, и у нас есть связанный список размером n (размер хеш-таблицы) из одного отдельного сегмента, при этом все остальные сегменты пусты, и это наихудший случай, когда Хеш-таблица действует как связанный список, и поиск — это O (n). Так что же нам делать?

Коэффициент нагрузки

Если n будет общим количеством корзин, которые мы решили заполнить, скажем, 10, и скажем, 7 из них уже заполнены, то есть коэффициент загрузки равен 7/10 = 0,7.

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

Реализация

Тип данных хеш-узла

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

Функции, которые мы планируем сохранить в нашей хэш-карте:

  • получаем (ключ К): возвращает значение , соответствующее ключу , если ключ присутствует в HT аст Т в состоянии)
  • getSize () : возвращает размер HT
  • add () : добавляет новый действительный ключ, пару значений в HT, если уже присутствует, обновляет значение
  • remove () : удаляет ключ, пару значений
  • isEmpty () : возвращает true, если размер равен нулю

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

ArrayList<HashNode<K, V>> bucket = new ArrayList<>();

Вспомогательная функция реализована для получения индекса ключа, чтобы избежать избыточности в других функциях, таких как get, add и remove. Эта функция использует встроенную функцию Java для генерации хеш-кода, и мы сжимаем хеш-код размером HT, чтобы индекс находился в пределах диапазона размера HT

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

  • Получить ключ ввода, чтобы найти индекс в HT
  • Пройдите по списку избранного, соответствующему HT, если вы найдете значение, а затем вернете его, если вы полностью обойдете список, не возвращая его, что означает, что значение отсутствует в таблице и не может быть получено, поэтому верните null

удалять()

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

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

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

Java в своей собственной реализации Hash Table использует Binary Search Tree, если связанный список, соответствующий определенному сегменту, имеет тенденцию становиться слишком длинным.

// Java-программа для демонстрации реализации нашего
// собственная хеш-таблица с цепочкой для обнаружения столкновений

import java.util.ArrayList;

  
// узел цепей

class HashNode<K, V>

{

    K key;

    V value;

  

    // Ссылка на следующий узел

    HashNode<K, V> next;

  

    // Конструктор

    public HashNode(K key, V value)

    {

        this.key = key;

        this.value = value;

    }

}

  
// Класс для представления всей хеш-таблицы

class Map<K, V>

{

    // bucketArray используется для хранения массива цепочек

    private ArrayList<HashNode<K, V>> bucketArray;

  

    // Текущая емкость списка массивов

    private int numBuckets;

  

    // Текущий размер списка массивов

    private int size;

  

    // Конструктор (Инициализирует емкость, размер и

    // пустые цепочки.

    public Map()

    {

        bucketArray = new ArrayList<>();

        numBuckets = 10;

        size = 0;

  

        // Создать пустые цепочки

        for (int i = 0; i < numBuckets; i++)

            bucketArray.add(null);

    }

  

    public int size() { return size; }

    public boolean isEmpty() { return size() == 0; }

  

    // Реализует хеш-функцию для поиска индекса

    // для ключа

    private int getBucketIndex(K key)

    {

        int hashCode = key.hashCode();

        int index = hashCode % numBuckets;

        return index;

    }

  

    // Метод для удаления данного ключа

    public V remove(K key)

    {

        // Применяем хеш-функцию для поиска индекса для данного ключа

        int bucketIndex = getBucketIndex(key);

  

        // Получить голову цепи

        HashNode<K, V> head = bucketArray.get(bucketIndex);

  

        // Поиск ключа в его цепочке

        HashNode<K, V> prev = null;

        while (head != null)

        {

            // если ключ найден

            if (head.key.equals(key))

                break;

  

            // Остальное продолжаем двигаться по цепочке

            prev = head;

            head = head.next;

        }

  

        // Если ключа там не было

        if (head == null)

            return null;

  

        // Уменьшаем размер

        size--;

  

        // Удалить ключ

        if (prev != null)

            prev.next = head.next;

        else

            bucketArray.set(bucketIndex, head.next);

  

        return head.value;

    }

  

    // Возвращает значение для ключа

    public V get(K key)

    {

        // Найти начало цепочки для данного ключа

        int bucketIndex = getBucketIndex(key);

        HashNode<K, V> head = bucketArray.get(bucketIndex);

  

        // Поиск ключа в цепочке

        while (head != null)

        {

            if (head.key.equals(key))

                return head.value;

            head = head.next;

        }

  

        // Если ключ не найден

        return null;

    }

  

    // Добавляет пару ключ-значение в хеш

    public void add(K key, V value)

    {

        // Найти начало цепочки для данного ключа

        int bucketIndex = getBucketIndex(key);

        HashNode<K, V> head = bucketArray.get(bucketIndex);

  

        // Проверяем, присутствует ли ключ

        while (head != null)

        {

            if (head.key.equals(key))

            {

                head.value = value;

                return;

            }

            head = head.next;

        }

  

        // Вставляем ключ в цепочку

        size++;

        head = bucketArray.get(bucketIndex);

        HashNode<K, V> newNode = new HashNode<K, V>(key, value);

        newNode.next = head;

        bucketArray.set(bucketIndex, newNode);

  

        // Если коэффициент загрузки превышает пороговое значение, то

        // размер двойного хеш-таблицы

        if ((1.0*size)/numBuckets >= 0.7)

        {

            ArrayList<HashNode<K, V>> temp = bucketArray;

            bucketArray = new ArrayList<>();

            numBuckets = 2 * numBuckets;

            size = 0;

            for (int i = 0; i < numBuckets; i++)

                bucketArray.add(null);

  

            for (HashNode<K, V> headNode : temp)

            {

                while (headNode != null)

                {

                    add(headNode.key, headNode.value);

                    headNode = headNode.next;

                }

            }

        }

    }

  

    // Метод драйвера для тестирования класса Map

    public static void main(String[] args)

    {

        Map<String, Integer>map = new Map<>();

        map.add("this",1 );

        map.add("coder",2 );

        map.add("this",4 );

        map.add("hi",5 );

        System.out.println(map.size());

        System.out.println(map.remove("this"));

        System.out.println(map.remove("this"));

        System.out.println(map.size());

        System.out.println(map.isEmpty());

    }

}

Выход :

3
4
null
2
false

Весь код доступен по адресу https://github.com/ishaan007/Data-Structures/blob/master/HashMaps/Map.java.

Эта статья предоставлена Ишааном Арора. Если вам нравится GeeksforGeeks и вы хотите внести свой вклад, вы также можете написать статью и отправить ее по почте на contrib@geeksforgeeks.org. Смотрите свою статью, появляющуюся на главной странице GeeksforGeeks, и помогите другим вундеркиндам.

Пожалуйста, напишите комментарии, если вы обнаружите что-то неправильное, или вы хотите поделиться дополнительной информацией по обсуждаемой теме

Рекомендуемые посты:

Реализация нашей собственной хеш-таблицы с раздельным построением цепочек в Java

0.00 (0%) 0 votes