[Java DS] Map 和 Set

目录

1.区别概念:

2.Map介绍

1.TreeMap方法总结

2.TreeMap的遍历

3.TreeSet 和TreeMap

1.一些概念:

2.TreeSet 和 TreeMap独有的特性

1.方法(TreeSet)

2.方法(TreeMap)

4.冲突

5.冲突解决

1.闭散列:

2.开散列(哈希桶):

1.HashMap的 get 和 put 过程


1.区别概念:

1、List

Arraylist、LinkedList、Vector

2、Set

TreeSet(有序,唯一):红黑树(自平衡的排序二叉树。)

HashSet(无序,唯一):哈希表或者叫散列集(hash table)

LinkedHashSet:链表和哈希表组成 。 由链表保证元素的排序 , 由哈希表证元素的唯一性

3、Map

TreeMap:红黑树(自平衡的排序二叉树)

HashMap:基于哈希表的Map接口实现(Map结构即映射表存放键值对)

HashTable:哈希表

LinkedHashMap:HashMap 的基础上加上了链表数据结构

DS下专门研究动态数据集查找的数据集结构:平衡搜索树、哈希表(HashMap、HashSet)

java.util.Map(key-value)

2.Map介绍

可以看到,有两个泛型类型:K(key 的类型),V(value 的类型)

V这个类型没有父接口的信息,是一个顶级接口(从语法角度(继承、实现)讲,Map 和 Collection 之间没有关系)

map 中的 key 是不允许重复的

1.TreeMap方法总结

方法解释备注
void clear()清空
boolean isEmpty()判断是否为空
int size()求长度
boolean containsKey(Object key)判断是否包含 key,成本更低
boolean containsValue(Object value)判断是否包含value,成本更高

以搜索树为例containsValue( ... )

全遍历操作
时间复杂度O(n)

V get(Object key)返回 key 对应的 value(查找)如果map 中不包含指定的 key 
返回为 null 
V getOrDefault(Object key, V defaultValue)返回 key 对应的 value

key 不存在,返回

提前写好的 defaultValu

(是不会修改map 的)
如果 key 不在map 中 :

key - value 的插入操作
如果 key 在map 中:

key - value 的替换操作,用新的 value 替换映射关系(更新)

V put(K key, V value)设置 key 对应的 value如果是插入的话:返回null
如果是更新的话:返回 旧的 value
V remove(Object key)删除 key 对应的 k-v 记录返回 null 说明key 不存在
删除成功:返回 key 对应的value
Set<K> keySet()得到所有的 key :Set<K>
Collection<V> values()得到所有的 value:Collection<V>
Set<Map.Entry<K, V>> entrySet()得到所有的 k-v 映射记录:Set<Map.Entry<K,V>>

遍历操作:

map 没有办法直接遍历

1.得到所有的 key :Set<K>

2.得到所有的 value:Collection<V>

3.得到所有的 k-v 映射记录:Set<Map.Entry<K,V>>

对Set、Collection 进行我们熟悉的遍历

为什么key、kv记录用Set,value 用Collection?

Set中的元素是不允许重复的,Map 中的 key 、kv 记录是不会重复的,所以用Set

Map 中的value 是可以重复的,所以用Collection

2.TreeMap的遍历

public static void main(String[] args) {
        Map<Integer,String> 地支 = new TreeMap<>();
        地支.put(3, "寅");
        地支.put(6, "巳");
        地支.put(10, "酉");
        地支.put(11, "戌");
        地支.put(12, "亥");
        地支.put(7, "午");
        地支.put(1, "子");
        地支.put(2, "丑");
        地支.put(8, "未");
        地支.put(9, "申");
        地支.put(4, "卯");
        地支.put(5, "辰");
        

        // 遍历 key
        for (Integer key : 地支.keySet()) {
            System.out.println(key);
        }

        //遍历value
        System.out.println("================================");
        for (String value : 地支.values()) {
            System.out.println(value);
        }


        // 遍历key-value
        System.out.println("================================");
        for (Map.Entry<Integer, String> entry : 地支.entrySet()) {
            Integer key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key + " => " + value);
        }
    }

3.TreeSet 和TreeMap

1.一些概念:

搜索树在Java中表现为红黑树(一种平衡二叉树) 

纯 key :TreeSet 实现Set 接口

key-value:TreeMap 实现Map 接口 

2.TreeSet 和 TreeMap独有的特性

搜索树的结构维护的基础是: key 之间的大小关系

所以要求 key 的类型必须具备比较的能力,要么Comparable、要么Comparator

1.方法(TreeSet)

 first:返回最小的 key(不是第一次插入)

last:返回最大的 key 


 floor:返回小于等于 e 中最大的

ceiling:返回大于等于 e 中最小的


 higher:返回大于 e 中最小的一个

lower:返回小于 e 中最大的一个

 这些方法能够成立,是因为BST的中序遍历是有序的。我们很容易得到关于 key 的顺序信息

