数据结构:Map和Set(1)

搜索树

概念

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

这棵树的中序遍历结果是有序的

接下来我们来模拟一棵二叉搜索树,和二叉树一样,定义左右结点,结点值和根结点

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;
}

查找

拿目标值key与root进行比较,比root大的往右边搜索,比root小的往左边搜索;接着继续与左/右子树根结点比较,重复上面步骤

搜索代码

    public boolean search(int key){TreeNode cur = root;while(cur != null){if(cur.val < key){cur = cur.right;} else if (cur.val>key) {cur = cur.left;}else{return true;}}return false;}

时间复杂度:

最好情况:完全二叉树,那时间复杂度为O(logN)

最坏情况:单分支二叉树,时间复杂度O(N)

插入

对于下方的二叉树,我们需要插入15

我们可以定义一个cur,负责遍历二叉树;定义一个parent记录子树的根结点位置

如果当前cur结点的值比目标插入的值小,就把parent定位到当前结点,把cur往右子树移动

如果cur值较大就向左子树移。cur负责帮助parent定位到目标值附近

定义一个node承载目标值,如果parent的值小于目标值就把node右插,反之则左插

    public boolean search(int key){TreeNode cur = root;while(cur != null){if(cur.val < key){cur = cur.right;} else if (cur.val>key) {cur = cur.left;}else{return true;}}return false;}public static boolean insert(TreeNode root, int val){if(root == null){root = new TreeNode(val);return true;}TreeNode cur = root;TreeNode parent = null;while(cur!=null){if(cur.val < val){parent = cur;cur = cur.right;}else if(cur.val > val){parent = cur;cur = cur.left;}else{return false;}}TreeNode node = new TreeNode(val);if(parent.val>val){parent.left = node;}else{parent.right = node;}return true;}

拿这么一个列表来测试一下

array = {5,12,3,2,11,15}

正常画出来的图是这样的

测试debug后画出来的图一模一样😊

 删除

设待删除的结点是cur,而待删除结点的双亲结点是parent

第一种情况:cur.left == null

1. cur是root时,让root = cur.right

2.cur不是root,cur是parent.left,则parent.left = cur.right

3.cur不是root,cur是parent.right,则parent.right = cur.right

        if(cur.left == null){if(cur == root){root = parent.right;}else if(cur == parent.left){parent.right = cur.left;}else{parent.right = cur.right;}}

第二种情况:cur.right == null

1.cur是root,则root = cur.left

2.cur不是root,cur是parent.left,则parent.left = cur.left

3.cur不是root,cur是parent.right,则parent.right = cur.left

        else if(cur.right == null) {if(cur == root){root = parent.left;}else if(cur == parent.left){parent.left = cur.left;}else{parent.right = cur.left;}}

第三种情况:cur.left != null && cur.right != null

我们逍遥删除cur位置这个40元素

首先确定的一点是,40的左子树比40都小,右子树比40都大

替换法:找一个数据来替换cur

1.确定cur这里将来要放的数据一定比左边都大,比右边都小

2.要么在左树里面找到最大的数值(左树最右边的数据);

要么就在右树里面找到最小的数值来替换(右树最左边的数据)

