哈希冲突

在保存元素不是固定范围时,哈希冲突有可能避免吗?

不能避免;因为存储的元素范围远远大于数组的长度

但可以尽可能的减少冲突,那么如何减少?

  • 数组大小用素数(Java中不太用)

      当 index = hashValue % arr.length 时

  • hash函数(hashCode())要尽可能的均匀
  • 每次插入元素时的冲突率与元素个数 / 数组长度成(负载因子)正比

 (数组长度不变的情况下,元素个数越多越容易冲突;元素个数确定的情况下,数组长度越大越不容易冲突)

   可以通过降低负载因子从而降低冲突率       

  • 我们对冲突率有个上线的阈值,所以我们对负载因子也有个上限阈值
  • 要降低冲突率,需要降低负载因子中,负载因子中,元素个数不能动,所以只能增加数组的长度(扩容)
  • 了解Java中一般负载因子是0.75

如何解决hash冲突? 

  1. 线性探测
  2. 拉链法(Java的hashMap选用这种)

1. 线性探测法解决hash冲突

例:

int[]  arr = new int[11]

存放元素:7,31,5,8,9,6,20,24

解:

hash函数: 元素 % 11;

7 % 11 = 7; --》1次 

31 % 11 = 9; --》1次

5 % 11 = 5;--》1次

8 % 11 = 8; --》1次

9 % 11 = 9;(9 + 1)% 11 = 10; --》2次

6 % 11 = 6; --》1次

20 % 11 = 9;  (20 + 1)% 11 = 10;(21 + 1) % 11 = 0; --》3次

24% 11 =2; --》1次

问题1:对所有在hash表中的元素做查找,其平均比较次数是多少?

答:(1+1+1+1+2+1+3+1)/ 8 =  11/8 

问题2:对所有不在hash表中的元素做查找,其平均比较次数是多少?

答:(2+1+2+1+1+8+7+6+5+4+3)/ 11 = 40 / 11

 2. 拉链法: 使用另一种数据结构来解决冲突的元素

如果不是int类型怎么办? 例如定义一个Person类型的对象存放到hash表

