哈希表小结-冲突解决——哈希桶实现(开散列法)代码+思路+图片详解

概念小结

冲突

冲突一词出现在哈希表当中,即不同关键字通过相同的哈希函数计算出相同的哈希地址,该现象称为哈希冲突(也叫哈希碰撞)。对于关键字的理解你可以认为它就是你想放进哈希表中的元素。

哈希函数

哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(HashTable)(或者称散列表) 。 相当于每个哈希表都会有哈希函数来解决冲突。哈希函数也有很多种,但常用的是除留余数法(设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址),在此我们一般会把散列表的大小作为p,以此来设计散列函数。
对于散列表其实就是数组

冲突避免

第一个就是设置哈希函数,第二个就是对负载因子的调节
负载因子的定义表中的元素个数 / 散列表的长度
负载因子越大冲突率越高(在开放定址法中限制在0.7-0.8以下)
解决:散列表中元素是一定的,则对散列表扩容才可以避免
强调:冲突是必然发生的,我们只能降低冲突率

冲突解决

1.闭散列

也叫开放定址法:当发生哈希冲突时,如果哈希表没被装满,则可以把key存放到冲突位置的“下一个”空位置中去
寻找“下一个”空位置:

线性探测法

在这里插入图片描述
当key为13模8为5,此时5位置已经有了,发生冲突,则只需将13往后面移动即可,直到找出空位置,这就是线性探测法。

二次探测法

在使用线性探测法处理冲突时,最后会发现数据都集中在一起,二次探测法就是为了避免这样的情况。hash(key) = (key + i^2),其中i为1,2,3… 或者是将加号换成相减也可以。
在这里插入图片描述
当第一次使用二次探测法取余为5时,此时5位置有元素,则进行第二次取余,取余为0则将12放到0位置
缺陷:* 闭散列法空间利用率较低*

2.开散列(哈希桶)

开散列法又叫链地址法(开链法),首先对关键码集合用哈希函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中(即数组中)。

哈希桶实现(key - val模型)

1.哈希桶由数组+链表+红黑树构成。
2.简单理解为数组中每个下标中存放链表的头结点,然后由头结点组成链表(看图)
在这里插入图片描述

具体实现(代码+文字+图片)

1.创建数组链表

    static class Node {   //内部类
        public int key;
        public int val;
        public Node next;   //存放下一个结点的地址

        public Node(int key, int val) {
            this.key = key;
            this.val = val;
        }
    }
    public Node[] array;   //创建数组,类型为Node类型(因为存放的是链表)
    public int usedSize;    //当前数组里面数据个数	

    public static final double LOAD_FACTOR = 0.75;   //定义负载因子
    public HashBucket(){
        array = new Node[10];   //定义数组大小
    }

2.插入实现

思路: 1. 使用哈希函数计算关键字key在数组中的位置
2.定义一个Node类型的cur对链表进行遍历,找出key,若有就将val替换,若无就进行插入(此处采用头插法,尾插也可,用尾插就先找出链表尾部)。
在这里插入图片描述
3.插入成功后则usedSize++
4.判断负载因子的大小,若超过就要进行扩容操作

    public void put(int key, int value) {
        int index = key % array.length; //用哈希函数计算key在数组中的下标
        Node cur = array[index];   //对index下标的链表进行遍历
        while (cur != null){   //该下标下面有链表
            if (cur.key == key){          //若链表中有key,将val进行覆盖
                cur.val = value;
                return;
            }
            cur = cur.next;       //走向下一个结点
        }
        //链表中无key,则进行头插
        Node node = new Node(key,value);
        node.next = array[index];
        array[index] = node;
        usedSize++;
        if (calculateLodeFactor() >= LOAD_FACTOR){   //负载因子大于等于0.75进行扩容
            resize();    //调用扩容方法
        }
    }
     private double calculateLodeFactor(){   //计算负载因子
        return usedSize*1.0 / array.length;
    }

3.扩容实现

注意:在这里插入图片描述

