java-----哈希冲突

1,何时哈希冲突

首先在哈希表存储元素时,最初是根据值的hashcode()计算结果作为下标在数组中进行存储,数组开辟的大小至少等于hashcode()值,存在一个问题:如果只有一个元素,但是他的hashcode值很大时,那么就存在堆内存严重浪费的问题。当多个元素的hashcode()返回值相同时,就会发生哈希冲突。
1.当将8、1、2、7都放入数组中后,再放6会发生哈希冲突
在这里插入图片描述
当数组大小为4时
放入过程:

  • 8%4=0 将8放入0号下标
  • 1%4=1 放入1号下标处
  • 2%4=2 放2号下标处
  • 7%4=3 放入3号下标处

扩容之后,需要重新哈希
默认大小:16
扩容倍数:0.75

2.线性探测法

思想:主要根据index = val.hashcode()%element.length来解决哈希冲突问题。从数组当前位置(hashcode())开始,找到下一个空位置,进行存放。当数组填充满之后,进行数组的扩容操作,继续存放值。存储的键值对中键相等进行值的覆盖操作。进行扩容后的操作,还得进行元素的重新哈希操作。

在这里插入图片描述
扩容之后,需要重新哈希
默认大小:16
扩容倍数:0.75
在这里插入图片描述

2.1 代码实现

map:

public interface Map<K,V>{
    void put(K key,V value);//添加
    boolean containKey(K key);//是否包含键
    void remove(K key);//删除k
    V get(K key);//查找k,返回对应value
}

//线性探测法
public class MyHashMap<K extends Comparable<K>,V> implements Map<K,V>{

    private Entry<K,V>[] element;
    private int size;//有效节点个数

    public MyHashMap(){
        element = new Entry[16];
    }

    /*
    重新哈希
     */
    private void rehash(Entry[] newArray){
        //原有的数据依次遍历  重新哈希到新数组中
        int index;
        for(int i = 0;i< element.length;i++){
            if(element[i] != null){
                index = element[i].key.hashCode()% newArray.length;
                if(newArray[index]==null){
                    newArray[index] = element[i];
                }else {
                    for (int j = (index + 1) % newArray.length; j != index; j = (++j) % newArray.length) {
                        if (newArray[j] == null) {
                            newArray[j] = element[i];
                            break;
                        }
                    }
                }

            }
        }
        element = newArray;
    }
    @Override
    public void put(K key, V value) {
        //1.如果数组“已满“,扩容因子=> 0.75(不能全满,效率高),扩容,重新哈希
        if((double)size/element.length >=0.75){
            Entry[] newElement = new Entry[element.length*2];
            rehash(newElement);
        }
        //2.index? 如果哈希冲突,从当前位置开始找一圈,找一个空位置插入
        int index = key.hashCode()% element.length;//获得插入数据的下标
        if(element[index] == null){
            element[index] = new Entry<>(key,value);
        }else{  //存在元素
            for(int i = (index+1)%element.length;i != index;i=(++i)% element.length){
                if(element[i]==null){
                    element[i] = new Entry<>(key,value);
                    break;
                }
            }
        }

        size++;

    }

