Apprenez à connaître la table de hachage et implémentez vous-même la fonction de table de hachage

Table de hachage

  • La table de hachage interne est un tableau;
  • key (valeur clé) et hashFunc (fonction de hachage) établissent une relation (généralement modulo (%), ou une relation de fonction), et obtiennent un indice;
  • Fonction de hachage: hash (key) = key% capacity; (la capacité est la taille totale de l'espace sous-jacent de l'élément de stockage)

1. Concept

  • Vous pouvez obtenir l'élément recherché directement à partir du tableau à la fois sans aucune comparaison. Si une structure de stockage est construite, une relation de mappage un à un peut être établie entre l'emplacement de stockage d'un élément et son code clé via une certaine fonction (hashFunc), puis l'élément peut être rapidement trouvé via la fonction lors de la recherche.
  1. Insérer élément: En fonction du code clé de l'élément à insérer, l'emplacement de stockage de l'élément est calculé par cette fonction et stocké en fonction de cet emplacement.
  2. Rechercher un élément: effectuez le même calcul sur le code clé de l'élément, prenez la valeur de fonction obtenue comme emplacement de stockage de l'élément et comparez l'élément en fonction de cette position dans la structure. Si le
    code clé est égal, la recherche est réussi

2. Collision de hachage

  • Conflit de hachage: différents codes de clé peuvent obtenir le même indice via la même fonction de hachage;

3. Évitement des conflits

  • Tout d'abord, nous devons être clairs, car la capacité du tableau sous-jacent de notre table de hachage est souvent inférieure au nombre réel de mots-clés à stocker, cela pose un problème. L'apparition de conflits est inévitable, mais ce que nous peut faire est de faire de notre mieux Pour réduire le taux de conflit.

4. Conception de la fonction d’évitement des conflits

  • Fonctions de hachage courantes
  1. Méthode de personnalisation directe (couramment utilisée): prenez une certaine fonction linéaire du mot-clé comme adresse de hachage: Hash (Key) = A * Key + B Avantages: simple et uniforme Inconvénients: besoin de connaître la distribution des mots-clés à l'avance Scénarios d'utilisation : approprié Rechercher des boîtiers petits et continus.
  2. Méthode diviser et laisser le reste - (couramment utilisée): définissez le nombre d'adresses autorisées dans la table de hachage sur m, et prenez un nombre premier p non supérieur à m mais le plus proche ou égal à m comme diviseur, selon la fonction de hachage : Hash (key) = key% p (p <= m), convertit le code clé en adresse de hachage.
  3. Méthode au carré - (OK)
  4. Méthode de pliage - (comprendre)
  5. Méthode des nombres aléatoires - (comprendre)

5. Facteur de charge d’évitement des conflits

  • Facteur de charge = le nombre actuel d'éléments dans la table de hachage / la taille du tableau sous-jacent
  • Le taux de conflit est proportionnel au facteur de charge, de sorte que la réduction du facteur de charge peut réduire le taux de conflit, qui peut être réduit en développant la matrice;
    Insérez la description de l'image ici

6. Résolution des conflits - deux méthodes