在这里插入图片描述

    private void resize() {   //哈希表的扩容
        Node[] newArray = new Node[2*array.length];     //进行二倍扩容
        //逐个遍历,调整映射关系
        for (int i = 0; i < array.length; i++) {
            Node cur = array[i];
            while (cur != null){
                Node curNext = cur.next;    //定义一个新node,用来保存cur在旧数组中的位置,以便于cur继续向后面走
                int index = cur.key % newArray.length;    //找到key在新的数组对应的位置(映射关系)
                //进行头插。将cur指向的结点插到新数组对应的位置
                cur.next = newArray[index];
                newArray[index] = cur;
                //cur继续向后面走,调整链表中其余结点对应的位置(映射关系)
                cur = curNext;
            }
        }
        //将新的数组给旧数组,扩容成功
        array = newArray;
    }

4.查找实现

思路:在这里插入图片描述

    public int get(int key) {
        int index = key % array.length;  //找到key的位置
        Node cur = array[index];
        //对链表进行遍历
        while (cur != null){
            if (cur.key == key){
                return cur.val;
            }
            cur = cur.next;
        }
        return -1;   //无关键字key
    }

5. 测试类(对上述方法测试)

    public static void main(String[] args) {
        HashBucket hashBucket = new HashBucket();
        hashBucket.put(2,12);
        hashBucket.put(3,13);
        hashBucket.put(4,14);
        hashBucket.put(5,15);
        hashBucket.put(6,16);
        hashBucket.put(7,17);
        hashBucket.put(8,18);
        System.out.println("---------------");
        int ret = hashBucket.get(7);
        System.out.println(ret);
    }
插入,扩容在这里插入图片描述

此时在数组中有7个数据(结点),小于负载因子则不需要扩容,接下来来测测扩容,当我们在数组里面放8个数据(结点),负载因子:8 / 10 = 8 大于0.75,则就会以2倍数组大小扩容
在这里插入图片描述

get方法

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

整体代码

public class HashBucket {   // key-value 模型
    static class Node {
        public int key;
        public int val;
        public Node next;

        public Node(int key, int val) {
            this.key = key;
            this.val = val;
        }
    }
    public Node[] array;
    public int usedSize;    //当前数据个数

    public static final double LOAD_FACTOR = 0.75;
    public HashBucket(){
        array = new Node[10];   //默认数组的大小
    }


    public void put(int key, int value) {
        int index = key % array.length; //用散列函数计算key在数组中的下标
        Node cur = array[index];   //对index下标的链表进行遍历
        while (cur != null){   //该下标下面有链表
            if (cur.key == key){          //若链表中有key,将val进行覆盖
                cur.val = value;
                return;
            }
            cur = cur.next;
        }
        //链表中无key,则进行头插
        Node node = new Node(key,value);
        node.next = array[index];
        array[index] = node;
        usedSize++;
        if (calculateLodeFactor() >= LOAD_FACTOR){   //负载因子大于等于0.75进行扩容
            resize();
        }
    }

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

    private void resize() {   //哈希表的扩容
        Node[] newArray = new Node[2*array.length];     //进行二倍扩容
        //逐个遍历,调整映射关系
        for (int i = 0; i < array.length; i++) {
            Node cur = array[i];
            while (cur != null){
                Node curNext = cur.next;    //定义一个新node,用来保存cur在旧数组中的位置,以便于cur继续向后面走
                int index = cur.key % newArray.length;    //找到key在新的数组对应的位置(映射关系)
                //进行头插。将cur指向的结点插到新数组对应的位置
                cur.next = newArray[index];
                newArray[index] = cur;
                //cur继续向后面走,调整链表中其余结点对应的位置(映射关系)
                cur = curNext;
            }
        }
        //将新的数组给旧数组,扩容成功
        array = newArray;
    }


    public int get(int key) {
        int index = key % array.length;  //找到key的位置
        Node cur = array[index];
        //对链表进行遍历
        while (cur != null){
            if (cur.key == key){
                return cur.val;
            }
            cur = cur.next;
        }
        return -1;
    }
}
public class Test_HashBucket {

    public static void main(String[] args) {
        HashBucket hashBucket = new HashBucket();
        hashBucket.put(2,12);
        hashBucket.put(3,13);
        hashBucket.put(4,14);
        hashBucket.put(5,15);
        hashBucket.put(6,16);
        hashBucket.put(7,17);
        hashBucket.put(8,18);
        hashBucket.put(9,19);
        System.out.println("---------------");
        int ret = hashBucket.get(19);
        System.out.println(ret);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值