前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【数据结构】Java中Map和Set详解(含二叉搜索树和哈希表)

【数据结构】Java中Map和Set详解(含二叉搜索树和哈希表)

作者头像
小皮侠
发布2024-04-08 20:46:44
1280
发布2024-04-08 20:46:44
举报
文章被收录于专栏:Java学习从基础到就业

Map和Set详解

Map:一种键值对结构,hashMap中键和值均可以为空,hashTable中则不可以存放null值 Set:一种集合,不能存放重复元素,可以理解为与map中的键的集合。 Map set 是一种专门用来进行搜索的容器或者数据结构,其搜索的效率与其具体的实例化子类有关 。 在Java中Map和Set最常见到下面四个实现类,HashMap/TreeMap/HashSet/TreeSet,他们分别与两种数据结构相关,二叉搜索树和哈希表,下面的文章中我会详解这两种数据结构,以及Java是怎样对这两种数据结构进行实现的。

1.二叉搜索树

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

  • 若它的左子树不为空,则左子树上所有节点都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树

如下图所示:

二叉搜索树的模拟实现:

代码语言:javascript
复制
public class BinarySearchTree {
    public static class Node {
        int key;
        Node left;
        Node right;
        public Node(int key) {
            this.key = key;
        }
    }
    private Node root = null;
    /**
     * 在搜索树中查找 key,如果找到,返回 key 所在的结点,否则返回 null
     * @param key
     * @return
     */
    public Node search(int key) {
        Node cur = root;
        while (cur != null) {
            if (key == cur.key) {
                return cur;
            } else if (key < cur.key) {
                cur = cur.left;
            } else {
                cur = cur.right;
            }
        }
        return null;
    }
    /**
     * 插入
     * @param key
     * @return true 表示插入成功, false 表示插入失败
     */
    public boolean insert(int key) {
        if (root == null) {
            root = new Node(key);
            return true;
        }
        Node cur = root;
        Node parent = null;
        while (cur != null) {
            if (key == cur.key) {
                return false;
            } else if (key < cur.key) {
                parent = cur;
                cur = cur.left;
            } else {
                parent = cur;
                cur = cur.right;
            }
        }
        Node node = new Node(key);
        if (key < parent.key) {
            parent.left = node;
        } else {
            parent.right = node;
        }
        return true;
    }
    /**
     * 删除成功返回 true,失败返回 false
     * @param key
     * @return
     */
    public boolean remove(int key) {
        return delete(root,key);
    }
    private boolean delete(Node root,int key){
        /*
根据cur的孩子是否存在分四种情况
1. cur左右孩子均不存在
2. cur只有左孩子
3. cur只有右孩子
4. cur左右孩子均存在
看起来有四种情况,实际情况1可以与情况2或者3进行合并,只需要处理是那种情况即可
除了情况4之外,其他情况可以直接删除
情况4不能直接删除,需要在其子树中找一个替代节点进行删除
*/
        Node cur = root;
        Node parent = null;
        while (cur != null) {
            if (key == cur.key) {
                break;
            } else if (key < cur.key) {
                parent = cur;
                cur = cur.left;
            } else {
                parent = cur;
                cur = cur.right;
            }
        }
// 该元素不在二叉搜索树中
        if(null == cur){
            return false;
        }
//由于二叉查找树的性质,如果将当前节点替换为左子树中最大的或者右子树中最小的一定不会破坏二叉查找树的结构。
        if((cur.left!=null&&cur.right==null)||(cur.left==null&&cur.right==null)){
            cur=cur.left;
        }else if(cur.left==null&&cur.right!=null){
            cur=cur.right;
        }else{
            int val=SearchleftMax(cur.left);
            cur.key=val;
            delete(cur.left,val);
        }
        return true;
    }

    private int SearchleftMax(Node left) {
        if(left==null){
            return 0;
        }
        if(left.right==null){
            return left.key;
        }
        return SearchleftMax(left.right);
    }

}

性能分析:

二叉搜索树的插入和删除操作都离不开查找,所以主要看二叉搜索树的查找效率。

二叉搜索树的查找效率与树的高度有关,结点越深,所需要的比较次数越多。对于同一个数的集合来说,关键码的插入次序不同也会得到不同结构的二叉搜索树。

  1. 最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:log2N
  2. 最差情况下,二叉搜索树退化为单支树,其平均比较次数为:N/2

可以看到如果退化为单支树,性能就会下降非常多。为了保证二叉搜索树的效率,大佬们还发明AVL树和红黑树,来限制树的高度尽量趋近于完全二叉树。

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

2.Map常见方法

在Java中Map和Set是两个接口,我们可以利用他们选择任意一个实现类。

map的常见方法:

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

方法

解释

V get (Object key)

返回 key 对应的 value

V getOrDefault (Object key, V defaultValue)

返回 key 对应的 value , key 不存在,返回默认值

V put (K key, V value)

设置 key 对应的 value

V remove (Object key)

删除 key 对应的映射关系

Set<K> keySet ()

返回所有 key 的不重复集合

Collection<V> values ()

返回所有 value 的可重复集合

Set<Map.Entry<K, V>> entrySet ()

返回所有的 key-value 映射关系

boolean containsKey (Object key)

判断是否包含 key

boolean containsValue (Object value)

判断是否包含value

  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删除掉,然后再来进行重新插入。

3.Set常见方法

Set 与 Map 主要的不同有两点: Set 是继承自 Collection 的接口类, Set 中只存储了 Key 。

方法

解释

boolean add (E e)

添加元素,但重复元素不会被添加成功

void clear ()

清空集合

boolean contains (Object o)

判断 o 是否在集合中

Iterator<E> iterator ()

返回迭代器

boolean remove (Object o)

删除集合中的 o

int size()

返回 set 中元素的个数

boolean isEmpty()

检测 set 是否为空,空返回 true ,否则返回 false

Object[] toArray()

将 set 中的元素转换为数组返回

boolean containsAll(Collection<?> c)

集合 c 中的元素是否在 set 中全部存在,是返回 true ,否则返回 false

boolean addAll(Collection<? extends E> c)

将集合 c 中的元素添加到 set 中,可以达到去重的效果

  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可以。

4.哈希表

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

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

当向该结构中:

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

该方式即为哈希 ( 散列 ) 方法, 哈希方法中使用的转换函数称为哈希 ( 散列 ) 函数,构造出来的结构称为哈希表 (Hash Table)( 或者称散列表 )

例如:数据集合 {1 , 7 , 6 , 4 , 5 , 9} ;

哈希函数设置为: hash(key) = key % capacity ; capacity 为存储元素底层空间总的大小。

冲突的诞生:

对于两个数据元素的关键字ki 和kj (i != j) ,有 i != j ,但有: Hash(ki) == Hash(kj) ,即: 不同关键字通过相同哈 希函数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞

关于如何解决哈希冲突我将会在下篇文章进行分享,欢迎大家关注我的博客在文章发布的第一时间来捧场呀!!! 主页已更新完Java基础内容,数据结构基础, 正在更新算法篇,数据库篇, 未来会更新Java项目,SpringBoot,Redis以及各种Java路线会用到的技术。 求点赞!求收藏!求评论!求关注! 谢谢大家!!!!!!!!!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-03-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Map和Set详解
    • 1.二叉搜索树
      • 2.Map常见方法
        • 3.Set常见方法
          • 4.哈希表
          相关产品与服务
          云数据库 Redis®
          腾讯云数据库 Redis®(TencentDB for Redis®)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档