哈希表的使用及冲突处理(闭散列、开散列(哈希桶的插入、查找、删除))

概念

哈希表就是一种以 键-值(key-value) 存储数据的结构,我们只要输入待查找的值即key,即可查找到其对应的值。插入删除都是O(1)
插入元素 :
根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放
搜索元素 :
对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若 关键码相等,则搜索成功
该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(Hash Table)(或者称散列表)

哈希函数

除留余数法
设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数: Hash(key) = key% p(p<=m),将key转换成哈希地址

key % 数组长度 =》下标值
哈希查找第一步就是使用哈希函数将键映射成索引。这种映射函数就是哈希函数。
在实际中,我们的键并不都是数字,有可能是字符串,还有可能是几个值的组合等,所以我们需要实现自己的哈希函数。
特点: 两个相同的key,计算得到的hash值一定相同。

如果key为整数,hash‘值计算方式主要就是通过除留余数的方式
如果key为字符串,利用md5 sha1这种常用的字符串哈希算法

md5:
1、无论输入的字符串有多长,最终得到的md5的值都是固定长度。
2、如果两个字符串大部分内容相同,只有某个字符不同,计算生成的md5的值差别也会很大,这就决定了,md5计算的hash值最终冲突概率比较小,工程上可以忽略不记。
3、通过字符串计算哈希值很高效,通过md5找出字符串,计算量非常大,理论上不可行经常用于加密场景中。

md5可以用来进行文件校验,对文件计算md5之后进行比较如果md5值相等则相同。

冲突

如果两个不同的key,通过哈希函数计算之后得到了相同的hash值即:不同关键字通过相同哈希函数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。 把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。

处理冲突

避免冲突

尽量避免冲突(降低冲突概率)
负载因子:哈希表中保存的元素个数 / 数组的长度
在这里插入图片描述
已知哈希表中已有的关键字个数是不可变的,那我们能调整的就只有哈希表中的数组的大小

如果给定的的数组只有120个元素大小,但是实际上有100个元素需要保存,此时冲突的概率也会比较高,相反的如果给定的的数组有1000个元素大小,但是实际上有100个元素需要保存,此时冲突的概率就会比较低 (负载因子越小,效率越高)

选择更合适的hash函数,一般将数组的长度为素数,哈希冲突会降低很多。
在这里插入图片描述

正面应对

a) 闭散列

发现冲突后,就在冲突的位置的周围找到一个空闲位置来进行插入元素。
常见查找位置方法:
1、线性探测(如下图)
在这里插入图片描述
2、二次探测
可以左右震荡 (下标以平方的大小增加,45+1(45、46)、45+4(45、46、47、48)、45+9、45+16、45+25.。。。。)

b) 开散列(哈希桶)

开散列的数组中的每个节点相当于一个链表的头结点,当出现hash冲突的时候,就把新的元素插入到链表中。
在这里插入图片描述

无论是第一种还是第二种方法,当哈希冲突比较严重的时候,插入查找删除的效率仍然会比较低。

哈希冲突比较严重:

1)扩容(降低负载因子),成本比较高
2)针对开散列来说(链表),把链表替换成更高效的数据结构,比如二叉搜索树(Java HashSet / HashMap),或者哈希表(二次哈希)

性能分析

虽然哈希表一直在和冲突做斗争,但在实际使用过程中,我们认为哈希表的冲突率是不高的,冲突个数是可控的, 也就是每个桶中的链表的长度是一个常数,所以,通常意义下,我们认为哈希表的插入/删除/查找时间复杂度是 O(1)
哈希表对于空间的利用率是比二叉搜索树、顺序表之类的都低。

哈希表和 java 类集的关系

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

哈希表实现


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

        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }
    private Node[] array = new Node[101];
    private int size = 0; //哈希表实际元素个数
    //负载因子:size/array.length

    private int hashFunc(int key){
        return key % array.length;
    }
    //插入
    public void put(int key, int value){
        //1、根据key带入哈希函数,计算下标,用key%数组长度
        int index = hashFunc(key);
        //2、根据下标得到对应的链表
        Node head = array[index];
        //3、先判定当前key是否存在,存在就修改value(不插入新节点)
        for( Node cur = head; cur != null; cur = cur.next){
            if(cur.key == key){
                cur.value = value;
                return;
            }
        }
        //4、如果不存在,则插入
        //进行链表头插
        Node newNode = new Node(key, value);
        newNode.next = head;
        array[index] = head;
        size++;
    }
    public Integer get(int key){
        //1、根据key得到hash值
        int index = hashFunc(key);
        //2、在对应的链表中查找指定key对应的节点
        Node head = array[index];
        for (Node cur = head; cur != null; cur = cur.next){
            if(cur.key == key){
                return cur.value;
            }
        }
        //3、没有找到
        return null;
    }
    //删除
    private Node finPrev(int key){ //找到要删除的前驱
        int index = hashFunc(key);
        Node prev = array[index];
        while (prev.next != null){
            if(prev.next.key == key){
                return prev;
            }
            prev = prev.next;
        }
        return null;
    }

    public void delete(int key){
        int index = hashFunc(key);
        Node head = array[index];
        Node prev = finPrev(key);
        if(head.key == key){
             head.next = array[index];
            return;
        }
        if(prev == null){
            System.out.println("没有这个元素!");
            return;
        }
        Node cur = prev.next;
        prev.next = cur.next;
        size--;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值