3.找到合适的数据之后,直接替换cur的值,然后删除那个合适的数据结点

        else{//找到合适的值(从右子树找最小值)//target负责找到合适值,targetParent负责记录target的双亲结点TreeNode targetParent = cur;TreeNode target = cur;while(target.left != null){targetParent = target;target = target.left;}cur.val = target.val;//删除target,因为已经到最左边了,所以直接让parent的左边 = target的右边//就算右边为空也没关系targetParent.left = target.right;}

其实这个代码还存在一个问题,如下图,我们找到50为最小值进行替换,替换完删除50的时候执行targetParent.left = target.right; 但是变成20改为55了,这明显不对劲

也就是说,上面的代码只适合targetParent.left = target的情况

遇到这种targetParent.right = target的情况,需要让targetParent的右边 = target的右边

代码修改(只修改删除target的部分)

            if(targetParent.left == target){targetParent.left = target.right;}else{targetParent.right = target.right;}

不想看分析可以直接看这

整个的代码:

    public void remove(int val){TreeNode cur = root;TreeNode parent = null;while(cur!=null){if(cur.val < val){parent = cur;cur = cur.right;}else if(cur.val > val){parent = cur;cur = cur.left;}else{//开始删除removeNode(cur,parent);}}}private void removeNode(TreeNode cur, TreeNode parent) {if(cur.left == null){if(cur == root){root = parent.right;}else if(cur == parent.left){parent.right = cur.left;}else{parent.right = cur.right;}}else if(cur.right == null) {if(cur == root){root = parent.left;}else if(cur == parent.left){parent.left = cur.left;}else{parent.right = cur.left;}}else{//找到合适的值(从右子树找最小值)//target负责找到合适值,targetParent负责记录target的双亲结点TreeNode targetParent = cur;TreeNode target = cur;while(target.left != null){targetParent = target;target = target.left;}cur.val = target.val;//删除targetif(targetParent.left == target){targetParent.left = target.right;}else{targetParent.right = target.right;}}}

Map的使用

搜索

我们学过的搜索

1.直接遍历: --> O(N),速度较慢

2.二分查找:--> O(logN),但要求搜索的序列有序

上面的搜索都属于静态搜索

而现实中我们需要在查找时进行一些插入和删除操作,就需要用到Map和Set这两个适合动态查找的容器了

Map属于Key-Value 模型 Key-Value 模型比如:
统计文件中每个单词出现的次数,统计结果是每个单词都有与其对应的次数: < 单词,单词出现的次数 >
梁山好汉的江湖绰号:每个好汉都有自己的江湖绰号

 Map有两种形式,二叉搜索树和哈希表

        Map<String,Integer> map = new TreeMap<>();//二叉搜索树, 查找复杂度O(logN)Map<String,Integer> map1 = new HashMap<>();//哈希表, 查找复杂度O(1) 哈希表-->数组+列表+红黑树

方法

put设置key和对应的value 

get通过key来找到对应的值 

如果找不到就返回null

getOrDefault方法和get差不多,只不过找不到就默认返回自己设置的返回值

keySet获取所有的key

entrySet返回key-value的所有关系

Set<Map.Entry<String,Integer>> entrySet = map.entrySet();

而Set是所有Entry的集合

注意:

1.Map是一个接口,不能直接实例化对象,只能实例化其实现类TreeMap或HashMap

2.Map里的键值对中key是唯一的,但value是可以重复的,以最后一个value为准

3.Map中键值对的Key不能直接修改,value可以修改,如果要修改key,只能先将该key删除掉,然后再来进行重新插入。


Set的使用

Set属于 key 模型 key 模型比如:
有一个英文词典,快速查找一个单词是否在词典中
快速查找某个名字在不在通讯录中

方法

iterator方法,输出每个key,每行1个 

注意:

1.Set是继承自Collection的一个接口类

2.TreeSet的底层是TreeMap

3.实现Set接口的常用类有TreeSetHashSet,还有一个LinkedHashSetLinkedHashSet是在HashSet的基础上维护了一个双向链表来记录元素的插入次序。


哈希表

概念

顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,
必须要经过关键码的多次比较。搜索的效率取决于搜索过程中元素的比较次数
理想搜索方法:可以不经过比较,一次直接从表中得到要搜索的元素,通过该元素的存储位置和它的关键码之间建立一一映射的关系

这种函数叫做哈希函数:hash(key) = key % capacity

capacity是存储元素底层空间的大小

 哈希冲突:不同的关键字key,通过相同的哈希函数,得到相同的值

哈希冲突无法解决,只能降低冲突率

哈希函数的设计

合理的哈希函数可以降低冲突率

原则:

1.哈希函数定义域包括需要存储的全部关键码,如果散列表允许有m个地址时,值域必须在0到m-1之间

2.哈希函数计算出来的地址能均匀分布在整个空间中

3.哈希函数比较简单

常见的哈希函数

直接订制法:取关键字的某个线性函数为散列地址

优点:简单、均匀
缺点:需要事先知道关 键字的分布情况
使用场景:适合查找比较小且连续的情况
387. 字符串中的第一个唯一字符 - 力扣(LeetCode)
    public int firstUniqChar(String s) {int[] count = new int[26];for(int i = 0; i < s.length(); i++){char ch = s.charAt(i);count[ch-'a']++;}for(int i = 0; i< s.length();i++){char ch = s.charAt(i);if(count[ch-'a'] == 1){return i;}}return -1;}

除留余数法

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

负载因子调节 

由图片我们可以知道负载因子越高,冲突率逐渐增加

填入表里面的元素已经是固定的情况下,为了使负载因子降低,我们只能让散列表扩容


冲突避免方法

闭散列

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

那么怎么找到这个“下一个”位置呢?

1.线性探测法:

比如我们要在这个线性表中放入44,14,24,34,54

44与4冲突了,探测到4下一个的位置时空的,就把44放进去,后面的14,24,34挨个放进去空位置,到了54,后面没有位置可以放了,返回到前面继续探测,找到0位置 

但是线性探测有个缺点,产生冲突的数据会堆在一块 

2.二次探测

或者

H0时通过散列函数对关键码key计算出来的位置,m是表的大小,i = 0,1,2,3....代表的是要插入的数据排在第几位

 这样明显不会堆在一起,而是均匀散开来了


开散列/哈希桶

又叫链地址法(开链法),采用数组+链表(+红黑树,当数组长度>64 && 链表长度 >=8 之后才会把链表变成红黑树)的方法,把冲突的元素采用尾插法插入被冲突元素的后面(JDK 1.7之前采用头插法,JDK 1.8之后采用尾插法)

数组的每个元素是链表的头节点


手搓一个哈希桶

初始化链表

每个结点需要有三个域,key,value和next

    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 usedSize;//记录存放的有效数据

插入元素 

第一步:首先用cur进行遍历,遍历index下标的链表是否存在key,如果存在就更新value,遍历完还不存在就插入元素

    public void put(int key, int val){int index = key % array.length;//遍历index下标的链表是否存在key,如果存在就更新value,不存在就插入数据Node cur = array[index];while(cur!=null){if(cur.key == key){//更新valuecur.val = val;}cur = cur.next;}//cur==null-->遍历完没有找到key// 头插法Node node = new Node(key,val);node.next = array[index];array[index] = node;usedSize++;}

第二步:计算负载因子

如果负载因子仍然大于默认的最低负载因子,则散列表需要进行扩容

    public static final float DEFAULT_LOAD_FACTOR = 0.75f;private float doLoadFactor(){return usedSize * 1.0f / array.length;}

面试题:可以这样进行扩容吗?

        if(doLoadFactor()>DEFAULT_LOAD_FACTOR){//扩容array = Arrays.copyOf(array, 2*array.length);}

都这么问了,那很明显是不行的😊

假设我们的11元素经过哈希之后放在插入到1下标这里

经过扩容之后,capacity发生了改变,变成了20

根据hash(key) = key % capacity,此时11%20 = 11

因为长度改变,原来冲突的元素放到了其他位置中去,所以这样扩容是不行滴

设置cur遍历原来的数组,每遍历到一个元素就纵向遍历链表的元素并进行头插法

代码: 

注意:这里为什么要用一个tmp保存cur.next呢?

所以我们用一个tmp来保存原来的纵向遍历时11元素的地址


获取元素

    public int get(int key){int index = key % array.length;Node cur = array[index];while(cur != null){if(cur.key == key){return cur.val;}cur = cur.next;}return -1;}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AD9371 官方例程裸机SW 和 HDL配置概述(二)

AD9371 系列快速入口 AD9371ZCU102 移植到 ZCU106 &#xff1a; AD9371 官方例程构建及单音信号收发 ad9371_tx_jesd -->util_ad9371_xcvr接口映射&#xff1a; AD9371 官方例程之 tx_jesd 与 xcvr接口映射 AD9371 官方例程 时钟间的关系与生成 &#xff1a; AD9371 官方…

Electron[3] 基础配置准备和Electron入门案例

1 背景 上一篇文章已经分享了&#xff0c;如何准备Electron的基础环境了。但是博客刚发才一天&#xff0c;就发现有人问问题了。经过实践发现&#xff0c;严格按照作者的博客教程走是不会有问题的&#xff0c;其中包括安装的环境版本等都要一致。因为昨天发的博客&#xff0c;…

使用Jsoup库编写程序

Jsoup库编写的Kotlin网络爬虫程序 kotlin import org.jsoup.Jsoup import org.jsoup.nodes.Document import org.jsoup.nodes.Element import org.jsoup.select.Elements import java.net.HttpURLConnection import java.net.URL fun main(args: Array<String>) { v…

Zephyr-7B-β :类GPT的高速推理LLM

Zephyr 是一系列语言模型&#xff0c;经过训练可以充当有用的助手。 Zephyr-7B-β 是该系列中的第二个模型&#xff0c;是 Mistralai/Mistral-7B-v0.1 的微调版本&#xff0c;使用直接偏好优化 (DPO) 在公开可用的合成数据集上进行训练 。 我们发现&#xff0c;删除这些数据集的…

BIOS开发笔记 - CMOS

CMOS原来指的是一种生产电子电路的工艺,在PC上一般指的是RTC电路单元,因为早期它是由这种工艺生产出来的,所以又把RTC称作了CMOS。 RTC(Real Time Clock)即实时时钟,用于保存记录时间和日期,也可以用来做定时开机功能。RTC靠一组独立的电源给它供电,这样设计的目的就是…

Win系统强制删除文件/文件夹

Win系统强制删除文件/文件夹 前言系统磁盘清理360强制删除NPM删除 前言 Win系统的用户删除文件/文件夹时&#xff0c;可能由于权限问题导致文件无法正常删除&#xff0c;下文介绍解决方案。当常规的删除不起作用时&#xff0c;可使用如下方案进行删除&#xff0c;包含系统磁盘…

大数据商城人流数据分析与可视化 - python 大数据分析 计算机竞赛

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于大数据的基站数据分析与可视化 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度…

若依分离版——配置多数据源(mysql和oracle),实现一个方法操作多个数据源

目录 一、若依平台配置 二、编写oracle数据库访问的各类文件 三. 一个方法操作多个数据源 一、若依平台配置 1、在ruoyi-admin的pom.xml添加oracle依赖 <dependency> <groupId>com.oracle</groupId> <artifactId>ojdbc6</artifactId> <v…

JVM 各个参数详解

在一些规模稍大的应用中&#xff0c;Java虚拟机&#xff08;JVM&#xff09;的内存设置尤为重要&#xff0c;想在项目中取得好的效率&#xff0c;GC&#xff08;垃圾回收&#xff09;的设置是第一步。 PermGen space&#xff1a;全称是Permanent Generation space.就是说是永久…

ZZ308 物联网应用与服务赛题第B套

2023年全国职业院校技能大赛 中职组 物联网应用与服务 任 务 书 &#xff08;B卷&#xff09; 赛位号&#xff1a;______________ 竞赛须知 一、注意事项 1.检查硬件设备、电脑设备是否正常。检查竞赛所需的各项设备、软件和竞赛材料等&#xff1b; 2.竞赛任务中所使用的…

宜昌市公安局、点军区政府与中科升哲达成战略合作,共建视频图像联合创新实验室

11月3日&#xff0c;宜昌视频图像联合创新战略合作签约仪式在宜昌市公安局举行。 宜昌市副市长、市公安局党委书记、局长上官福令&#xff0c;市公安局党委副书记、副局长龚海波&#xff0c;宜昌市点军区委书记万红&#xff0c;点军区委副书记、区长黄文云&#xff0c;升哲科技…

git commit规范提交

Git每次提交代码时&#xff0c;都要写Commit Message&#xff08;提交说明&#xff09;&#xff0c;通常情况下&#xff0c;Commit Message应该清晰明了&#xff0c;说明本次提交的目的和具体操作等。然而笔者工作多年来发现&#xff0c;有些公司对Commit Message没有明确的要求…

AI:64-基于深度学习的口罩佩戴检测

🚀 本文选自专栏:AI领域专栏 从基础到实践,深入了解算法、案例和最新趋势。无论你是初学者还是经验丰富的数据科学家,通过案例和项目实践,掌握核心概念和实用技能。每篇案例都包含代码实例,详细讲解供大家学习。 📌📌📌在这个漫长的过程,中途遇到了不少问题,但是…

Luckysheet 实现excel多人在线协同编辑

前言 前些天看到Luckysheet支持协同编辑Excel&#xff0c;正符合我们协同项目的一部分&#xff0c;故而想进一步完善协同文章&#xff0c;但是遇到了一下困难&#xff0c;特此做声明哈&#xff0c;若侵权&#xff0c;请联系我删除文章&#xff01; 若侵犯版权、个人隐私&#x…

Loftware——重新定义创建、管理和打印标签的方式

重新定义创建、管理和打印标签的方式 Loftware 帮助各种规模的企业管理其运营和供应链中的标签。无论您拥有五台还是数千台打印机&#xff0c;寻找云还是本地打印机&#xff0c;我们都能提供适合您业务需求的标签解决方案。 全面的标签解决方案 01、一体化标签解决方案 通过…

【Redis】Redis整合SSMRedis注解式缓存Redis中的缓存穿透、雪崩、击穿的原因以及解决方案(详解)

目录&#xff1a; 目录 一&#xff0c;SSM整合redis 二&#xff0c;redis注解式缓存 三&#xff0c;Redis中的缓存穿透、雪崩、击穿的原因以及解决方案&#xff08;附图&#xff09; 一&#xff0c;SSM整合redis 1.原因&#xff1a; 整合SSM和Redis可以提升系统的性能、可…

桶装水订水系统水厂送水小程序开发;

桶装水小程序正式上线&#xff0c;支持多种商品展示形式&#xff0c;会员卡、积分、分销等功能&#xff1b; 开发订水送水小程序系统&#xff0c;基于用户、员工、商品、订单、配送站和售后管理模块&#xff0c;对每个模块进行统计分析&#xff0c;简化了分配过程&#xff0c;提…

vivo 网络端口安全建设技术实践

作者&#xff1a;vivo 互联网安全团队 - Peng Qiankun 随着互联网业务的快速发展&#xff0c;网络攻击的频率和威胁性也在不断增加&#xff0c;端口是应用通信中的门户&#xff0c;它是数据进出应用的必经之路&#xff0c;因此端口安全也逐渐成为了企业内网的重要防线之一&…

【Spring实战——构建Spring Web应用程序】1.10 处理表单

引言 Web应用功能 ○ 提供内容 ○ 用户填写表单 ○ 提交数据 Spring MVC的控制器提供了 ○ 处理表单展示 ○ 用户提交数据的支持 在Spittr应用中&#xff0c;需要一个注册表单供新用户使用。SpitterController是一个新的控制器&#xff0c;目前只有一个请求处理方法用于展示…