当前位置: 首页 > news >正文

[数据结构] Map和Set

目录

1. 搜索树

1.1 概念

1.2 查找

1.3 插入

1.4 删除

2. 搜索

2.1 搜索的概念

2.2 模型

2.2.1 Key模型

2.2.2 Key- Value模型

3. Map的使用

3.1 Map的介绍

3.2 Map.Entry的介绍

3.3 Map常用方法介绍

4. Set的使用

4.1 Set的介绍

4.2 Set常用方法介绍

5. 哈希表

5.1 概念

5.2 冲突的概念

5.3  冲突的避免

5.4 负载因子调节

5.5 解决冲突

5.6 代码实现

5.7 哈希表的底层源码分析

5.8 相关习题


1. 搜索树

1.1 概念

二叉搜索树又叫做二叉排序树,可以是一棵空树,也可以是一颗具有以下性质的树。

二叉搜索树的头结点的左边结点的值都比根结点的值小,右边结点的值都比根节点大,并且左边结点和右边结点各自组成的树依然是二叉搜索树。

下面就是一棵二叉搜索树:

1.2 查找

由于根结点左边的数比它小,右边的数比它大,所以查找的时候需要比较查找元素和根结点的大小,注意根节点是否为空。

代码如下:

public class BinarySearchTree {static class Node {public Node left;public int val;public Node right;public Node(int val) {this.val = val;}}//头结点public Node root;//查找public boolean searchNode(int key) {if(root == null) {return false;}Node tmpRoot = root;while(tmpRoot != null) {if(key > tmpRoot.val) {tmpRoot = tmpRoot.right;}else if(key < tmpRoot.val) {tmpRoot = tmpRoot.left;}else {return true;}}return false;}
}

1.3 插入

如果树是空树,直接头结点等于插入的元素,如果不是空树,则从头结点开始比较,将新的结点插入到指定的位置。

代码如下:

    //插入public void insertNode(int key) {if(root == null) {root = new Node(key);return;}Node tmpRoot = root;Node parent = null;while(tmpRoot != null) {if(key > tmpRoot.val) {parent = tmpRoot;tmpRoot = tmpRoot.right;}else if(key < tmpRoot.val) {parent = tmpRoot;tmpRoot = tmpRoot.left;}else {return;}}Node tmp = new Node(key);if(parent.val > key) {parent.left = tmp;}if(parent.val < key) {parent.right = tmp;}}

1.4 删除

删除结点的话需要分情况,

当删除结点的左边为空时,但删除结点的右边为空时

但删除结点时左边和右边都不为空时,此时采用替换法,将删除结点的右边树中的最小结点跟删除结点的值进行交换,再删除最小结点位置的删除结点的值。

代码如下:

    //删除public void remove(int key) {Node parent = null;Node newRoot = root;while(newRoot != null) {if(key > newRoot.val) {parent = newRoot;newRoot = newRoot.right;}else if(key < newRoot.val) {parent = newRoot;newRoot = newRoot.left;}else {//删除removeNode(newRoot,parent);}}}/*** 删除结点的具体实现* @param cur 被删除的结点* @param parent 被删除结点的父亲结点*/private void removeNode(Node cur, Node parent) {//删除的结点左边没有结点if(cur.left == null) {if(cur == root) {root = root.right;}else if(cur == parent.right) {parent.right = cur.right;}else {parent.left = cur.right;}}else if(cur.right == null) {if(cur == root) {root = root.left;}else if(cur == parent.right) {parent.right = cur.left;}else {parent.left = cur.left;}}else {//删除结点的左右两边都不为空Node tmpParent = cur;Node tmp = cur.right;while(tmp.left != null) {tmpParent = tmp;tmp = tmp.left;}//此时找到右边树的最小值的结点cur.val = tmp.val;if(tmp == tmpParent.right) {tmpParent.right = tmp.right;}else {tmpParent.left = tmp.right;}}}

2. 搜索

2.1 搜索的概念

Map和set是一种专门用来进行搜索的容器或者数据结构,其搜索的效率与其具体的实例化子类有关。

TreeMap和TreeSet是利用二叉搜索树实现的Map和Set的,实际上用的是红黑树,在二叉搜索树的基础上加上颜色以及红黑树的性质实现的树。

而我们见过的查找方式有

直接遍历元素查找,或者利用二分查找(有序)等这些查找方式属于静态查找,不能满足生活中的一些查找,所以这里就有了动态查找Map和Set两个容器来进行查找。

2.2 模型

一般我们把搜索的关键字称为key,而关键字对应的元素称为value。key-value称为键值对。

2.2.1 Key模型

一般用来查找这个教室里有没有叫张三的学生这类问题。

而Set里面只存储Key数据。

2.2.2 Key- Value模型

一般用来查找学号关键字对应的学生姓名等这类问题。

而Map里面存储Key和Value的值。

3. Map的使用

3.1 Map的介绍

Map里面存储的是<key,value>结构的键值对,并且key的值具有唯一性。

3.2 Map.Entry<K,V>的介绍