6.1 Hachage fermé

  • Concept : également appelée méthode d'adressage ouverte. Lorsqu'un conflit de hachage survient, si la table de hachage n'est pas pleine, cela signifie qu'il doit y avoir une position vide dans la table de hachage. Ensuite, la clé peut être stockée dans le "suivant" du conflit position. Allez dans une position vide.
  • Façons de trouver des postes vides:
  1. Détection linéaire : à partir de la position conflictuelle, détection en arrière en séquence, jusqu'à ce que la prochaine position vide soit trouvée.
  • Comme le montre la figure ci-dessous, lorsque 44 est placé, le code clé de 44 est également 4, mais il y a déjà des éléments en position 4, alors effectuez une détection linéaire, puis regardez la position 5, il y a aussi des éléments, donc la détection linéaire est effectuée à chaque fois jusqu'à 8 Si la position numérique est vide, mettez 44 dans.
    Insérez la description de l'image ici
    Détection secondaire : Le défaut de la détection linéaire est l'accumulation de données contradictoires, qui est liée à la recherche du prochain emplacement vide, car la façon de trouver l'emplacement vide est de trouver l'emplacement vide un par un, donc la deuxième détection est d'éviter ce problème, la méthode pour trouver la prochaine position vide est: Hi = (H0 + i ^ 2)% m , ou: Hi = (H0 -i ^ 2)% m . Parmi eux: i = 1,2,3 ..., est la position obtenue en calculant la clé de code clé de l'élément via la fonction de hachage Hash (x), et m est la taille de la table.

  • Comme le montre la figure ci-dessous, lorsque 44 est placé, le code clé de 44 est également 4, mais il y a déjà des éléments en position 4, alors effectuez une deuxième détection, 4 + 1 2 = 5, mais il y a des éléments en position 5 , continuez avec 4+ 2 2 = 8, j est mis; mais comme l'espace du tableau est limité, il peut dépasser la plage du tableau lors de la détection, donc chaque fois que la capacité est de 5%, il est toujours dans la plage du tableau, la même chose est vraie pour 8;
    Insérez la description de l'image ici

