web-dev-qa-db-fra.com

Comment HashTables gère-t-il les collisions?

J'ai entendu dans mes cours de degré qu'un HashTable placerait une nouvelle entrée dans le compartiment 'prochain disponible' si la nouvelle entrée de clé entrait en collision avec une autre.

Comment le HashTable retournera-t-il toujours la valeur correcte si cette collision se produit lors de l'appel d'une personne avec la clé de collision?

Je suppose que le type Keys est String et que le hashCode() renvoie le défaut généré par Java.

Si j'implémente ma propre fonction de hachage et que je l'utilise dans le cadre d'une table de recherche (c'est-à-dire un HashMap ou Dictionary), quelles stratégies existe-t-il pour faire face aux collisions?

J'ai même vu des notes relatives aux nombres premiers! Information pas si claire de la recherche Google.

83
Alex

Les tables de hachage traitent des collisions de deux manières.

Option 1: Chaque compartiment contient une liste chaînée d'éléments qui sont hachés pour ce compartiment. C'est pourquoi une mauvaise fonction de hachage peut ralentir les recherches dans les tables de hachage.

Option 2: Si les entrées de la table de hachage sont toutes pleines, la table de hachage peut augmenter le nombre de compartiments dont elle dispose et ensuite redistribuer tous les éléments de la table. La fonction de hachage retourne un entier et la table de hachage doit prendre le résultat de la fonction de hachage et le modifie par rapport à la taille de la table de manière à être sûr qu'il ira dans le compartiment. Donc, en augmentant la taille, il sera réorganisé et exécutera les calculs modulo qui, si vous êtes chanceux, pourraient envoyer les objets à des compartiments différents.

Java utilise les options 1 et 2 dans ses implémentations de table de hachage.

86
ams

Lorsque vous avez parlé de "La table de hachage placera une nouvelle entrée dans le compartiment" prochain disponible "si la nouvelle entrée de clé entre en collision avec une autre.", Vous parlez de la stratégie d'adressage ouvert de la résolution de collision de la table de hachage.


Il existe plusieurs stratégies pour que la table de hachage résolve la collision.

Le premier type de grande méthode nécessite que les clés (ou leurs pointeurs) soient stockées dans la table, ainsi que les valeurs associées, ce qui inclut en outre:

  • Chaînage séparé

enter image description here

  • Adressage ouvert

enter image description here

  • Coalesced hachage
  • Coucou haché
  • hachage de Robin Hood
  • hachage à 2 choix
  • Hopscotch hashing

Une autre méthode importante pour gérer les collisions consiste à le redimensionnement dynamique , qui comporte en outre plusieurs méthodes:

  • Redimensionner en copiant toutes les entrées
  • Redimensionnement incrémental
  • Clés monotones

[~ # ~] éditer [~ # ~] : les précédents sont empruntés à wiki_hash_table , où vous devriez vous rendre un coup d'oeil pour obtenir plus d'informations.

67
herohuyongtao

Il existe plusieurs techniques disponibles pour gérer les collisions. Je vais expliquer certains d'entre eux

Chaînage: Lors du chaînage, nous utilisons des index de tableau pour stocker les valeurs. Si le code de hachage de la deuxième valeur pointe également vers le même index, nous remplaçons cette valeur par une liste chaînée et toutes les valeurs pointant vers cet index sont stockées dans la liste chaînée et l'index réel du tableau pointe vers l'en-tête de la liste chaînée. Mais s'il n'y a qu'un seul code de hachage pointant sur un index de tableau, la valeur est directement stockée dans cet index. La même logique est appliquée lors de la récupération des valeurs. Ceci est utilisé dans Java HashMap/Hashtable pour éviter les collisions.

Sondage linéaire: Cette technique est utilisée lorsque la table contient plus d'index que les valeurs à stocker. La technique de sondage linéaire fonctionne sur le concept de continuer à incrémenter jusqu'à ce que vous trouviez un emplacement vide. Le pseudo-code ressemble à ceci:

index = h(k) 

while( val(index) is occupied) 

index = (index+1) mod n

Technique de double hachage: Dans cette technique, nous utilisons deux fonctions de hachage h1 (k) et h2 (k). Si le créneau en h1 (k) est occupé, la deuxième fonction de hachage h2 (k) est utilisée pour incrémenter l'index. Le pseudo-code ressemble à ceci:

index = h1(k)

while( val(index) is occupied)

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

Les techniques de sondage linéaire et de double hachage font partie de la technique d'adressage ouvert et ne peuvent être utilisées que si le nombre d'emplacements disponibles est supérieur au nombre d'éléments à ajouter. Cela prend moins de mémoire que de chaîner car il n’ya pas de structure supplémentaire utilisée ici, mais sa lenteur à cause de beaucoup de mouvement se produit jusqu’à ce que nous trouvions un emplacement vide. Également dans la technique d'adressage ouvert lorsqu'un élément est supprimé d'un emplacement, nous mettons une désactivation pour indiquer que l'élément est supprimé à partir de là, c'est pourquoi il est vide.

Pour plus d'informations, voir this site .

19
Jatinder Pal

Je vous suggère fortement de lire ce billet de blog paru récemment sur HackerNews: Comment HashMap fonctionne en Java

En bref, la réponse est

Que se passera-t-il si deux objets clés HashMap différents ont le même hashcode?

Ils seront stockés dans le même compartiment mais pas de noeud suivant de la liste chaînée. Et la clé equals () sera utilisée pour identifier la paire clé/valeur correcte dans HashMap.

16
zengr

Dans mes classes de diplômes, j'ai entendu dire qu'une table de hachage placerait une nouvelle entrée dans le compartiment "prochain disponible" si la nouvelle entrée de clé entrait en collision avec une autre.

En réalité, cela n’est pas vrai, du moins pour le JDK Oracle (il est un détail d’implémentation qui peut varier d’une implémentation différente de l’API). Au lieu de cela, chaque compartiment contient une liste chaînée d'entrées antérieures à Java 8 et une arborescence équilibrée dans Java 8 ou supérieur.).

alors comment la table de hachage pourrait-elle toujours renvoyer la valeur correcte si cette collision se produit lors de l'appel d'une personne avec la clé de collision?

Il utilise la equals() pour rechercher l'entrée qui correspond réellement.

Si j'implémente ma propre fonction de hachage et que je l'utilise dans le cadre d'une table de recherche (c'est-à-dire un tableau de hachage ou un dictionnaire), quelles stratégies existe-t-il pour faire face aux collisions?

