数据结构-Java Map 和 Set-1

简介: 数据结构-Java Map 和 Set

前言

Set接口是继承与Collection的,而Map是独立的一个接口。

其中Set的实现类有:

  1. TreeSet
  2. HashSet


Map实现的类有:

  1. HashMap
  2. TreeMap


(HashSet和HashMap底层是一个哈希表,TreeSet和TreeMap底层是一棵搜索树(红黑树))


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


搜索树

定义树:

public class BinarySearchTree {
    static class TreeNode {
        public int val;
        public TreeNode left;
        public TreeNode right;
        public TreeNode(int val) {
            this.val = val;
        }
    }
    public TreeNode root = null;
}


概念

二叉搜索树,也被称为二叉排序树(可能为一棵空树),具备以下性质:

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


二叉搜索树

中序遍历二叉搜索树,得到的结果是一组有序的数据。

查找


如上图,首先假设上图的二叉树已经存在,且根节点为root,要查找的值为val,如果root为空就返回null,定义一个cur节点来遍历这棵树,如果root.val > val,根据二叉搜索树的性质,就去root结点的左子树去找,否则就去右子树找,如果找到了(cur.val == val),就然后cur结点,直到cur == null,就返回空的结点。

/**
    *  查找二叉搜索树val的值
    *  val
    *
    */
    public TreeNode find(int val) {
        TreeNode cur = root;
        while (cur != null) {
            if (cur.val == val) {
                return cur;
            } else if (cur.val < val) {
                cur = cur.right;
            } else {
                cur = cur.left;
            }
        }
        return null;
 
    }


插入

搜索树满足概念中性质a,b,c三点,如果要插入一个val。值首先定义一个cur节点,如果root == null就 new一个 值为val的节点newNode,并将root置为这个节点(root == newNode)

如果root != null,就进行搜索,如果root.val > val,就去左边继续搜索,如果root.val < val就去右边搜索。如果当前cur结点的左和右都为null,则直接比较插入即可

例如在上图中插入6,12 > 6,就去12的左树插入,12的左树的根值为5 < 6,所以去5这个节点的右树插入,其右树跟为9 > 6,又因为val为9这个结点的左和右都为null。所以直接在这个结点的左侧插入。

// 不使用parent结点
public void insert1(int val) {
        if (root == null) {
            root = new TreeNode(val);
            return;
        }
        TreeNode newNode = new TreeNode(val);
        TreeNode cur = root;
        while (cur.left != null && cur.right != null) {
            if (newNode.val > cur.val) {
                cur =cur.right;
            }else {
                cur =cur.left;
            }
        }
        if (newNode.val > cur.val) {
            cur.right = newNode;
        }else {
            cur.left = newNode;
        }
    }

当然也可以使用parent结点来记录cur的上一个结点,如果cur == null的时候,就可以找到cur的上一个结点parent,并在parent这个结点上插入。

public void insert2(int val) {
        if (root == null) {
            root = new TreeNode(val);
            return;
        }
        TreeNode newNode = new TreeNode(val);
        TreeNode cur = root;
        TreeNode parent = null;
        while (cur != null) {
            parent = cur;
            if (cur.val < val) {
                cur =cur.right;
            }else {
                cur = cur.left;
            }
        }
        if (parent.val < val) {
            parent.right = newNode;
        }else {
            parent.left = newNode;
        }
    }

删除

二叉搜索树

如何删除

设cur节点为当前要删除的节点,parent为cur结点的父节点。

  1. 如果 cur.left == null

a.cur == root && root.left == null ,则root = cur.right。


b.cur != root && cur == parent.left ,则 parent.left = cur.right


c.cur != root && cur == parent.right,则 parent.right = cur.right

  1. 如果cur.right == null,则原理同上。
  1. cur == root,则root = root.left
  2. cur != root && cur == parent.left,则 parent.left = cur.left
  3. cur != root && cur == parent.right,则 prent.right = cur.left


图略

  1. cur.left != null && cur.right != null。

某个二叉搜索树

假设有这样一棵二叉搜索树。root为根节点,cur为当前将要被删除的节点。

该如何删除?

需要使用替换法进行删除,即在它的右子树中寻找中序下的第一个结点(值最小),用它的值填补到被

删除节点中,再删除这个最小值节点即可。

首先,你需要找出cur节点和cur的父节点:

实现

public void remove(int val) {
    TreeNode cur = root;
    TreeNode parent = null;
    while(cur != null){
        if (cur.val == val) {
            removeNode(parent, cur);
            break;
        }else if (cur.val > val) {
            parent = cur;
            cur = cur.left;
        }else {
             parent = cur;
            cur = cur.right;   
        }
    }
}


然后需要在cur的右树里面找到其最小值,这个最小值无非就是cur的右子树的第一个结点,或者是右子树上左树的最左的一个端点。找到这个节点之后,将其删除。

然后综上所述的cur.left == null 或者 cur.right == null的情况来完成removeNode这个方法;

private void removeNode(TreeNode parent, TreeNode cur) {
        // 要删除的节点的左子树为空
        if (cur.left == null) {
            if (cur == root) {
                root = cur.right;
            }else if (parent.left == cur) {
                parent.left = cur.right;
            }else {
                parent.right = cur.right;
            }
        // 要删除的节点的右子树为空
        }else if (cur.right == null) {
            if (cur == root) {
                root = cur.left;
            }else if (parent.left == cur) {
                parent.left = cur.left;
            }else {
                parent.right = cur.left;
            }
        }else/* suggests that:cur.left != null && cur.right != null */ {
            TreeNode target = cur.right;
            TreeNode targetParent = cur;
            while (target.left != null) {
                targetParent = target;
                target = target.left;
            }
            cur.val = target.val;
            if (target == targetParent.left) {
                targetParent.left = target.right;
            }else {
                targetParent.right = target.right;
            }
        }
    }


