【数据结构】Map 和 Set

news2024/11/17 0:37:10

目录

  • 二叉搜索树
    • 二叉搜索树---查找
    • 二叉搜索树---插入
    • 二叉搜索树---删除
  • Map和Set
    • Map的使用
    • Set的使用
  • 哈希表
    • 哈希冲突
    • 冲突避免
    • 冲突解决
      • 冲突解决---闭散列
      • 冲突解决---开散列
  • 题目练习
    • 只出现一次的数
    • 复制带随机指针的链表
    • 宝石与石头
    • 旧键盘

二叉搜索树

二叉搜索树也叫二叉排序树,具有以下性质:

  • 若它的左子树不为空,则左子树的所有结点的值都小于根结点的值。
  • 若它的右子树不为空,则右子树的所有结点的值都大于根结点的值。
  • 左右子树都是二叉搜索树。

例如:
搜索树

二叉搜索树—查找

二叉搜索树和普通二叉树的结构都一样,只是结点的值不太一样。

//二叉搜索树结构
static class TreeNode {
    public int val;
    public TreeNode left;
    public TreeNode right;

    public TreeNode(int val) {
        this.val = val;
    }
}

public TreeNode root = null;

如何在二叉搜索树中查找某一结点值?我们可以根据二叉搜索树的性质,如果要查找的值大于根结点的值就继续往右边查找,如果要查找的值小于根结点的值就继续往左边查找。
查找过程
查找代码:

public TreeNode find(int val) {
    TreeNode cur = root;
    while (cur != null) {
    	//大于往右边
        if (val > cur.val) {
            cur = cur.right;
        } else if (val < cur.val) {
        //小于往左边
            cur = cur.left;
        } else
            return cur;
    }
    return null;
}

二叉搜索树—插入

插入
插入代码:

public boolean insert(int val) {
    TreeNode node = new TreeNode(val);
    //空树
    if (root == null) {
        root = node;
        return true;
    }
    TreeNode cur = root;
    TreeNode parent = null;
    while (cur != null) {
        if (val == cur.val) {
            return false;
        } else if (val < cur.val) {
            parent = cur;
            cur = cur.left;
        } else {
            parent = cur;
            cur = cur.right;
        }
    }
    //cur为空了,小于放左边,大于放右边
    if (val < parent.val) {
        parent.left = node;
    } else {
        parent.right = node;
    }
    return true;
}

二叉搜索树—删除

假设待删除结点为 cur,待删除结点的双亲结点为 parent,那么有下面几种情况:

  1. cur.left 为空
    cur的左为空

  2. cur.right 为空
    cur的右为空

  3. cur.left 不为空 并且 cur.right 不为空
    左右不为空

删除代码:

public void remove(int val) {
    TreeNode cur = root;
    TreeNode parent = null;
    //循环遍历找到要删除的结点
    while (cur != null) {
        if (cur.val == val) {
            //找到后,判断当前节点的情况
            removeNode(parent, cur);
            return;
        } else if (cur.val < val) {
            parent = cur;
            cur = cur.right;
        } else {
            parent = cur;
            cur = cur.left;
        }
    }
}

//根据结点情况删除结点
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 {
        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;
        }
    }
}

Map和Set

map 和 set 是一种专门用来进行搜索的容器或者数据结构,属于动态查找,也就是在查找时可以进行插入和删除操作。

一般把搜索的数据称为关键字(Key),和 Key 对应的称为值(Value),将其称为 Key-value 的键值对。分为以下两种:

  1. 纯 key 模型(Set): 例如:某单词是否在一句话中。
  2. Key-Value 模型(Map): 例如:某单词出现的次数 <单词,出现次数>。

Map的使用

put(K key, V value),设置 key 对应的 value

put方法

使用 put 方法时,插入的 key 一定不能是 null,否则会空指针异常,但是 value 可以。

key不能为null

插入的 key 一定是可以比较的,否则会报错。

如果 key 相同,则会替换原来的 value。

会替换原来的value

get(Object key) 返回 key 对应的 value
getOrDefault(Object key, V defaultValue) 返回 key 对应的 value,key 不存在,返回默认值

get 和 getOrdefalut

remove(Object key) 删除 key 对应的映射关系

remove方法

Set<K> keySet() 返回所有 key 的不重复集合
Collection<V> values() 返回所有 value 的可重复集合

KeySet 和 values方法

Set<Map.Entry<K, V>> entrySet() 返回所有的 key-value 映射关系

在这里插入图片描述