Il existe différentes stratégies de traitement des collisions avec différents avantages et inconvénients. L'entrée de Wikipedia sur les tables de hachage donne un bon aperçu.

7
Michael Borgwardt

Mise à jour depuis Java 8: Java 8 utilise un arbre auto-équilibré pour la gestion des collisions, améliorant la pire des cas de O(n) à O (log n) pour la recherche. L’utilisation d’un arbre auto-équilibré a été introduite dans Java 8 comme amélioration du chaînage (utilisé jusqu’à Java 7), qui utilise une liste chaînée et présente le cas le plus défavorable. O(n) pour la recherche (car il doit parcourir la liste)

Pour répondre à la deuxième partie de votre question, l’insertion est effectuée en mappant un élément donné sur un index donné dans le tableau sous-jacent de la table de hachage. Toutefois, lorsqu’une collision se produit, tous les éléments doivent encore être conservés (stockés dans une structure de données secondaire). , et pas seulement remplacés dans le tableau sous-jacent). Cela est généralement fait en faisant de chaque composant de tableau (emplacement) une structure de données secondaire (ou compartiment), et l'élément est ajouté au compartiment résidant sur l'index de tableau donné (si la clé n'existe pas déjà dans le compartiment, auquel cas il est remplacé).

Pendant la recherche, la clé est hachée dans son tableau-index correspondant et la recherche d'un élément correspondant à la clé (exacte) du compartiment donné est effectuée. Etant donné que le compartiment n'a pas besoin de gérer les collisions (comparaison directe des clés), cela résout le problème des collisions, mais le fait au prix de l'insertion et de la recherche de la structure de données secondaire. Le point clé est que dans une table de hachage, la clé et la valeur sont stockées. Ainsi, même si le hachage entre en collision, les clés sont comparées directement pour déterminer leur égalité (dans le compartiment) et peuvent donc être identifiées de manière unique dans le compartiment.

Le traitement par collission apporte les pires performances d'insertion et de recherche de O(1) en l'absence de traitement par collission à O(n) pour le chaînage (une liste liée est utilisé comme structure de données secondaire) et O (log n) pour l’arbre auto-équilibré.

Les références:

Java 8 a apporté les améliorations/modifications suivantes des objets HashMap en cas de fortes collisions.

  • La fonction de hachage de chaîne ajoutée dans Java 7 a été supprimée.

  • Les compartiments contenant un grand nombre de clés en collision stockent leurs entrées dans une arborescence équilibrée au lieu d'une liste chaînée lorsqu'un certain seuil est atteint.

Les modifications ci-dessus garantissent les performances de O(log(n)) dans les cas les plus défavorables ( https://www.nagarro.com/fr/blog/post/24/performance-improvement-for -hashmap-in-Java-8 )

5
Daniel Valland

En cas de confusion quant à l’algorithme utilisé par HashMap de Java (dans l’implémentation Sun/Oracle/OpenJDK), voici les extraits de code source pertinents (d’OpenJDK, 1.6.0_20, sur 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;
}

Cette méthode (cite des lignes 355 à 371) est appelée lors de la recherche d’une entrée dans la table, par exemple à partir de get(), containsKey() et quelques autres. La boucle for parcourt ici la liste liée formée par les objets d’entrée.

Voici le code pour les objets d'entrée (lignes 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)

}

Juste après, la méthode 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);
}

Ceci ajoute la nouvelle entrée sur le devant du compartiment, avec un lien vers l’ancienne entrée première (ou null, si elle n’existe pas). De même, la méthode removeEntryForKey() parcourt la liste et ne supprime qu'une entrée, laissant le reste de la liste intact.

Donc, voici une liste d'entrées liées pour chaque compartiment, et je doute fort que cela ait changé de _20 À _22, Car c'était comme ça à partir de 1.2.

(Ce code est (c) 1997-2007 Sun Microsystems, et disponible sous GPL, mais vous pouvez mieux utiliser le fichier original, contenu dans src.Zip dans chaque JDK de Sun/Oracle et également dans OpenJDK.)

3
Paŭlo Ebermann

Il utilisera la méthode equals pour voir si la clé est présente même et surtout s'il y a plus d'un élément dans le même compartiment.

2

Il existe différentes méthodes de résolution des collisions. Certaines sont le chaînage séparé, l'adressage ouvert, le hachage Robin Hood, le hachage coucou, etc.

Java utilise un chaînage séparé pour résoudre les collisions dans les tables de hachage. Voici un excellent lien vers la façon dont cela se passe: http://javapapers.com/core-Java/java-hashtable/

voici une implémentation très simple de la table de hachage en Java. in implémente uniquement put() et get(), mais vous pouvez facilement ajouter ce que vous voulez. il s'appuie sur la méthode hashCode() de Java implémentée par tous les objets. vous pouvez facilement créer votre propre interface,

interface Hashable {
  int getHash();
}

et le forcer à être mis en œuvre par les clés si vous voulez.

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));
    }
}
1
Jeffrey Blattman