所有的Object类下,都有一个int hashCode() 方法;把一个对象转变为int 对象(不一定是合法下标),所有还需要把int 类型转化为一个合法的下标,即需要模上数组长度--》int index = hashValue % array.length;

        //把对象转化为int类型
        int hashValue = key.hashCode();
        //把hash值转为合法的下标
        int index = hashValue % arr.length;

    class Node {
        Integer key;
        Node next;

        public Node(Integer key) {
            this.key = key;
        }
     }

    //元素类型:Integer
    //使用拉链法解决冲突
    public class MyHashTable {
    //需要一个数组
    private Node[] arr = new Node[11];
    //记录hash表中有的元素个数
    private int size;

    //true:key之前不在hash表中
    //false:key之前在hash表中
    //插入的时间复杂度与链表的长度有关
    public boolean insert(Integer key) {
        //把对象转化为int类型
        int hashValue = key.hashCode();
        //把hash值转为合法的下标
        int index = hashValue % arr.length;
        //遍历index位置处的链表,确定key在不在链表中
        Node cur = arr[index];
        while (cur != null) {
            if (key.equals(cur.key)) {
                return false;
            }
            cur = cur.next;
        }
        //把key装入到节点中,并插入到对应的链表中
        //头插尾插都可以
        Node node = new Node(key);
        node.next = arr[index];
        arr[index] = node;
        //维护元素个数
        size++;
        //通过维护负载因子,进而维护较低的冲突率
        if (size / arr.length * 100 >= 75) { //size / arr.length >= 0.75两边同时×100
            //扩容
            expandTheCapacity();
        }
        return true;
    }

    //时间复杂度O(n)
    private void expandTheCapacity() {
        Node[] newArr = new Node[arr.length * 2]; //2倍
        //复制原来的元素
        //不能直接按链表的搬运,因为元素保存的下标的数组长度有关
        // 数组长度变了,下标也会变,所以需要把每个元素重新计算一次
        for (int i = 0; i < arr.length;i++) {
            //遍历每条链表
            Node cur = arr[i];
            while (cur != null) {
                //复制节点
                Integer key = cur.key;
                int hashValue = key.hashCode();
                int index = hashValue % newArr.length;
                //头插
                Node node = new Node(key);
                node.next = newArr[index];
                newArr[index] = node;

                cur = cur.next;
            }
        }
        arr = newArr;
    }

    //值在并且删除成功返回true
    //值不在返回false
    public boolean remove(Integer key) {
        int hashValue = key.hashCode();
        int index = hashValue % arr.length;
        Node cur = arr[index];
        Node prv = null;
        while (cur != null) {
            if (key.equals(cur.key)) { //找到了该元素
                //开始删除
                if (prv != null) {
                    prv.next = cur.next;
                } else {
                    //cur是链表的头结点
                    arr[index] = cur.next;
                }
                size--;
                return true;
            }
            prv = cur;
            cur = cur.next;
        }
        return false;
    }

    public boolean contains(Integer key){
        int hashValue = key.hashCode();
        int index = hashValue % arr.length;
        Node cur = arr[index];
        while (cur != null) {
            if (key.equals(cur.key)) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }
}

hash表的Java实现:

纯key模型:HashSet

    public static void main(String[] args) {
        Set<Integer> set = new HashSet<>();
        set.add(1);
        set.add(2);
        set.add(3);
        System.out.println(set);
        System.out.println(set.add(1)); //false
        System.out.println(set);
        set.remove(3); //true
        System.out.println(set);
        System.out.println(set.contains(4)); //false
        System.out.println(set.contains(2)); //true
    }

key-value模型:HashMap

    public static void main(String[] args) {
        Map<String,Integer> map = new HashMap<>();
        System.out.println(map.put("ta",1)); //null
        System.out.println(map); //{ta=1}
        System.out.println(map.put("ta",2)); //1
        System.out.println(map.put("ya",22)); //null
        map.put("aa",33);
        System.out.println(map); //{aa=33, ya=22, ta=2}
        for (String k : map.keySet()) {
            System.out.print(k + " "); //aa ya ta
        }
    }

自定义类使用HashSet 和HashMap 的key时,需要注意:

  • 必须重写hashCode() 和 equals() 方法
  • 如果你认为两个对象相等,则hashCode() 值相等,并且equals返回true

       如果p1.equals(p2) 返回true,则最好是 p1.hashCode() == p2.hashCode()

  • 如果 p1.hashCode() == p2.hashCode(),不需要保证p1.equals(p2)返回true,因为可能出现hash冲突

hash表的时间复杂度:插入、删除、查找的平均复杂度为O(1)  泊松分布

如果有意识的构造hash冲突。当链表长度超过8时,不能用链表存储冲突元素,而是用高效的搜索数据结构(Java中选用红黑树(搜索树)),大大减少了查找时间

 

自定义类使用HashSet和HashMap的注意点:需要重写hashCode() 和 equals()!!!
import java.util.Objects;
import java.util.HashMap;
import java.util.HashSet;

class Person {
    String name;
    Integer age;

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(name, person.name) &&
                Objects.equals(age, person.age);
    }

    @Override
    public int hashCode() {
        // 复写为0不影响查找,但是是错误的,所有元素都hash冲突了
        //return 0;
        return Objects.hash(name, age);
    }
}


public class Main {
    public static void main(String[] args) {
        Person p1 = new Person("ya",18);
        Person p2 = new Person("ya",18);

        HashSet<Person> hashSet = new HashSet<>();
        hashSet.add(p1);
        System.out.println(hashSet.contains(p2)); //目前返回false
        //要想返回true,应该如何做?
        //重写hashCode() 和 equals() 方法

    }
    
    public static void main1(String[] args) {
        Person p1 = new Person("ya",18);
        Person p2 = new Person("ya",18);

        HashMap<Person,String> hashMap = new HashMap<>();
        hashMap.put(p1,"ya");
        System.out.println(hashMap.get(p2)); //ya

        HashMap<String,Person> hashMap1 = new HashMap<>();
        hashMap1.put("ya",p1);
        System.out.println(hashMap1.get("ya"));

    }
   
}

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值