加入收藏 | 设为首页 | 会员中心 | 我要投稿 核心网 (https://www.hxwgxz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 电商 > 正文

自己动手实现java数据结构(五)哈希表

发布时间:2021-04-04 12:21:30 所属栏目:电商 来源:网络整理
导读:1.哈希表介绍 前面我们已经介绍了许多类型的数据结构。在想要查询容器内特定元素时,有序向量使得我们能使用二分查找法进行精确的查询(( O(logN)对数复杂度,很高效 )。 可人类总是不知满足,依然在寻求一种更高效的特定元素查询的数据结构, 哈希表/散列表(

  因此getIndex方法的时间复杂度为O(1)。

   
     * 通过key的hashCode获得对应的内部数组下标
     *  key 传入的键值key
     *  对应的内部数组下标
     *  getIndex(K key){
        return getIndex(key,1)">.elements);
    }

    
     * 通过key的hashCode获得对应的内部数组插槽slot下标
     *  elements 内部数组
     * int getIndex(K key,EntryNode<K,1)">[] elements){
        ){
            ::: null 默认存储在第0个桶内
            return 0;
        }{
            int hashCode = key.hashCode();

            :::通过 高位和低位的异或运算,获得最终的hash映射,减少碰撞的几率
            int finalHashCode = hashCode ^ (hashCode >>> 16);
            return (elements.length-1) & finalHashCode;
        }
    }

3.3 链表查询方法:

  当出现hash冲突时,会在对应插槽处生成一个单链表。我们需要提供一个方便的单链表查询方法,将增删改查接口的部分公用逻辑抽象出来,简化代码的复杂度。

  值得注意的是:在判断Key值是否相等时使用的是EntryNode.keyIsEquals方法,内部最终是通过equals方法进行比较的。也就是说,判断key值是否相等和其它数据结构一样,依然是由equals方法决定的。hashCode方法的作用仅仅是使我们能够更快的定位到所映射的插槽处,加快查询效率。

  思考一下,为什么要求在重写equals方法的同时,也应该重写hashCode方法?

   
     * 获得目标节点的前一个节点
     *  currentNode 当前桶链表节点
     *  key         对应的key
     *   返回当前桶链表中"匹配key的目标节点"的"前一个节点"
     *          注意:当桶链表中不存在匹配节点时,返回桶链表的最后一个节点
     *  currentNode,K key){
        :::不匹配
        EntryNode<K,V> nextNode = currentNode.next;
        :::遍历当前桶后面的所有节点
        while(nextNode != :::如果下一个节点的key匹配
            if(nextNode.keyIsEquals(key)){
                 currentNode;
            }:::不断指向下一个节点
                currentNode = nextNode;
                nextNode = nextNode.next;
            }
        }
        :::到达了桶链表的末尾,返回最后一个节点
         currentNode;
    }

3.4 增删改查接口:

  哈希表的增删改查接口都是通过hash值直接计算出对应的插槽下标(getIndex方法),然后遍历插槽内的桶链表进行进一步的精确查询(getTargetPreviousEntryNode方法)。在负载因子位于正常范围内时(一般小于1),桶链表的平均长度非常短,可以认为单个桶链表的遍历查询时间复杂度为(O(1))。

  因此哈希表的增删改查接口的时间复杂度都是O(1)。

    @Override
     V put(K key,V value) {
        (needReHash()){
            reHash();
        }

        :::获得对应的内部数组下标
        int index = getIndex(key);
        :::获得对应桶内的第一个节点
        EntryNode<K,V> firstEntryNode = .elements[index];

        :::如果当前桶内不存在任何节点
        if(firstEntryNode == :::创建一个新的节点
            this.elements[index] = new EntryNode<>(key,value);
            :::创建了新节点,size加1
            this.size++;
            ;
        }

        (firstEntryNode.keyIsEquals(key)){
            :::当前第一个节点的key与之匹配
            V oldValue = firstEntryNode.value;
            firstEntryNode.value = value;
             oldValue;
        }:::不匹配

            :::获得匹配的目标节点的前一个节点
            EntryNode<K,V> targetPreviousNode = getTargetPreviousEntryNode(firstEntryNode,key);
            :::获得匹配的目标节点
            EntryNode<K,V> targetNode = targetPreviousNode.next;
            if(targetNode != :::更新value的值
                V oldValue = targetNode.value;
                targetNode.value = value;
                 oldValue;
            }:::在桶链表的末尾 新增一个节点
                targetPreviousNode.next = :::创建了新节点,size加1
                ;
                ;
            }
        }
    }

    @Override
     V remove(K key) {
        ;
        }
        :::当前第一个节点的key与之匹配

            :::将桶链表的第一个节点指向后一个节点(兼容next为null的情况)
            this.elements[index] = firstEntryNode.next;
            :::移除了一个节点 size减一
            this.size--:::返回之前的value值
             firstEntryNode.value;
        } targetPreviousNode.next;

            :::将"前一个节点的next" 指向 "目标节点的next" ---> 相当于将目标节点从桶链表移除
                targetPreviousNode.next = targetNode.next;
                :::移除了一个节点 size减一
                 targetNode.value;
            }:::如果目标节点为空,说明key并不存在于哈希表中
                 V get(K key) {
        :::当前第一个节点的key与之匹配
            ;
            }
        }
    }

3.5 扩容rehash操作:

  前面提到,当插入数据时发现哈希表过于拥挤,超过了负载因子指定的值时,会触发一次rehash扩容操作。

  扩容时,我们的内部数组扩容了2倍,所以对于每一个插槽内的元素在rehash时存在两种可能:

    1.依然映射到当前下标插槽处

    2.映射到高位下标处(当前下标 + 扩容前内部数组长度大小)

(编辑:核心网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读