Java哈希表详解

一.什么是哈希表

哈希表也叫散列表,哈希表是一种数据结构,它提供了快速的插入操作和查找操作,无论哈希表总中有多少条数据,插入和查找的时间复杂度都是为O(1),因为哈希表的查找速度非常快,所以在很多程序中都有使用哈希表,例如拼音检查器等。

二.为什么引入哈希表

        直接原因:

顺序结构以及平衡树 中,元素关键码与其存储位置之间没有对应的关系,因此在 查找一个元素时,必须要经过关键 码的多次比较 顺序查找时间复杂度为 O(N) ,平衡树中为树的高度,即 O(log2N )    ,搜索的效率取决于搜索过程中元素的比较次数。
    所以需要一个查找效率比较快的结构
如果有一种结构 不需要数据的比较就能够找到搜索的元素,那么这种结构就是我们理想的结构,它的查找的时间复杂度为O(1) 
如果构造一种存储结构,通过某种函 (hashFunc) 使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快 找到该元素
对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功。
该方式即为哈希 ( 散列 ) 方法, 哈希方法中使用的转换函数称为 哈希(散列)函数 ,构造出来的结构称为哈希表 (Hash Table)( 或者称散列表 )

三. 哈希函数

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

如果哈希函数设置为:hash(key) = key % capacity; capacity为存储元素底层空间总的大小。

解释 此数组的capacity为10   放入1的时候 1%10 = 1 所以放到数组 1 下表

        其他的也类似

 问题:如果我要放入 14 的时候 14 % 10 = 4  可是目前数组4下表已经有元素了 所以14就放不进去

四.哈希冲突

对于两个数据元素的关键字 和 (i != j) ,有 != ,但有: Hash( ) == Hash( ) ,即: 不同关键字通过相同哈希哈数计算出相同的哈希地址 ,该种现象称为哈希冲突或哈希碰撞
把具有不同关键码而具有相同哈希地址的数据元素称为 同义词”。
在上图中 不同的关键字4 和 14 通过相同的哈希函数hash(key) = key % capacity,计算出了相同的值。

那么如何避免哈希冲突?----->设计合理的哈希函数 

冲突的-避免-负载因子调节(重点掌握)

五.精讲    冲突-解决-开散列/哈希桶(重点掌握) 

闭散列先不讲

开散列法又叫链地址法 ( 开链法 ) ,首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个 单链表 链接起来,各链表的头结点存储在哈希表中。 我们可以看到 数组中每一个有效元素的下方都是一个单链表 此 时我们再尝试放入14
就解我决了14放不进去的问题

 如果哈希表中插入重复的数据会怎么样?当插入重复的数据时 会进行覆盖,保证数据唯一性

代码实现

public class hashBucket {
    static class Node {
        private int key;
        private int val;
        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;

    hashBucket() {
        array = new Node[10];
    }

    public void put(int key, int val) {
        /*
        放入元素的时时候 如果有这个数据就覆盖 没有的话进行头插
         */
        int index = key % array.length;  //数组映射关系函数
        Node cur = array[index];  //cur指向当前节点,因为要在当前的桶放入元素
        while (cur != null) {
            if(cur.key == key) {   //如果有当前key 说明要进行数据覆盖
                cur.val = val;
            }
            cur = cur.next;
        }
        //走到这里说明都没有找到重复的值,此时进行头插
        Node node = new Node(key,val);
        node.next = array[index];  //可以吧array[index]看作为head头结点来进行理解
        array[index] = node;
        usedSize++;

        //查看是否超过了负载因子 如果超过考虑扩容
        if(LOAD_FACTOR <= loadFactor()) {
            resize();
        }
    }
public double loadFactor() {
        //查看是否超过了负载因子
        return usedSize*1.0 / array.length;
    }

5.1 哈希扩容

如果当前数组大小不满足需求的时候,需要对数组进行扩容。

需要注意的是,如果进行扩容 那么函数的映射关系会发生改变,也就是说要再新数组中重新映射元素。

比如 数组长度为10  此时14放到了 4 下标
     扩容后数组长度为20  此时14 放到了 14 下标

代码如下

 private void resize() {
        /*
        进行开散列扩容的时候 需要注意到可能 原有的映射关系可能不对应新的数组
        比如 数组长度为10  此时14放到了 4 下标
            数组长度为20  此时14 放到了 14 下标
        所以要重新进行函数映射关系
         */
        Node[] newArray = new Node[array.length * 2];  //新数组  2倍扩容
        //重新进行映射
        for (int i = 0; i < array.length; i++) {
            Node cur = array[i]; //从旧数组的0下标开始挨个映射

            //在新数组中进行头插
            while (cur != null) {
                Node curNext = cur.next;
                int index = cur.key % newArray.length;  //1.找到旧数组下标在新数组当中的位置

                cur.next = newArray[index];  //2.头插+
                newArray[index] = cur;

                cur = curNext;

            }
        }
        array = newArray;
    }

5.2获取元素 

可以通过哈希函数设置元素 也可以通过哈希函数获取元素

步骤 1.先找到数组下标

        2.在此下标中链式查找,如果找到就返回

public int get(int key) {
        int index = key % array.length;  //比如要找18 18在8下标的位置,肯定是链式存储
        Node cur = array[index];
        while (cur != null) {
            if(cur.key == key) {
                return cur.val;
            }
            cur = cur.next;
        }

        return -1;

    }

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值