性能分析

无论是插入,还是删除操作,都需要先查找,所以查找的速度代表了二叉搜索树中各个操作的性能。查找是根据节点的值比较来的,所以树越趋向于平衡(完全二叉树或者满二叉树),查找的次数就越趋向于树的深度,否则则会趋向于节点的个数,例如单分支的二叉搜索树:他的搜索时间复杂度为O(n)

完全二叉树

单分支二叉树(右单支)


最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:

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


数据结构-Java Map 和 Set-2

https://developer.aliyun.com/article/1517090

目录
相关文章
|
2月前
|
存储 缓存 JavaScript
Set和Map有什么区别?
Set和Map有什么区别?
254 1
|
3月前
|
存储 JavaScript 前端开发
for...of循环在遍历Set和Map时的注意事项有哪些?
for...of循环在遍历Set和Map时的注意事项有哪些?
259 121
|
5月前
|
前端开发 Java
java实现队列数据结构代码详解
本文详细解析了Java中队列数据结构的实现,包括队列的基本概念、应用场景及代码实现。队列是一种遵循“先进先出”原则的线性结构,支持在队尾插入和队头删除操作。文章介绍了顺序队列与链式队列,并重点分析了循环队列的实现方式以解决溢出问题。通过具体代码示例(如`enqueue`入队和`dequeue`出队),展示了队列的操作逻辑,帮助读者深入理解其工作机制。
164 1
|
6月前
|
编译器 C++ 容器
【c++丨STL】基于红黑树模拟实现set和map(附源码)
本文基于红黑树的实现,模拟了STL中的`set`和`map`容器。通过封装同一棵红黑树并进行适配修改,实现了两种容器的功能。主要步骤包括:1) 修改红黑树节点结构以支持不同数据类型;2) 使用仿函数适配键值比较逻辑;3) 实现双向迭代器支持遍历操作;4) 封装`insert`、`find`等接口,并为`map`实现`operator[]`。最终,通过测试代码验证了功能的正确性。此实现减少了代码冗余,展示了模板与仿函数的强大灵活性。
173 2
|
3月前
|
存储 安全 Java
Java 集合面试题从数据结构到 HashMap 源码剖析详解及长尾考点梳理
本文深入解析Java集合框架,涵盖基础概念、常见集合类型及HashMap的底层数据结构与源码实现。从Collection、Map到Iterator接口,逐一剖析其特性与应用场景。重点解读HashMap在JDK1.7与1.8中的数据结构演变,包括数组+链表+红黑树优化,以及put方法和扩容机制的实现细节。结合订单管理与用户权限管理等实际案例,展示集合框架的应用价值,助你全面掌握相关知识,轻松应对面试与开发需求。
194 3
|
3月前
|
存储 C++ 容器
unordered_set、unordered_multiset、unordered_map、unordered_multimap的介绍及使用
unordered_set是不按特定顺序存储键值的关联式容器,其允许通过键值快速的索引到对应的元素。在unordered_set中,元素的值同时也是唯一地标识它的key。在内部,unordered_set中的元素没有按照任何特定的顺序排序,为了能在常数范围内找到指定的key,unordered_set将相同哈希值的键值放在相同的桶中。unordered_set容器通过key访问单个元素要比set快,但它通常在遍历元素子集的范围迭代方面效率较低。它的迭代器至少是前向迭代器。前向迭代器的特性。
183 0
|
3月前
|
编译器 C++ 容器
用一棵红黑树同时封装出map和set
再完成上面的代码后,我们的底层代码已经完成了,这时候已经是一个底层STL的红黑树了,已经已符合库里面的要求了,这时候我们是需要给他穿上对应的“衣服”,比如穿上set的“衣服”,那么这个穿上set的“衣服”,那么他就符合库里面set的要求了,同样map一样,这时候我们就需要实现set与map了。因此,上层容器map需要向底层红黑树提供一个仿函数,用于获取T当中的键值Key,这样一来,当底层红黑树当中需要比较两个结点的键值时,就可以通过这个仿函数来获取T当中的键值了。我们就可以使用仿函数了。
46 0
|
3月前
|
存储 编译器 容器
set、map、multiset、multimap的介绍及使用以及区别,注意事项
set是按照一定次序存储元素的容器,使用set的迭代器遍历set中的元素,可以得到有序序列。set当中存储元素的value都是唯一的,不可以重复,因此可以使用set进行去重。set默认是升序的,但是其内部默认不是按照大于比较,而是按照小于比较。set中的元素不能被修改,因为set在底层是用二叉搜索树来实现的,若是对二叉搜索树当中某个结点的值进行了修改,那么这棵树将不再是二叉搜索树。
199 0
|
4月前
|
安全 Java API
【Java性能优化】Map.merge()方法:告别繁琐判空,3行代码搞定统计累加!
在日常开发中,我们经常需要对Map中的值进行累加统计。}else{代码冗长,重复调用get()方法需要显式处理null值非原子操作,多线程下不安全今天要介绍的方法,可以让你用一行代码优雅解决所有这些问题!方法的基本用法和优势与传统写法的对比分析多线程安全版本的实现Stream API的终极优化方案底层实现原理和性能优化建议一句话总结是Java 8为我们提供的Map操作利器,能让你的统计代码更简洁、更安全、更高效!// 合并两个列表});简单累加。
436 0