飞道的博客

真的太重要了,面试出现的概率达到了 99%!!!对于哈希表的知识(建议收藏)

259人阅读  评论(0)

1、哈希表的引入:

前言:

顺序结构 以及 平衡树 中,元素关键码与其存储位置之间没有对应的关系,因此在 查找一个元素时,必须要经过关键码的多次比较 。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O( log2 N),搜索的效率 取决于搜索过程中元素的 比较次数

理想的搜索方法可以不经过任何比较,一次直接从表中得到要搜索的元素。 如果构造一种存储结构,通过某种函数(hashFunc)使元素的 存储位置 与它的 关键码 之间能够建立一 一映射的关系,那么在查找时通过该函数可以很快找到该元素。

当向该结构中:

  • 插入元素
    根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放
  • 搜索元素
    对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功

该方式即为 哈希(散列)方法 ,哈希方法中使用的 转换函数 称为 哈希(散列)函数构造出来的结构 称为 哈希表(HashTable)

  • 哈希表 : 数组(存储元素) + 哈希函数(根据元素,得到数组中的一个合法下标)

(哈希函数的计算:一般是 该元素 % 数组长度

举个例子:

数据集合: {1,7,6,4,5,9}

用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快

2、哈希冲突

(1)概念:

哈希冲突:不同关键字通过相同哈希函数计算出相同的哈希地址

例如上面的那个例子中:

如果集合中多添加了一个元素:14
此时,元素 4 和 14 所计算出来哈希地址是相同的,那数组中该怎么保存这两个元素,这就是哈希冲突

(哈希表底层数组的容量往往是小于实际要存储的关键字的数量的)
哈希冲突的发生是必然的 - 衣柜的容量是有限的,而衣服是无限的,总会出现哈希冲突的
但是我们可以尽量的 降低冲突率

  • 1、重新设计哈希函数
  • 2、理论上,数组长度可以使用素数(数组长度是 2 的 n 次方)
  • 3、设置一个负载因子的阈值,达到阈值时,通过扩容,来降低冲突率

(2)避免哈希冲突负载因子调节


(3)哈希冲突的解决方法:(重重重点)

1、探测法(开放寻址法)

当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把 key 存放到冲突位置中的“下一个” 空位置中去。

那如何寻找下一个空位置呢?

以这个为例,此时 4 和 14位置冲突

线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止

  • 通过哈希函数获取待插入元素在哈希表中的位置
  • 如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素
  • 采用这种方式处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。比如删除元素4,如果直接删除掉,14查找起来可能会受影响。因此线性探测采用标记的伪删除法来删除一个元素。

2、拉链法(哈希桶)(Java 的 HashMap选用这种)(重点学习!!)

使用另一种数据结构存储冲突元素 (单链表 -> 红黑树)

(具有相同地址的元素归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。)


从上图可以看出,每个桶中放的都是发生哈希冲突的元素。

如果数组中 存储的元素不是 int 类型 怎么办?(Person 对象)

(所有的 Object 类,都有一个方法 int hashCode())

  • 1、 通过该方法,将一个对象变为 int 类型(int hashValue = key.hashCode())
  • 2、把 int 类型转成一个合法下标(int index = hashValue % array.length)

代码实现拉链法:(重点掌握 插入元素的过程!!!)

public class MyHashTable {
   
    //1、数组:
    private Node[] array = new Node[11];
    //2、维护哈希表中的元素个数
    private int size;
    //true:key 之前不在哈希表中
    //false:key 之前已经在哈希表
    public boolean insert(Integer key) {
   
        //1、把对象转成 int 类型
        int hashValue = key.hashCode();
        //2、把 hashValue 转成合法下标
        int index = hashValue % array.length;
        //3、遍历 index 位置的链表,确定 key 在不在元素中
        Node cur = array[index];
        while (cur != null) {
   
            if (key.equals(cur.key)) {
   
                return false;
            }
            cur = cur.next;
        }
        //4、把 key 装入结点中,对插入到对应的链表中(JDK 1.8 改成尾插了)
        Node node = new Node(key);
        node.next = array[index];
        array[index] = node;
        //5、维护元素个数
        size++;
        return true;
    }

    public boolean remove(Integer key) {
   
        int hashValue = key.hashCode();
        int index = hashValue % array.length;
        Node cur = array[index];//要删除的当前结点
        //删除第一个结点
        if (key.equals(cur.key)) {
   
            cur.next = null;
            return true;
        }
        Node prev = null;//要删除的结点的前一个结点
        while (cur != null) {
   
            if (key.equals(cur.key)) {
   
                if (cur.next != null) {
   
                    //删除中间结点
                    prev.next = cur.next;
                } else {
   
                    //删除最后一个结点
                    prev.next = null;
                }
                size--;
                return true;
            }
            prev = cur;
            cur = cur.next;
        }
        //没找到要删除的元素
        return false;
    }

    public boolean contains(Integer key) {
   
        //找到对应的下标
        int hashValue = key.hashCode();
        int index = hashValue % array.length;
        Node cur = array[index];
        while (cur != null) {
   
            if (key.equals(cur.key)) {
   
                return true;
            }
            cur = cur.next;
        }
        return false;
    }
}


HashMap 和 HashSet 中有关对象时,都需要覆写 equels 和 hashCode 方法

如果不重写 hashCode ,就不能保证两个 index 是相同的(默认的 hashCode 十有八九是不相同的)
如果两个对象想通过 hashMap 找到,要意味两者的 hashCode 必须是相等的

3、Java 类集的关系:

  • 1、HashMap 和 HashSet 即 java 中利用哈希表实现的 Map 和 Set 接口
  • 2、 java 中使用的是哈希桶方式解决冲突的
  • 3、java 会在冲突链表长度大于一定阈值后,将链表转变为搜索树(红黑树)
  • 4、java 中计算哈希值实际上是调用的类的 hashCode 方法,进行 key 的相等性比较是调用 key 的 equals 方法。所以如果要用自定义类作为 HashMap 的 key 或者 HashSet 的值,必须覆写 hashCode 和 equals 方法,而且要做到 equals 相等的对象,hashCode 一定是一致的

转载:https://blog.csdn.net/qq_45658339/article/details/115828883
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场