    @Override
    public boolean containKey(K key) {
        for(int i = 0;i < element.length;i++){
            if(element[i] != null){
                if(element[i].key.compareTo(key)==0){
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public void remove(K key) {
        for(int i = 0;i < element.length;i++){
            if(element[i] != null){
                if(element[i].key.compareTo(key)==0){
                    element[i]=null;
                }
            }
        }

    }

    @Override
    public V get(K key) {
        for(int i = 0;i < element.length;i++){
            if(element[i] != null){
                if(element[i].key.compareTo(key)==0){
                    return element[i].value;
                }
            }
        }
        return null;
    }

    static class Entry<K,V>{
        private K key;
        private V value;

        public Entry(K key,V value){
            this.key = key;
            this.value = value;
        }
    }
}

3.链地址法

线性探测法当产生哈希冲突的键值对越多,遍历数组的次数就会越多。为了解决这个问题,我们采用数组和链表结合的方式,数组元素是一个链表结构,产生哈希冲突,在相同的索引下标下进行链表的插入操作。
在这里插入图片描述
缺点:
① 链地址法存在的不足在于,当产生哈希冲突的节点越多,那么数组索引下标查找就会变成链表的遍历操作。数组的优势在于随机取值,时间复杂度达到O(1),而链表的查找时间效率达到O(n)。
② 当产生数组扩容时,所有的元素节点都必须进行重新哈希。

3.1代码实现

重新哈希:
在这里插入图片描述

import src15.Map;

public class MyLinkedHashMap<K extends Comparable<K>,V>implements Map<K,V> {
    private Node<K,V>[] element;//Node 节点类型
    private int node_size;//保存当前节点个数
    private int array_size;//数组有效个数
    private static final double LOADFACTOR = 0.75;
    private static final int INITSIZE = 10;

    //构造
    public MyLinkedHashMap(){
        element = new Node[INITSIZE];
    }

    private void rehash(Node<K,V>[] array){
        array_size=0;
        for(int i = 0;i<element.length;i++){//遍历原数组
            while(element[i] != null){
                Node<K,V> p = element[i];//p用来节点删除以及头插array中
                element[i] = element[i].next;//p从当前链表独立出来
                int arrayIndex = p.value.key.hashCode()% array.length;//新数组插入的下标
                if(array[arrayIndex]==null){
                    array_size++;
                }
                p.next = array[arrayIndex];
                array[arrayIndex] = p;//p头插进数组中
            }
        }
        element = array;
    }
    @Override
    public void put(K key, V value) {
        //map特点  键相同,值覆盖
        Node.Entry<K,V> entryKV = containKey0(key);
        if(entryKV !=null){//找到,值覆盖
            entryKV.value=value;
            return;
        }

        //键不存在,插入操作
        if (1.0 * array_size / element.length >= LOADFACTOR) {//扩容
            Node<K, V>[] newArray = new Node[element.length * 2];
            rehash(newArray);
        }

        //1.根据key值,求index -> 单链表头插
        int index = key.hashCode()% element.length;
        if(element[index] == null){
            array_size++;
        }
        //2.头插法
        Node.Entry<K,V> entry = new Node.Entry<>(key,value);
        Node<K,V> node = new Node<>(entry);
        node.next = element[index];// 头插    新节点的next 保存原来”head‘
        element[index] = node;//  保存新的头部起始位置
        node_size++;
    }

    private Node.Entry containKey0(K key){
        int index = key.hashCode()% element.length;
        for(Node<K,V> p = element[index];p!=null;p=p.next){
            if(p.value.key.compareTo(key) == 0){
                return p.value;
            }
        }
        return null;
    }
    @Override
    public boolean containKey(K key) {
        return containKey0(key) != null;
    }

    @Override
    public void remove(K key) {


    }

    @Override
    public V get(K key) {
        Node.Entry<K,V> entry = containKey0(key);
        if(entry == null){
            return null;
        }else{
            return entry.value;
        }
    }


    //单链表节点类型
    static class Node<K,V>{
        private Entry<K,V> value;
        private Node<K,V> next;

        //构造
        public Node(Entry<K, V> value) {
            this.value = value;
        }

        //键值对
        static class Entry<K,V>{
            private K key;
            private V value;

            public Entry(K key, V value) {
                this.key = key;
                this.value = value;
            }
        }
    }


}

今天也要好好学习呀~

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
在Java中,哈冲突是指当多个元素的哈值相同时,在哈表中发生的冲突。哈表是根据元素的哈值将元素存储在数组中的数据结构。当两个或多个元素的哈值相同时,它们会被存储在数组的同一个位置,导致冲突。\[2\]哈冲突是由于哈函数的映射范围有限,而输入的数据量是无限的,所以不同的数据可能会映射到相同的哈值上。在Java中,解决哈冲突的方法之一是链接地址法,即使用链表来存储同义词。在JDK 1.7中,完全采用单链表来存储同义词;而在JDK 1.8中,采用了一种混合模式,对于链表长度大于8的,会转换为红黑树来存储,以提高查询效率。\[1\]另外一种解决哈冲突的方法是再哈法,即使用另一个哈函数来重新计算冲突的元素的位置。\[2\]总之,哈冲突是指在哈表中多个元素具有相同的哈值,而解决哈冲突的方法有链接地址法和再哈法。 #### 引用[.reference_title] - *1* *3* [哈冲突的解决方法](https://blog.csdn.net/weixin_34256074/article/details/91994040)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [java-----冲突](https://blog.csdn.net/m0_54720446/article/details/120207250)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小鹿可可乐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值