Map和Set

前面我们已经介绍了二叉搜索树的一些基本功能,那它和Java类集有什么关系

TreeMap 和 TreeSet 即 java 中利用搜索树实现的 Map 和 Set;实际上用的是红黑树,而红黑树是一棵近似平衡的 二叉搜索树,即在二叉搜索树的基础之上 + 颜色以及红黑树性质验证。

1.概念

Map和Set是一种专门用来进行搜索的容器或者数据结构,其搜索的效率与具体的实例化子类有关。Map和Set是一种适合动态查找的集合容器。经常用于以下一些场景:

1. 根据姓名查询考试成绩 2. 通讯录,即根据姓名查询联系方式 3. 不重复集合,即需要先搜索关键字是否已经在集合中。

2.模型

一般我们把搜索的数据称为关键词key,和关键字对应的称为值value,将其称为key-value的键值对,所以模型有两种:

1.纯key模型,比如:在英文字典中查找一个单词是否存在。

2.key-value模型,比如:统计文件中每个单词出现的次数,统计结果是每个单词都有与其对应的次数:单词,单词出现的次数>。

Map中存储的就是key-value的键值对,Set中只存储了Key

3.Map

Map是一个接口类,没有继承自Collection,该类中储存的是<k,v>结构的键值对,并且k一定是唯一的,不能重复。

3.1关于Map.Entry的说明

Map.Entry 是Map内部实现的用来存放键值对映射关系的内部类,该内部类中主要提供了 的获取,value的设置以及Key的比较方式。

注:Map.Entry<K,Y>并没有提供设置key的方法。

3.2Map的常用方法介绍

相关说明:

1.Map是一个接口,不能直接实例化对象,如果要实例化对象只能实例化其实现类TreeMap或者HashMap。

2. Map中存放键值对的Key是唯一的,value是可以重复的。

3.在TreeMap中插入键值对时,key不能为空,否则就会抛NullPointerException异常,value可以为空。但 是HashMap的key和value都可以为空。

4. Map中的Key可以全部分离出来,存储到Set中来进行访问(因为Key不能重复)。

5. Map中的value可以全部分离出来,存储在Collection的任何一个子集合中(value可能有重复)。

6. Map中键值对的Key不能直接修改,value可以修改,如果要修改key,只能先将该key删除掉,然后再来进行 重新插入。

HashMap的具体实现

public static void main(String[] args) {
        HashMap<String,Integer> map=new HashMap<>();

        //1.如果key不存在,会将key-value的键值对插入到map中,返回null
        map.put("abc",1);
        map.put("asd",1);
        map.put("asd",2);//
        map.put(null,null);//这里用的是HashMap,key和value都可以为空,如果是TreeMap则key不能为空
        System.out.println(map);

   // 2.如果key存在,会使用value替换原来key所对应的value,返回旧value
        System.out.println( map.get("abc"));
        System.out.println(map.get("pooh"));


        System.out.println(map.getOrDefault("abc",1));
        System.out.println(map.getOrDefault("pooh",1));
        System.out.println();

        //打印所有的key,keySet是将map中的key防止在Set中返回
        for(String s:map.keySet()){
            System.out.println(s+" ");
        }
        System.out.println();

        //打印所有的value,values()是将map中的value放在collect的一个集合中返回的
        for(Integer m:map.values()){
            System.out.println(m+" ");
        }
        System.out.println();
        //打印所有的键值对
        for(Map.Entry<String,Integer> entry:map.entrySet()){
            System.out.println("key:"+entry.getKey()+" val:"+entry.getValue());
        }

    }

运行结果

4.Set

4.1常见方法说明

相关说明:

1. Set是继承自Collection的一个接口类。

2. Set中只存储了key,并且要求key一定要唯一。

3. TreeSet的底层是使用Map来实现的,其使用key与Object的一个默认对象作为键值对插入到Map中的。

4. Set最大的功能就是对集合中的元素进行去重。

5. 实现Set接口的常用类有TreeSet和HashSet,还有一个LinkedHashSet,LinkedHashSet是在HashSet的基础 上维护了一个双向链表来记录元素的插入次序。

6. Set中的Key不能修改,如果要修改,先将原来的删除掉,然后再重新插入。

7. TreeSet中不能插入null的key,HashSet可以。