6.2 Ouvrir le seau de hachage / hachage (clé maître)

  • Concept : la méthode de hachage ouvert est également appelée méthode d'adresse de chaîne (méthode de chaîne ouverte). Tout d'abord, utilisez une fonction de hachage pour calculer l'adresse de hachage du jeu de codes de clé. Les codes de clé avec la même adresse appartiennent au même sous-ensemble, et chaque sous-ensemble est appelé un compartiment, les éléments de chaque compartiment sont liés par une seule liste liée, et le nœud principal de chaque liste liée est stocké dans la table de hachage.
    Insérez la description de l'image ici
  • Comme on peut le voir sur la figure ci-dessus, chaque compartiment dans le hachage ouvert est un élément qui a un conflit de hachage ; chaque nœud de conflit qui entre est connecté par interpolation de queue;
  • Lorsque la longueur de la liste chaînée dépasse 8, elle deviendra un arbre rouge-noir;
  1. JDK1.7: Utilisez tableau + liste liée (méthode d'insertion de tête);
  2. JDK1.8: utiliser tableau + liste liée (méthode d'insertion de queue) + arbre rouge-noir;
  • Expansion de hachage: chaque élément doit être parcouru et re-haché;

7. Réalisez

7.1 Élément d'insertion de table de hachage (seau de hachage)

//放入哈希表
    public void put(int key,int value){
    
    
        int index = key % array.length;//拿到位置
        //遍历这个位置下的链表
        for (Node cur = array[index];cur != null;cur = cur.next){
    
    
            if (cur.data == key){
    
    //判断一下之前有没有插入进去过value这个值,如果之前已经有了直接替换掉
                cur.value = value;
                return;
            }
        }
        //到了这里说明没有和当前key值一样的元素,进行头插法
        //头插法:首先先跟后面的联系起来;
        Node node = new Node(key,value);//待插入节点
        node.next = array[index];//数组里面放的就是第一个节点的地址
        array[index] = node;//然后把新插入的地址放到数组里面
        usedSize++;//插入进去之后,链表的数量加一
        //判断是否需要扩容,当负载因子大于0.75,就需要扩容
        if (loadFactor() > 0.75){
    
    
            //需要扩容
            resize();//调用扩容函数
        }
    }

7.2 Calculer le facteur de charge

//计算负载因子
    public float loadFactor(){
    
    
        return usedSize*1.0f / array.length;
    }

7.3 Extension de la table de hachage

//扩容(扩容的时候必须重新哈希)
    public void resize(){
    
    
        Node[] newArray = new Node[2*array.length];//扩为原来的二倍
        for (int i = 0;i < array.length;i++){
    
    //遍历,进行重新哈希
            Node curNext = null;//用来标记下一个,防止重新哈希cur的时候,后面的节点丢失;
            for (Node cur = array[i];cur != null;cur = curNext){
    
    //对每一个数组下标下的链表都要进行遍历;
                curNext = cur.next;
                //array[i]下标是一个链表
                //cur是头节点
                int index = cur.data % newArray.length;//计算新的位置
                cur.next = newArray[index];//然后进行头插
                newArray[index] = cur;
            }
        }
        this.array = newArray;//最后赋值得到新的扩容之后的哈希;
    }

7.4 Obtenir la valeur en fonction de la clé

//根据键去获取值value
    public int getvalue(int key){
    
    
        int index = key % array.length;//拿到位置
        for(Node cur = array[index];cur != null;cur = cur.next){
    
    
            if (cur.data == key){
    
    
                return cur.value;
            }
        }
        return -1;
    }

8. Code source complet et résultats

//哈希表
//哈希桶
class HashBuck{
    
    

    static class Node{
    
    
        int data;//map.put(key,value),data就是键key
        int value;
        Node next;
        public Node(int data,int value){
    
    
            this.data = data;
            this.value = value;
        }
    }
    Node[] array;//数组里面放的是Node节点
    int usedSize;
    public HashBuck(){
    
    
        this.array =new Node[10];
        usedSize = 0;
    }
    //放入哈希表
    public void put(int key,int value){
    
    
        int index = key % array.length;//拿到位置
        //遍历这个位置下的链表
        for (Node cur = array[index];cur != null;cur = cur.next){
    
    
            if (cur.data == key){
    
    //判断一下之前有没有插入进去过value这个值,如果之前已经有了直接替换掉
                cur.value = value;
                return;
            }
        }
        //到了这里说明没有和当前key值一样的元素,进行头插法
        //头插法:首先先跟后面的联系起来;
        Node node = new Node(key,value);//待插入节点
        node.next = array[index];//数组里面放的就是第一个节点的地址
        array[index] = node;//然后把新插入的地址放到数组里面
        usedSize++;//插入进去之后,链表的数量加一
        //判断是否需要扩容,当负载因子大于0.75,就需要扩容
        if (loadFactor() > 0.75){
    
    
            //需要扩容
            resize();//调用扩容函数
        }
    }
    //计算负载因子
    public float loadFactor(){
    
    
        return usedSize*1.0f / array.length;
    }
    //扩容(扩容的时候必须重新哈希)
    public void resize(){
    
    
        Node[] newArray = new Node[2*array.length];//扩为原来的二倍
        for (int i = 0;i < array.length;i++){
    
    //遍历,进行重新哈希
            Node curNext = null;//用来标记下一个,防止重新哈希cur的时候,后面的节点丢失;
            for (Node cur = array[i];cur != null;cur = curNext){
    
    //对每一个数组下标下的链表都要进行遍历;
                curNext = cur.next;
                //array[i]下标是一个链表
                //cur是头节点
                int index = cur.data % newArray.length;//计算新的位置
                cur.next = newArray[index];//然后进行头插
                newArray[index] = cur;
            }
        }
        this.array = newArray;//最后赋值得到新的扩容之后的哈希;
    }
    //根据键去获取值value
    public int getvalue(int key){
    
    
        int index = key % array.length;//拿到位置
        for(Node cur = array[index];cur != null;cur = cur.next){
    
    
            if (cur.data == key){
    
    
                return cur.value;
            }
        }
        return -1;
    }
}


public class TestDemo {
    
    
    public static void main(String[] args) {
    
    
        HashBuck hashBuck = new HashBuck();
        hashBuck.put(1,1);
        hashBuck.put(2,2);
        hashBuck.put(3,3);
        hashBuck.put(4,4);
        hashBuck.put(5,5);
        hashBuck.put(6,6);
        hashBuck.put(7,7);
        System.out.println("abcd");
    }
}

  • Remarque : Le résultat peut être débogué et vérifié par vous-même (en cassant le point, dans la phrase de sortie), vous pouvez voir la valeur insérée;
    Insérez la description de l'image ici

Je suppose que tu aimes

Origine blog.csdn.net/qq_45665172/article/details/109813531
conseillé
Classement