Map.Entry<K,V>是Map类内部实现的用来存放<Key,Value>键值对的内部类,该类主要提供了下面的方法:

K getKey()   获取entry里面的key

V getValue()  获取entry里面的value

V setValue(V value)   修改键值对中Value的值

但是其中不包含设置Key的值的方法。

3.3 Map常用方法介绍

注意事项:

  1. Map是一个接口,实例化对象只能实例化其实现的类TreeMap或者HashMap。
  2. Map里面存放的Key是唯一的,value可以重复。
  3. 在TreeMap中插入键值对时,key不能为空,否则会出现NullPointerException异常,value可以为空,而在HashMap中key和value都可以为空。
  4. Map中的key可以存储到Set中来进行访问。
  5. Map中的Value可以存放到Collection中的任何一个子集合中。
  6. Map中key的值不能修改,而value的值可以修改。

4. Set的使用

4.1 Set的介绍

Set是继承Collection的一个接口,继承它的子类有TreeSet和HashSet,而Set里面只存储Key的值。

4.2 Set常用方法介绍

注意事项:

  1. Set是继承Collection的一个接口。
  2. Set中只存储key,并且要求key的值要唯一。
  3. TreeSet的底层是用Map来实现的,TreeSet的key与Object的一个默认对象组成键值对插入Map中。
  4. Set最大的功能就是对集合中的元素进行去重。
  5. 实现Set接口的常用类有TreeSet和HashSet,还有一个LInkedHashSet,是在HashSet的基础上维护了一个双向链表来记录元素的插入次序的。
  6. Set众多的Key不能修改。
  7. TreeSet中不能插入null的key,HashSet可以。

5. 哈希表

5.1 概念

在顺序结构中我们查找元素,需要遍历一遍将两个元素之间比较。时间复杂度为元素数量,在树形结构中,我们查找元素时,需要从根节点遍历树,时间复杂度为树的高度。

有没有一种方法能够不用比较,直接找到想要的元素,这就是哈希方法,哈希方法将元素的存储位置和元素之间建立了某种联系。

但在哈希表中插入元素时,输入元素,利用函数计算该元素存储位置。

在哈希表中取元素时,利用函数找到存储位置,返回元素的值。

哈希表又叫做散列表,而哈希方法中使用的函数称为哈希函数。

5.2 冲突的概念

当两个元素在通过哈希函数计算出来的存储坐标相同时,称为哈希冲突。

5.3  冲突的避免

我们可以通过设计哈希函数来让元素尽可能的分散并且不冲突的分布。

常见的哈希函数有:

直接定制法:

这种方法是根据给定的数的值,来创建的哈希函数比如:hash(key) = A*key + B。这种方法比较简单,但是只适用于知道哈希表中的数据。‘

除留余数法:

这种方法是挑选一个接近于哈希表的存储容量m的数,然后利用哈希函数Hash(key) = key % m 来获取该数存储的地址。

5.4 负载因子调节

哈希表的负载因子a = 存储到哈希表中的元素数 / 哈希表的大小。

负载因子越大,元素越容易发生冲突,而在Java中负载因子尽量控制在0.75以内,我们可以通过降低负载因子,来降低冲突率,我们可以扩大哈希表的大小。

5.5 解决冲突

解决冲突有两种方法:闭散列开散列

闭散列:

闭散列也叫做开放地址法,当发生哈希冲突时,当哈希表中还有空位,我们可以将元素放到冲突位置的下一个空位置上。

如何查找下一个空位置呢?

方法一:线性探测法

从冲突位置开始向后面找到第一个空位置放元素,但是在删除元素时,采用的伪删除的办法,方便查找。但是线性探测容易导致冲突元素都聚集在一起。

方法二:二次探测

二次探测法找空位置的方法是利用公式 (冲突的位置 + i^2 ) / 哈希表大小。(i =1, 2,3....),这样可以减少冲突元素的聚集。

开散列:

我们可以将冲突的元素放到一个链表里面,然后链表的头结点存储到哈希表里面。

5.6 代码实现

public class HashBucket {static class Node {public int key;public int val;public Node next;public Node(int key, int val) {this.key = key;this.val = val;}}public Node[] array;public int useSize;public double load_factor = 0.75;public HashBucket() {array = new Node[10];}//放元素public void put(int key, int val) {Node newNode = new Node(key,val);int a = key % array.length;//判断是否有相同的keyNode cul = array[a];while(cul != null) {if(cul.key == key) {cul.val = val;return;}cul = cul.next;}//头插法newNode.next = array[a];array[a] = newNode;useSize++;//检查负载因子if((useSize * 1.0/array.length) > load_factor) {//扩容grow();}}private void grow() {Node[] newArray = new Node[2 * array.length];for (int i = 0; i < array.length; i++) {Node cur = array[i];while(cur != null) {int newIndex = cur.key % newArray.length;//头插法Node curNext = cur.next;cur.next = newArray[newIndex];newArray[newIndex] = cur;cur = curNext;}}this.array = newArray;}//获取元素public int get(int key) {int index = key % array.length;Node cul = array[index];while(cul != null) {if(cul.key == key) {return cul.val;}cul = cul.next;}return -1;}
}