containsKey(Object key) 判断是否包含 key
containsValue(Object value) 判断是否包含 value

containskey和value

Set的使用

set方法使用
set方法使用

哈希表

通过某种函数使元素的存储位置与它的关键码之间建立映射关系的一种存储结构叫做哈希表(散列表)。

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

例如:
按照哈希函数插入

哈希冲突

假设现在要插入 23,就会出现冲突:
哈希冲突

不同关键字通过相同的哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞

注意: 哈希表底层数组的容量往往小于实际要存储的数量,这就导致一个问题,冲突的发生是必然的,我们只能尽量的低冲突。


冲突避免

设计合理的哈希函数可以减小冲突的概率。哈希函数设计原则:

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

常见哈希函数:

  1. 直接定制法
    取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B
    优点:简单、均匀
    缺点:需要事先知道关键字的分布情况
    使用场景:适合查找比较小且连续的情况

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

还有几个不常用的:平方取中法、折叠法、随机数法、数学分析法。


负载因子调节: α(负载因子) = 元素个数 / 表的长度
负载因子越大,产生冲突的可能性就越大,所以要想减小负载因子,就要增大表的长度(扩容)。


冲突解决

解决哈希冲突两种常见的方法是:闭散列和开散列

冲突解决—闭散列

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

寻找下一个空位置:

  1. 线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。
    线性探测
    如果再插入 33,那几个数据就会在一块,那就需要其他方法解决这个问题。

  2. 二次探测
    线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题,找下一个空位置的方法为:H i i i= ( H 0 0 0+ i i i2)% m,i 等于1,2,3……。

二次探测

冲突解决—开散列

开散列法又叫链地址法(开链法),同样先计算出散列地址,把相同地址的关键码放于同一子集合,每一个子集合称为一个,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。类似下面这种:

冲突解决---开散列
开散列中每个桶中放的都是发生哈希冲突的元素,开散列就是把一个在大集合中的搜索问题转化为在小集合中做搜索了。我们还可以继续优化在小集合中搜索,例如:每个桶的背后是另一个哈希表或者每个桶的背后是一棵搜索树


题目练习

只出现一次的数

题目链接:【leetcode】136. 只出现一次的数字
题目描述:题目描述

这题我们利用 set 的性质来做,使用异或(将数组里的元素全部异或,最后结果就是只出现一次的数字)或者 map(统计每个元素出现的次数,value 为1的就是只出现一次的数字)也可以。

题解过程
代码:

class Solution {
    public int singleNumber(int[] nums) {
        Set<Integer> set = new HashSet<>();
        for(int x : nums){
        //利用add函数的返回值,返回false,
        //说明set中已经存在这个元素,那我们就移除
            if(!set.add(x)){
                set.remove(x);
            }
        }
		//最后 set 中就剩下只出现一次的数字
        for (Integer x : set) {
            return x;
        }
        return -1;
    }
}

复制带随机指针的链表

题目链接:【leetcode】138. 复制带随机指针的链表
题目描述:题目描述

题目的意思就是,我们需要根据题目给的链表,生成同等个数并且结点的 val 相等,所给链表的 next 和 random 域指向链表中的哪个结点,我们就需要在自己生成的链表中指向位置相对应的结点。

题解

为了达到题目要求,我们可以使用 map 来存储他们对应位置的结点。

过程

代码:

public Node copyRandomList(Node head) {
    Map<Node, Node> map = new HashMap<>();
    Node cur = head;
    //遍历链表
    while (cur != null) {
        //生成和题目的链表节点 相同值 的结点
        Node node = new Node(cur.val);
        //再把题目中链表的结点  和  生成的新结点存储到 map中
        map.put(cur, node);
        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;
    }
    //新链表的头结点对应老链表头结点的value
    return map.get(head);
}

宝石与石头

题目链接:【leetcode】771. 宝石与石头
题目描述:题目描述

我们可以把宝石都放到 set 中,然后遍历石头,如果 set 中有这个元素,那就是宝石,否则就不是。

代码:

class Solution {
    public int numJewelsInStones(String jewels, String stones) {
        Set<Character> set = new HashSet<>();
        //先把宝石加到 set中
        for (int i = 0; i < jewels.length(); i++) {
            set.add(jewels.charAt(i));               
        }
        int count = 0;
        //遍历石头,如果 set中包含,就是宝石
        for (int i = 0; i < stones.length(); i++) {
            if (set.contains(stones.charAt(i))) {
                count++;
            }
        }
        return count;
    }
}

旧键盘

