HashMap和HashSet

文章详细介绍了Java中的Map和HashSet数据结构,它们用于动态查找,如添加、删除和定位。HashMap是常用的动态查找数据类型,提供O(1)的访问速度。文章还讨论了哈希冲突及其解决方法,包括线性探测和二次探测,并提到了开散列的实现。此外,给出了HashMap和HashSet的使用示例,以及如何解决特定问题,如查找高频单词和唯一数字。
摘要由CSDN通过智能技术生成

Map和Set是专门用来进行搜索的接口,不能直接实例化对象,可以通过HashMap实例化对象,效率与其具体的实例化子类有关。相对直接遍历或二分查找来说,Map和Set更适合动态查找,如添加,删除,和定位通讯录的内容,使用HashMap可以有很快的访问速度O(1)。HashMap是Java程序员使用频率最高的用于动态查找的数据类型,它存储的是键值对(key-value)映射。相同的是,HashMap和HashSet都是无序的,不会记录添加的顺序,允许有null值(HashMap只允许有一条键值为null),并且线程不安全。不同的是,HashMap继承于AbstractMap,实现了Map,Cloneable,Serializable接口,根据key的HashCode值存储数据。HashSet实现了Set接口, 内部只存储了Key,是不允许有重复元素的集合。

哈希表

 

哈希冲突

在哈希表中,如果有两个元素的哈希地址相同,称为哈希冲突。常见的解决哈希冲突的方法有:闭散列和开散列。

1. 闭散列

又称“开放定址法”,当发生哈希冲突时,哈希表未被装满,可以把key存放到冲突位置的下一个空位中,有以下两种方法可以寻找到“下一个”位置。

1.1 线性探测
最简单的方法叫做线性探测。发生冲突时,在散列索引之后的索引序列中寻找另一个位置。

//哈希函数:
Hash(key)=key%capacity(数据的长度)

hash(obj) + 1
          + 2
          + 3
          + ... 
          

1.2 二次探测

线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题,其中在冲突时探索以下备选位置序列: 

//二次探测
    Hi = (H0 + i^2)% m, 或者: 
    Hi = (H0 - i^2)% m。
其中:i = 1,2,3…,是通过散列函数Hash(x)对元素的关键码 key 进行计算得到的位置, m是表的大小。

hash(obj) + 1 
          + 4  = 22
          + 9  = 32
          + 16 = 42
          + ... 

在合理的情况下,我们是否真的能够用这种方法找到一个开放的插槽。一个相对简单的数论结果可以保证,如果容量为素数且负载不超过0.5,则探测序列可以保证成功。闭散列最大的缺陷就是空间利用率比较低,这也是哈希的缺陷。 

2. 开散列

又称“链地址法(开链法)”,首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。

                                                               

 

 从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素。开散列可以认为是把一个在大集合中的搜索问题转化为在小集合中做搜索了。

如果冲突严重,就意味着小集合的搜索性能其实也时不佳的,这个时候我们就可以将这个所谓的小集合搜索问题继续进行转化,例如:
1. 每个桶的背后是另一个哈希表
2. 每个桶的背后是一棵搜索树

3.进行扩容,降低负载因子,降低冲突

 实现:

private static class Node{
        private int key;
        private int value;
        Node next;
        public Node(int key, int value){
           this.key=key;
           this.value=value;
        }
        private Node[] array;
        private int size;
        private static final double LOAD_FACTOR=0.75;
        public int put(int key,int value){
            int index=key% array.length;
            //在链表中查找key所在的节点
            for(Node cur=array[index];cur!=null;cur=cur.next){
                if(key==cur.key){//找到key所在的节点,更新
                    int oldValue=value;
                    cur.value=value;
                    return oldValue;
                }
            }
            //所有节点都不是key,插入一个新的节点
           Node node=new Node(key,value);
            node.next=array[index];
            array[index]=node;
            size++;

            if(loadFactor()>=LOAD_FACTOR){
                resize();
            }
            return -1;
        }
        private void resize(){//扩容
            Node[] newArray=new Node[array.length*2];
            for(int i=0;i<array.length;i++){
                Node next;
                for(Node cur=array[i];cur!=null;cur=cur.next){
                    next=cur.next;
                    int index=cur.key % newArray.length;
                    cur.next=newArray[index];
                    newArray[index]=cur;
                }
            }
            array=newArray;
        }
        private double loadFactor(){//负载因子
            return size*1.0/ array.length;
        }
        public HashBucket(){
            array=new Node[8];
            size=0;
        }
        public int get(int key){//查找
            int index=key % array.length;
            Node head=array[index];
            for(Node cur=head;cur!=null;cur=cur.next){
                if(key==cur.key){
                    return cur.value;
                }
            }
            return -1;
        }
    }

HashMap 例题---前k个高频单词

题目要求:

给定一个单词列表 words 和一个整数 k ,返回前 k 个出现次数最多的单词。

返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率, 按字典顺序 排序。

class Solution {
    public List<String> topKFrequent(String[] words, int k) {
     Map<String,Integer> map=new HashMap<>();
     for(String s: words){//统计每个单词出现的次数
         if(map.get(s)==null){
             map.put(s,1);
         }else{
             int val=map.get(s);
             map.put(s,val+1);//如果已添加过
         }
     }

     //创建小根优先队列,构建比较规则
     PriorityQueue<Map.Entry<String,Integer>> minHeap = new PriorityQueue<>(new Comparator<Map.Entry<String, Integer>>() {
            @Override
            public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
                //在出现频率相同的情况下,根据字母表顺序,变成大根堆
                if(o1.getValue().compareTo(o2.getValue()) == 0) {
                    return o2.getKey().compareTo(o1.getKey());
                }
                //出现频率不同的时候,按照频率从大到小排序
                return o1.getValue().compareTo(o2.getValue());
            }
        });

    //遍历HashMap,依次向堆加入元素
    for(Map.Entry<String,Integer> entry: map.entrySet()){
        if(minHeap.size()<k){//如果不满k个元素,继续插入
            minHeap.offer(entry);
        }else{
            Map.Entry<String,Integer> top=minHeap.peek();
            //频率一样的情况下,判断key值,key小的入
            if(top.getValue().compareTo(entry.getValue())==0){
                if(top.getKey().compareTo(entry.getKey())>0){
                    minHeap.poll();
                    minHeap.offer(entry);
                }
            }else{
                if(top.getValue().compareTo(entry.getValue())<0){
                    minHeap.poll();
                    minHeap.offer(entry);
                }
            }
        }
    }

    //小根堆顶部弹出元素为从小到大的,需要反转集合
    List<String> ret=new ArrayList<>();
    for(int i=0;i<k;i++){
        String s=minHeap.poll().getKey();
        ret.add(s);
    }
    Collections.reverse(ret);
    return ret;
}
}

HashSet 例题---只出现一次的数字

题目要求:

给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

class Solution {
    public int singleNumber(int[] nums) {
      Set<Integer> set=new HashSet<>();
      //元素放入集合中,最后剩下只出现一次的元素
      for(int x: nums){
          if(!set.contains(x)){
              set.add(x);
          }else{
              set.remove(x);
          }
      }
      //返回集合当中的这个元素
      for(int x:set){
          if(set.contains(x)){
              return x;
          }
      }
       return -1;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值