最酸爽的哈希(太难了!!!)

最酸爽的哈希

  • 概念
    • 什么是哈希表
  • 冲突
    •      1. 冲突的概念
    •      2. 冲突的避免
    •      3. 哈希函数设计
    •      4. 冲突的解决
      • 开散列(哈希桶--重点)
  • 总结

概念

什么是哈希表

  可以不经过任何比较,一次直接从表中得到要搜索的元素。如果构造一种存储结构,通过哈希函数使元素的存储位置与它的关键码之间能够建立一一映射的关系,查找的时候就会很快找到该元素。

  • 哈希函数设置:hash(key) = key % array.length(存储元素底层空间总的大小)

冲突

     1. 冲突的概念

	不同关键字通过相同哈希数计算出相同的哈希地址,这种想象称为哈希冲突或者哈希碰撞。

     2. 冲突的避免

	由于哈希表底层数组的容量往往是小于实际要存储的关键字的数量的,
这就导致一个问题,冲突发生是必然的,我们只能尽量降低冲突率。

     3. 哈希函数设计

	哈希函数设计原则:
	  1. 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,它的值域必须在0到m-1之间
	  2. 哈希函数计算出来的地址能均匀分布在整个空间中
	  3. 哈希函数应该比较简单
  1. 直接定制法
  2. 除留余数法
  3. 负载因子调节

     4. 冲突的解决

	解决哈希冲突两种常见的方法:闭散列和开散列

开散列(哈希桶–重点)

	开散列,首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,
每一个子集合称为一个桶,各个桶中的元素通过一个单链表连接起来,各链表的头节点存储在哈希表中(把大集合中的搜索问题转化为在小集合中搜索)

冲突严重是解决办法:

  1. 每个桶的背后是另一个哈希表
  2. 每个桶的背后是一颗搜索树
  3. 普通代码实现如下:

public class HashBucket {
     

    static class Node {
     
        public int key;
        public int value;
        public Node next;

        public Node (int key, int value) {
     
            this.key = key;
            this.value = value;
        }
    }

    public Node[] array;
    public int usedSize;

    public HashBucket() {
     
        this.array = new Node[8];
    }

    public void put(int key, int value) {
     

        int index = key % array.length;

         Node cur = array[index];

         while (cur != null) {
     
             if (cur.key == key) {
     
                 cur.value = value;
             }
             cur = cur.next;
         }

         Node node = new Node(key, value);
         node.next = array[index];
         array[index] = node;
         this.usedSize++;

         if (loadFactor() >= 0.75) {
     
             // 扩容
             resize();
         }
    }

    public int get(int key) {
     

        int index = key % this.array.length;

        Node cur = array[index];

        while (cur != null) {
     
            
            if (cur.key == key) {
     
                return cur.value;
            }
            cur = cur.next;
            
        }
        
        return -1;
    }

    // 扩容函数
    public void resize() {
     

        Node[] newArray = new Node[this.array.length * 2];
        for (int i = 0; i < this.array.length; i++) {
     

            Node cur = this.array[i];

            while (cur != null) {
     

                 int index = cur.key % newArray.length;

                 Node curNext = cur.next;
                 cur.next = newArray[index];
                 newArray[index] = cur;
                 cur = curNext;

            }
        }
        this.array = newArray;
    }

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

}
  1. 泛型代码实现如下:
class Person {
     
    public String id;

    public Person(String id) {
     
        this.id = id;
    }

    @Override
    public boolean equals(Object o) {
     
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(id, person.id);
    }

    @Override
    public int hashCode() {
     
        return Objects.hash(id);
    }
}

public class HashBucket2<K, V> {
     

    static class Node<K, V> {
     
        public K key;
        public V val;
        public Node<K, V> next;

        public Node(K key, V val) {
     
            this.key = key;
            this.val = val;
        }
    }

    public Node[] array;
    public int usedSize;

    public HashBucket2() {
     
        this.array = (Node<K, V>[]) new Node[8];
    }

    public void put(K key, V val) {
     
        //如何找到key 的正确的位置 下标
        //int index = key % array.length;
        int hash = key.hashCode();
        int index = hash % array.length;

        Node<K, V> cur = array[index];
        while (cur != null) {
     
            if (cur.key.equals(key)) {
     
                cur.val = val;
                return;
            }
            cur = cur.next;
        }

        Node<K, V> node = new Node<>(key, val);
        node.next = array[index];
        array[index] = node;
        this.usedSize++;

        if (loadFactor() > 7.5) {
     
            // 扩容
            resize();
        }

    }

    public V get(K key) {
     

        int hash = key.hashCode();
        int index = hash % array.length;

        Node<K, V> cur = array[index];

        while (cur != null) {
     
            if (cur.key.equals(key)) {
     
                return cur.val;
            }
            cur = cur.next;
        }
        return null;
    }

    // 扩容
    public void resize() {
     

        Node[] newArray = new Node[this.array.length * 2];

        for (int i = 0; i < this.array.length; i++) {
     

            Node cur = this.array[i];

            while (cur != null) {
     

                int hash = cur.key.hashCode();
                int index = hash % newArray.length;

                Node curNext = cur.next;
                cur.next = array[index];
                array[index] = cur;
                cur = curNext;

            }
        }
        this.array = newArray;
    }

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

总结

  • 在实际使用过程中,哈希表的冲突率不高,冲突个数是可以控制的,也就是每个桶中的链表的长度是一个常数,通常情况下,哈希表的插入、删除、查找时间复杂度O(1)
  • HashMap 和 HashSet ,Java中利用哈希表实现的Map 和 Set
  • java中使用哈希桶的方式解决冲突
  • Java会在冲突链表长度大于一定阈值后,将链表转变为搜索树(红黑树)
  • Java中计算哈希值实际上是调用的类的hashcode方法,进行key的相等性比较是调用key的equals方法。所以如果用自定义类作为HashMap的key或者HashSet的值,必须覆写hashCode和equals方法,而且要做到equals相等的对象,hashCode一定是一致的

你可能感兴趣的:(数据结构,java)