HashSet的具体实现:

 public static void main(String[] args) {
        HashSet<String> set = new HashSet<>();
        //如果key存在,则只出现一次,不存在,则插入
        set.add("abcd");
        set.add("hello");
        set.add("world");
        set.add("world");
        System.out.println(set);
        set.add(null);//HashSet可以插入null
        System.out.println(set);
        System.out.println();

        //key存在,返回true,不存在,返回false
        System.out.println(set.contains("hello"));
        System.out.println(set.contains("hell"));
        System.out.println();

        //key存在,删除成功,返回true,key不存在,删除失败返回false;key为空,返回true(如果是TreeMap,抛出空指针异常)
        System.out.println(set.remove(null));
        System.out.println(set.remove("hello"));
        System.out.println(set.remove("hell"));
        System.out.println();

        Iterator<String> it=set.iterator();
        while(it.hasNext()){
            System.out.println(it.next()+" ");
        }
        System.out.println();

    }

运行结果:

5.哈希表

5.1概念

顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键 码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O( ),搜索的效率取决于搜索过程中 元素的比较次数。

哈希(散列)方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。这种方法构造了一种存储结构,通过某种函数使元素的存储位置与它的关键码之间能够建立一一映射的关系,在查找时通过该函数可以很快 找到该元素。

在该结构中:

  •  插入元素: 根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放。
  • 搜索元素: 对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若 关键码相等,则搜索成功。

举例:例如:数据集合{1,7,6,4,5,9}; 哈希函数设置为:hash(key) = key % capacity; capacity为存储元素底层空间总的大小。

用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快 问题:按照上述哈希方式,向集合中插入元 素44,会出现什么问题?

5.2 冲突

1.概念:对于两个数据元素的关键字Ki 和 Kj(i != j),有 Ki!=Kj ,但有:Hash(Ki ) == Hash(Kj ),即:不同关键字通过相同哈 希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。 把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。

但冲突是必然发生的,我们能做的应该是尽量的降低冲突率。

5.3冲突避免

5.3.1设计合理的哈希函数

引起哈希冲突的其中一个原因是:哈希函数设计的不够合理

1.那哈希函数设计的原则是:

  • 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1 之间
  • 哈希函数计算出来的地址能均匀分布在整个空间中
  • 哈希函数应该比较简单

2.常见哈希函数

1. 直接定制法: 取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B

优点:简单、均匀 缺点:需要事先知道关 键字的分布情况 使用场景:适合查找比较小且连续的情况。

2. 除留余数法:设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数: Hash(key) = key% p(p将关键码转换成哈希地址)。

注意:哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突。

5.3.2.调节负载因子

散列表的载荷因子定义为:a=填入表中的元素个数/散列表的长度

a是散列表装满程度的标志因子。由于表长是定值,与“填入表中的元素个数”成正比,所以,Q越大,表明填入表中的元素越多,产生冲突的可能性就越大:反之,越小,标明填入表中的元素越少,产生冲突的可能性就越小。实际上,散列表的平均查找长度是载荷因子a的函数,只是不同处理冲突的方法有不同的函数。

负载因子与冲突率的关系粗略演示:

所以当冲突率达到一个无法忍受的程度时,我们需要通过降低负载因子来变相的降低冲突率。 已知哈希表中已有的关键字个数是不可变的,那我们能调整的就只有哈希表中的数组的大小。

5.4 冲突解决

5.4.1 闭散列

定义:闭散列也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以 把key存放到冲突位置中的“下一个” 空位置中去。那么如何寻找下一个位置呢?

1.线性探测

定义:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。

基本步骤:通过哈希函数获取待插入元素在哈希表中的位置 如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到 下一个空位置,插入新元素。

比如这个例子,这时我们需要插入元素44,先通过哈希函数计算哈希地址,下标为4,因此44理论上应该插在该 位置,但是该位置已经放了值为4的元素,即发生哈希冲突。那我们就可以采取线性探测的方式来解决。

注:采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他 元素的搜索。比如删除元素4,如果直接删除掉,44查找起来可能会受影响。因此线性探测采用标 记的伪删除法来删除一个元素。

2.二次探测

线性探测存在一定的缺陷,会出现冲突的元素堆积在一块的情况,这与其找下一个空位置有关系,因为找空位置的方式就是挨 着往后逐个去找,因此二次探测为了避免该问题,找下一个空位置的方法为:Hi = ( H+ )% m, 或者: 其中:i = 1,2,3…, 是通过散列函数Hash(x)对元素的关键码 key 进行计算得到的位置, m是表的大小。 对于上面的例子中如果要插入44,产生冲突,使用解决后的情况为:

注:当表的长度为质数且表装载因子a不超过0.5时,新的表项一定能够插入,而且任何一个位置都不 会被探查两次。因此只要表中有一半的空位置,就不会存在表满的问题。在搜索时可以不考虑表装满的情 况,但在插入时必须确保表的装载因子a不超过0.5,如果超出必须考虑增容。 因此:比散列最大的缺陷就是空间利用率比较低,这也是哈希的缺陷。

5.4.2 开散列/哈希桶

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值