java-----哈希冲突

Posted 小鹿可可乐

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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;
            }
        }
    }


}

今天也要好好学习呀~

以上是关于java-----哈希冲突的主要内容,如果未能解决你的问题,请参考以下文章

java-----哈希冲突

Java集合哈希冲突及解决哈希冲突的4种方式

数据结构 | java中 哈希表及其冲突解决

C++进阶第二十一篇——哈希(概念+哈希函数+哈希冲突+哈希表+哈希桶+代码实现)

Java哈希表(散列表)解析

Java 数据结构HashMap和HashSet