5.7 哈希表的底层源码分析

HashMap存在三个构造方法:

第一个构造方法有两个参数,第一个参数是哈希表的容量,第二个参数是负载因子。

第二个构造方只有一个参数可以设置哈希表的大小。

第三个参数没有参数。

而在底层代码中:

  • HashMap和HashSet在Java中是用哈希表实现的Map和Set。
  • 在Java中利用哈希桶解决哈希冲突。
  • 当冲突的链条达到一定的值时,会将链表转化为红黑树。
  • 在Java中计算哈希值实际上是利用类里面的hashCode方法,在进行key值得比较时利用的equals方法的。因此在我们进行自定义类型存储时,需要重写hashCode和equals方法才能进行。重写的方法要满足equals相等时,hashCode也要相等。
  • 哈希桶的默认长度是16,如果哈希桶的长度大于等于64并且链表长度存在大于等于8得,哈希桶就要转换为红黑树。

5.8 相关习题

只出现一次的数字

这道题最快的解法是利用异或来解决,下面是我利用了HashSet来解决,利用了HashSet的存在元素的单一性。

代码如下:

   public int singleNumber(int[] nums) {Set<Integer> set = new HashSet<>();for(int i = 0; i < nums.length; i++) {if(!set.contains(nums[i])) {set.add(nums[i]);}else {set.remove(nums[i]);}} Object[] arr = set.toArray();return (int)arr[0];}

随机链表的复制

这道题利用的Map接口,将原来链表的每个节点的地址和新节点的每个节点的地址形成k-v结构存储起来,就能实现复制了。

代码如下:

    public Node copyRandomList(Node head) {Map<Node,Node> map = new HashMap<>();Node cur = head;while(cur != null) {Node tmp = new Node(cur.val);map.put(cur,tmp);cur = cur.next;}cur = head;while(cur != null) {map.get(cur).next = map.get(cur.next);map.get(cur).random = map.get(cur.random);cur = cur.next;}return map.get(head);}

宝石与石头

这题是利用Set的唯一性来解决的,将真宝石的字符存储起来,再遍历自己拥有的石头有多少真宝石。

代码如下:

    public int numJewelsInStones(String jewels, String stones) {Set<Character> set = new HashSet<>();int count = 0;for(int i = 0; i < jewels.length(); i++) {set.add(jewels.charAt(i));}for(int i = 0; i < stones.length(); i++) {if(set.contains(stones.charAt(i))) {count++;}}return count;}

坏的键盘打字

这里先利用一个Set来存储好的键位,再用一个Set来存储已经打印的坏的键位。再遍历目标打印的字符串,都不在两个Set里面的字符打印。

代码实现:

public class Main {public static void main(String[] args) {Scanner in = new Scanner(System.in);String s1 = in.next();String s2 = in.next();find(s1,s2);}public static void find(String s1,String s2) {Set<Character> set1 = new HashSet<>();for(char c : s2.toUpperCase().toCharArray()) {set1.add(c);}Set<Character> set2 = new HashSet<>();for(char c : s1.toUpperCase().toCharArray()) {if(!set1.contains(c) && !set2.contains(c)) {System.out.print(c);set2.add(c);}}}
}

http://www.dtcms.com/a/391292.html

相关文章:

  • [Go类库分享]Go template模版库
  • 辅助搜题系统-基于模糊搜索,上传word题库后,可搜索答案
  • 【完整源码+数据集+部署教程】遥感农田森林岩石图像分割系统: yolov8-seg-C2f-DCNV2
  • RTX 4090助力深度学习:从PyTorch到生产环境的完整实践指南
  • AWS中国云中的调用链监控(EC2版)
  • CI/CD到底是什么?
  • 3dmax三维动画渲染很慢怎么办?
  • ASIS CTF 2025 SatoNote
  • BasicForm的使用
  • CSP初赛——STL中的函数整理
  • 小杰机器学习高级(two)——极大似然估计、交叉熵损失函数
  • 关于px4 1.15.0电机控制有效矩阵的更新
  • 【设计模式】职责链模式
  • 22届考研(华为oD)-Java面经
  • 轻松实践:用Python实现“名字大作战”游戏,表白Zulu
  • EasyDSS视频直播点播平台如何为游戏直播提供超强技术底座?
  • MySQL----MVCC机制
  • 设计|str增量法|计算贡献
  • Spring中Controller层中容易搞混的注解
  • Git GitHub 个人账户创建教程
  • Python学习系统计划:从零到精通的科学路径
  • 解锁 JavaScript 的数学魔法:深入探索 Math 对象
  • dcm4che系列主要开源项目概述
  • 枚举深入解析
  • Qt中delete与deleteLater()的使用
  • AD5621(单通道缓冲电压输出DAC)芯片的详细用法
  • vLLM的面试题
  • 最优控制3 -- 动态规划-一个解析解的例子
  • 深入分析大众点评 Ajax 接口:直接请求 JSON 数据高效获取评论
  • 京东零售张科:DataAI Infra会成为驱动未来的技术基石