2.方法(TreeMap)

 和 TreeSet 同理

文档:

TreeMap (Java Platform SE 8 )

TreeSet (Java Platform SE 8 )

4.冲突

1.冲突多了是不是好事情?

        不好。以衣服放在衣柜里的例子来说,冲突越多意味着这个衣柜中的衣服越多,衣服越多,找起来越慢。实际上,根据经验和计算,总是能把冲突数维持在一个常数上(一般不超过8)

2.冲突不好,能否完全避免?

        理论上可以,但实际上做不到。

        衣柜的数量大于衣服的数量时,就可以做到不冲突(保证每个衣柜中不会出现两件衣服)

        现实中,元素个数远远大于柜子的个数,所以基本做不到完全没冲突。

3.进行冲突避免的几种思路

        1.hash函数得到的结果,尽可能的均匀

        2.通过扩容来实现

        虽然顺序表和哈希表都有扩容机制,但两者扩容触发时机是不同的。顺序表的扩容时机是数组中放不下新的元素了。哈希表的扩容时机并不是哈希表中放不下元素了(理论上哈希表可以放无限)。哈希表的扩容只是因为超过一定阈值,使得冲突率不可接受了。

5.冲突解决

1.闭散列:

“此处不留爷自有留爷处”

元素类型是整型(int)        hash函数:元素%数组长度

元素 193 的位置:index = 193 % 7 = 4 ,放在4 号下标的位置

元素 4 的位置:index = 4% 7 = 4,此时 4 号下标已经有了元素了

冲突的具体解决办法1:一个接一个往后找(带循环)

线性探测:如果对应位置已经有其他元素占用了,则正常向下一个可用位置放置。

因此元素 4 就放到了 5 号下标处。

2.开散列(哈希桶):

编程实践中常用。把所有冲突的 key 组织在一条链表中

数组的元素类型是链表的结点类型(头结点:代表整条链表)

 1.根据 key 找到index   2.根据 index 从array 得到一条链表  3.在链表中查找(由于冲突率不高,所以链表的长度不会很长)

用Map 形式,实现哈希桶,规定 key 的类型是String 类型,value 类型Long类型。

哈希表中,不仅仅可以保存数字类型的 key ,也可以保存其他对象类型的 key ,因此这里选用string类型 从一般对象 -> 整型数字。在String 类型中,相等性比较要用equals。

1.HashMap的 get 和 put 过程

get(查找):

1.利用hashCode() 方法,从对象得到整形数字

        备注1:hashCode() 方法来自Object 类,所以Java 中所有的对象都具有该访问

        备注2:如果自定义 class,要作为HashMap 的 K 类型或者 HashSet 的元素类型

                     必须正确的重写 hashCode() 和 equals() 方法

int n = key.hashCode();

2.从整型数字得到一个合法的数组下标(通过设计,应该让下标的设计,尽可能均匀)

    我们现在采用的是 index = n % array.length; 这个方法没有过多的考虑分布均匀的问题

int index = n % arrat.length;

3.根据下标,可以从 HashMap 的数组中使用O(1)时间复杂度得到一条链表

    Q:哈希表中最外侧的数组,是否可以用链表来代替?

     A: 不行,如果是链表,[index]的操作就不是O(1)时间复杂度      

Node head = array[index];

4.遍历刚刚得到的整条链表,查看结点 key 是否等于要找的 key

        备注:使用 equals 做相等性判断

key.equals(cur.key)

put(插入/更新):

1.利用hashCode() 方法,从对象得到整形数字

        备注1:hashCode() 方法来自Object 类,所以Java 中所有的对象都具有该访问

        备注2:如果自定义 class,要作为HashMap 的 K 类型或者 HashSet 的元素类型

                     必须正确的重写 hashCode() 和 equals() 方法

int n = key.hashCode();

2.从整型数字得到一个合法的数组下标(通过设计,应该让下标的设计,尽可能均匀)

    我们现在采用的是 index = n % array.length; 这个方法没有过多的考虑分布均匀的问题

int index = n % arrat.length;

3.根据下标,可以从 HashMap 的数组中使用O(1)时间复杂度得到一条链表

    Q:哈希表中最外侧的数组,是否可以用链表来代替?

     A: 不行,如果是链表,[index]的操作就不是O(1)时间复杂度      

Node head = array[index];

4.遍历刚刚得到的整条链表,查看结点 key 是否等于要找的 key

        备注:使用 equals 做相等性判断

                    如果找到的话做更新操作

key.equals(cur.key)

5.如果没有找到的话,需要进行插入操作

        5.1 将 key-value 装成链表结点

        5.2将链表结点,插入到 array[index] 对应的链表中(理论上,头插尾插都可以)

6.size/ array.length > 扩容阈值时

        为了降低冲突率,所以进行扩容。不是因为放不下才扩容,扩容的时候,需要把所有 k-v 重新计算,以得到新的下标。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值