题目链接:【牛客网】旧键盘
题目描述:题目描述

和上题类似,我们把坏键(_hs_s_a_es) 输出的字符串添加到 set 中,在遍历好键(7_This_is_a_test) 输出的字符串,如果没有哪个字符,那个键就是坏的,也就是我们要输出的。

代码:

public static void func(String str1,String str2) {
    Set<Character> set = new HashSet<>();
    //把坏键先转成大写,然后存储到 set中
    for (char c : str2.toUpperCase().toCharArray()) {
        set.add(c);
    }

    Set<Character> res = new HashSet<>();

    //遍历好键,把不存在的添加到 res中
    for (char c : str1.toUpperCase().toCharArray()) {
        //!res.contains(c) 避免重复输出 
        if (!set.contains(c) && !res.contains(c)) {
            res.add(c);
            System.out.print(c);
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/366438.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(二十六)大白话如何从底层原理解决生产的Too many connections故障?

今天我们继续讲解昨天的那个案例背景&#xff0c;其实就是经典的Too many connections故障&#xff0c;他的核心就是linux的文件句柄限制&#xff0c;导致了MySQL的最大连接数被限制&#xff0c;那么今天来讲讲怎么解决这个问题。 其实核心就是一行命令&#xff1a; ulimit -H…

分布式面试题

目录 分布式id的生成方案有哪些 雪花算法生成的ID由哪些部分组成 分布式锁在项目中有哪些应用场景? 分布式锁有哪些解决方案 Redis做分布式锁用什么命令 Redis做分布式锁&#xff0c;死锁有哪些情况&#xff1f;如何解决 Redis如何做分布式锁 MySQL如何做分布式锁 什么…

代码签名即将迎来一波新关注

在数字化高度发展的当下&#xff0c;个人隐私及信息安全保护已经成了大家关注的重点&#xff0c;包括日常使用的电脑软件&#xff0c;手机APP等&#xff0c;由于包含了大量的用户信息&#xff0c;已经成了重点关注对象&#xff0c;任何一个疏忽就可能泄露大量用户信息。所以权威…

了解线程安全

线程安全是多线程的重点和难点。 线程安全概念 线程安全&#xff1a;在多线程的各种随机调度顺序下&#xff0c;代码没有bug&#xff0c;都能够符合预期的方式来执行&#xff0c;此时认为线程安全 线程不安全&#xff1a;如果在多线程随机调度下代码出现bug&#xff0c;此时…

Java Web:开篇综述与第一章

前言 翻开这本书&#xff0c;又是一段新的学习路线&#xff0c;在学习的道路上是枯燥的&#xff0c;是乏味的&#xff0c;难免有放弃的想法。但回看曾经的学习笔记&#xff0c;自己也一步一步走过来了&#xff0c;即使会自我怀疑自我否定&#xff0c;但不坚持不努力是永远没有…

#G. 求约数个数之六

我们先求到区间[1..b]之间的所有约数之和于是结果就等于 [1..b]之间的所有约数之和减去[1..a-1]之间的约数之和很明显这两个问题是同性质的问题&#xff0c;只是右端点不同罢了.明显对于1到N之间的数字&#xff0c;其约数范围也为1到N这个范围内。于是我们可以枚举约数L,当然这…

【ROS学习笔记1】ROS快速体验输出Hello World

【ROS学习笔记1】ROS快速体验输出Hello World 文章目录【ROS学习笔记1】ROS快速体验输出Hello World1.1 ROS快速体验1.1.1 Hello World快速实现简介1.1.2 Hello World的C实现1.1.3 Hello World的Python实现写在前面&#xff0c;本系列笔记参考的是AutoLabor的教程&#xff0c;具…

求职3个月,简历大多都石沉大海,一听是手工测试都纷纷摇头....太难了

距离被上家公司裁员已经过去了3个月了&#xff0c;3个月的求职经历真的让我痛不欲生&#xff0c;我也从中理解感叹到了很多&#xff0c;想写出来&#xff0c;告诫跟我一样的经历的人。 我今年26岁&#xff0c;大学是一所普通的大专&#xff0c;学的是机电专业&#xff0c;如何…

【Django】内建用户、文件上传、发送邮件、项目部署

一、内建用户系统 Django带有一个用户认证系统用来处理账号、cookie等 from django.contrib.auth.models import User1、创建用户 from django.contrib.auth.models import User # 普通用户 user User.objects.create_uer(username用户名,password密码,email邮箱) # 超级用…

这几个免费、商用图片素材网,你一定要知道

很多朋友不知道去哪里找图片素材&#xff0c;找到了又担心会不会侵权。 今天给大家分享7个免费可商用图片素材网站&#xff0c;这下再也不用担心找不到素材或侵权啦&#xff01; 1、菜鸟图库 传送门&#xff1a;美女图片|手机壁纸|风景图片大全|高清图片素材下载网 - 菜鸟图库…

hive只复制表结构不复制表数据

目录 一、背景 二、准备测试数据 1.建表 2.造测试数据 三、操作 1.CTAS &#xff08;1&#xff09;.无分区表测试 &#xff08;2&#xff09;.分区表测试 2.LIKE &#xff08;1&#xff09;.无分区表测试 &#xff08;2&#xff09;.分区表测试 一、背景 有一张ori_…

《狂飙》壁纸大嫂如此惊艳,做成日历壁纸天天看

兄弟们&#xff0c;今年的反腐大剧狂飙都有看吗 &#xff1f; 话说&#xff0c;名字虽然叫狂飙&#xff0c;但是全剧只有有田一个人在狂飙&#xff01; 当然&#xff0c;有田虽然亮眼&#xff0c;但是毕竟是个糟老头子&#xff0c;正经人谁看有田啊&#xff0c;当然是看大嫂了…

【在 Colab 中使用 TensorBoard 绘图】

【在 Colab 中使用 TensorBoard 绘图】进入 Google Drive进入 Colab在深度学习中&#xff0c;使用本机GPU跑可能会比较慢&#xff0c;这里使用 Google Drive Colab 进行训练&#xff0c;运行代码 进入 Google Drive 进入网盘 初次进入需要注册账号。注意科学上网即可。右键…

路由器防火墙配置(14)

实验目的 通过本实验&#xff0c;理解路由器的防火墙工作原理&#xff0c;掌握路由器的防火墙功能配置方法&#xff0c;主要包括网络地址转换功能和数据包过滤功能的配置。 培养根据具体环境与实际需求进行网络地址转换及数据包过滤的能力。 预备知识网络地址转换 网络地址转…

SSIM学习

SSIM原文链接&#xff1a;https://www.researchgate.net/profile/Eero-Simoncelli/publication/3327793_Image_Quality_Assessment_From_Error_Visibility_to_Structural_Similarity/links/542173b20cf203f155c6bf1a/Image-Quality-Assessment-From-Error-Visibility-to-Struct…

Window 的 PHP XAMPP 安装 mongodb 的扩展

需要安装的扩展为&#xff1a;extensionphp_mongodb.dll根据官方的指引&#xff1a;PHP: Installing the MongoDB PHP Driver on Windows - Manual 1需要到 GitHub 上下载扩展&#xff0c;然后进行安装。这里的版本选择有些讲究。首先1.51 是 mongoDB 的驱动版本号&#xff0c;…

Bulletproofs++

1. 引言 前序博客&#xff1a; Bulletproofs: Short Proofs for Confidential Transactions and More学习笔记Bulletproofs 代码解析Bulletproofs: Shorter Proofs for Privacy-Enhanced Distributed Ledger学习笔记Bulletproofs 代码解析 Liam Eagen 2022年3月论文《Bullet…

canvas动画及案例

介绍 由于我们是用 JavaScript 去操控 对象&#xff0c;这样要实现一些交互动画也是相当容易的。 可能最大的限制就是图像一旦绘制出来&#xff0c;它就是一直保持那样了。如果需要移动它&#xff0c;我们不得不对所有东西&#xff08;包括之前的&#xff09;进行重绘。重绘是…

SpringBoot项目打包部署到阿里云服务器、通过Maven插件制作Docker镜像、部署项目容器、配置生产环境

制作通用模块jar包 通用模块不是运行的&#xff0c;而且要被其他模块引入的&#xff0c;所以该模块不能采用springboot打包方式制作jar包&#xff0c;否则其他模块无法引入通用模块。 1、修改通用模块&#xff0c;设置模块为非Springboot项目 <?xml version"1.0&qu…

致盛咨询携手亚马逊云科技进一步开拓中国市场

作为医疗保健领域的咨询公司&#xff0c;ZS需要保证服务可靠性、敏捷性和安全性的同时&#xff0c;获得经济效益。亚马逊云科技丰富的云服务产品简化了ZS基础架构的搭建&#xff0c;为ZS节省了大量的人力与资金成本。同时&#xff0c;缩短了ZS扩展基础设施的周转时间&#xff0…