哈希表

哈希表的引入

  • 哈希表是基于数组衍生出来的, 它拥有数组的随机访问能力, 所以哈希表非常高效
  • 假设我们现在要在下面这样一个数组中查找 5 这个元素, 哈希表就能以O(1)的时间复杂度实现
    在这里插入图片描述
    具体实现方式如下:
  1. 首先创建一个boolean[]类型的数组, 先遍历原来的数组, 第一个元素为7 我们就把新数组下标为 7 的内容标记为true, 第二个元素是 2 我们就在新数组下标为 2 的内容里填上true 如此一来新数组就变成了这样
    在这里插入图片描述

  2. 这时我们再要查找 5 这个元素只需要访问新数组 下标为 5 的元素, 看内容是true还是false

  • 这种方法也有一定的弊端, 好比这样的数组
    在这里插入图片描述
    我们没有办法去创建一个 1 ~ 100001 这么大的数组, 这样太浪费空间了, 所以我们想出了一个办法, 就是想办法创建出一种映射关系, 让数字能够对应到一个指定的下标, 我们就引入了 哈希函数 这一概念.

哈希函数

  • 哈希函数的目的就在于把一些数值较大的数字, 通过一系列复杂的数学变换, 去映射到一个简单地数组上. 方便我们通过下标寻找该数字.

  • 需要注意的是, 哈希函数只是为了降低两个数字对应到一个下标上的概率, 但是还是会存在一定的概率有可能发生冲突, 而我们把两个或多个数字对应到一个下标上发生的这一类冲突, 称为哈希冲突. 而解决这类冲突的方法就是哈希函数.

  • 哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是也无法避免哈希冲突 .

哈希冲突的两种解决方法

1. 闭散列(不常用)
  • 好比我们现在有下面这样一个数组, 我们要通过 % 的映射关系去创建一个新的boolean类型的数组, 但是出现了 0 和 20 都对应到下标 0 的位置上, 产生了哈希冲突.
    在这里插入图片描述
  • 解决冲突的方案就是 我们在 0 号下标的后面找 找到一个没有映射关系的位置, 把 20 放进去, 就是下标为 2 的位置.
  • 但是这种方案有非常大的弊端, 现在冲突的数字只有两个, 但是万一冲突的数字多了, 我们还要继续把冲突的数字放到后面的下标中 这样在后续找该数字的时候, 会非常难找, 并且也会导致其他数字的位置发生变化.
  • 所以我们有一种更好的解决方案, 就是下面的开散列
2. 开散列 / 哈希桶 (重点)
  • 还是对于上面那种情况, 开散列的解决措施就没有那么简单粗暴了, 而是将新数组的每个元素都变成一个链表.
  • 如果有冲突的元素, 我们就把冲突的元素连接到该链表上
  • 代码如下:
private HashNode[] arr = new HashNode[20];
    private int size = 0;

    //通过这个方法把key映射成下标
    private int hashCode(int key){
        return key % arr.length;
    }

    public void put(int key, int val){
        // 1. 先计算要放到哪个下标上
        int index = hashCode(key);
        // 2. 再遍历该下标所在的链表看该 key 是否存在
        //     如果存在直接修改 val 的值即可
        for(HashNode cur = arr[index]; cur != null; cur = cur.next){
            if(key == cur.key){
                cur.val = val;
                return;
            }
        }
        // 3. 如果该 key 不存在就创建一个新节点,头插到链表上
        HashNode newNode = new HashNode(key, val);
        newNode.next = arr[index];
        arr[index] = newNode;
        size++;
        // 4. 这时还需要判断数组内元素是不是过于多了, 哈希冲突的可能就会越大
        //    我们引入一个 负载因子 的概念
        //    就是 元素个数 / 数组长度
        //    这里设成 0.75 是因为java标准库中的负载因子就是 0.75
        if((double)size / arr.length > 0.75){
            resize();
        }
    }

    private void resize() {
        //扩容的原理就是创建一个更大的数组, 把原来的数组中的元素搬运到新数组中就行了
        HashNode[] newarr = new HashNode[arr.length*2];
        for(int i = 0; i < arr.length; i++){
            HashNode next = null;
            for(HashNode cur = arr[i]; cur != null; cur = cur.next){
                next = cur.next;
                int newIndex = cur.key % newarr.length;
                cur.next = newarr[newIndex];
                newarr[newIndex] = cur;
            }
        }
        arr = newarr;
    }

    public Integer get(int key){
        int index = hashCode(key);
        for(HashNode cur = arr[index]; cur != null; cur = cur.next){
            if(key == cur.key){
                return cur.val;
            }
        }
        return null;
    }

    public void remove(int key){
        int index = hashCode(key);
        for(HashNode cur = arr[index]; cur != null;){
            HashNode pre = cur;
            if(key == cur.key){
                pre.next = cur.next;
            }
            pre = cur;
            cur = cur.next;
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值