数据结构——(java版)Map与Set

news2024/11/26 17:35:12

文章目录

  • 二叉搜索树
    • (1) 二叉搜索树的概念:
    • (2)二叉搜索树的意义:
    • (3)二叉搜索树的实现:
      • 实现的方法与属性
      • 实现二叉搜索树的查询:
      • 实现二叉搜索树的插入:
      • 实现二叉搜索树的删除:
  • Map与Set
    • 关于Map与Set的概念
    • 模型的概念
    • Map与Set在java框架中的位置:
    • Map中常用的方法:
    • Set中常用的方法:
  • 哈希表(散列表)
    • 什么是哈希表(散列表)?
    • 哈希冲突及如何减少冲突率:
      • *(1)设置合理的哈希函数*
      • *(2)* 调节负载因子
    • 哈希冲突的解决:
      • 哈希冲突解决——开散列(哈希桶)(重点掌握)
      • 哈希桶的模拟实现:
        • 哈希桶实现的方法与属性与内部类:
        • 实现插入操作方法:
        • 实现获取对应key的Value的值
      • 哈希冲突解决—闭散列法(了解即可)
  • oj练习:
    • 1.只出现一次的数字
    • 2.复制带随机指针的链表
    • 3.宝石与石头
    • 4.坏键盘打字
    • 5.前k个高频单词


二叉搜索树

(1) 二叉搜索树的概念:

二叉搜索树是一颗特殊的二叉树,(在其不为空的情况下,意为可以是空树)
特性:其根节点的值比左子树中每一个节点的值都大,比其右子树中每一个节点的值都小。并且对于其左右子树而言亦满足此规则,即所有节点的值比其左子树的值都大,比其右子树的值都小。
在这里插入图片描述
在二叉搜索树中所有的节点均是特殊的,不存在值相同的情况。

(2)二叉搜索树的意义:

查找有两种: 静态查找与动态查找
在静态查找中,有两种:
顺序查找,时间复杂度为O(N), 二分查找,时间复杂度为O(logN)(以2为底)
二分查找的效率较快,但必须在数据有序或逆序的情况下才可以使用。
如果我们隐约将数据与所在位置构建一种映射关系,则不会局限于这堆数据是否有序,且查询搜索效率会大大加快,二叉搜索树即是对这样一种思想的实现(在二叉搜索树中的存放位置与数据大小有关)。

(3)二叉搜索树的实现:

二叉搜索树主要实现三种操作:查找,插入,删除。
我们阐述这几种操作的思想,并辅以编码实现。

实现的方法与属性

public class BinarySearchTree {
       //实现节点的内部类
       static class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;

        public TreeNode(int val) {
             this.val = val;
        }
    }      
     TreeNode root;   //创建二叉树根节点
     //关于二叉搜索树的查询方法:
        public TreeNode search(TreeNode root, int val){}
    // 关于二叉搜索树的插入方法:
        public void insert(int val){}
    //关于二叉搜索树的删除方法:
        public void remove(int val){};
}

实现二叉搜索树的查询:

在这里插入图片描述
时间复杂度:最优情况下:是完全二叉树,时间复杂度为O(logN)(以2为底)
最差情况下:是单分支树:时间复杂度为:O(N).
在这里插入图片描述

  public TreeNode search(int val) {
        //在类中的方法,可以直接引用类中的字段属性
        //root.val = 10;
        //遍历整个二叉树
        TreeNode cur = root;
        while (cur != null) {
            if (cur.val == val) {
                return root;
            } else if (cur.val > val) {
                //如果根节点值比要查找的值大,则往左子树上寻找
                cur = cur.left;
            } else {
                cur = cur.right;
            }
        }
        return null;
    }

我们也可以采用递归来实现:
因为递归同遍历一样,也最终执行出一条路径,找到其对应的位置。
不过递归的空间复杂度较高,不推荐。

实现二叉搜索树的插入:

功能:插入方法用于向二叉搜索树中插入元素,或不断地插入元素来创建二叉搜索树。
思想:(1):在二叉搜索树中插入一个值,最终此值要插入的位置一定是叶子节点。
(2):我们需要一个cur节点来指向在遍历路径中的每一个节点,当cur遇到空节点时,则将需要插入的结点赋给cur,此时我们需要一个parent节点,指向cur的前一个节点。
(3):当需要插入时,还需要判断要插入的节点是在parent的左子树上,还是右子树上,将插入的值与parent的值比较即可得出。

  public void insert(int val){
           TreeNode node  = new TreeNode(val);
           if(root==null){
               root = node;
               return ;
           }
          TreeNode cur = root;
           //设置一个父亲节点
          TreeNode parent = null;
           //如果根节点的值比要插入的值大,则将此值插入到左子树上去。
          while (cur!=null) {
              if (cur.val > val) {
                  parent = cur;
                  cur = cur.left;
              } else if(cur.val>val)    {
            //如果根节点值比要插入的值小,则将此值插入到右子树上去.
                  parent = cur;
                  cur = cur.right;
              }else {
                  //如果要插入的值在二叉搜索树中已经存在,直接返回
                   return;
              }
          }
 //当cur为空时:需要判断新的节点是插在父母节点的左子树上,还是右子树上
          if(val>parent.val) {
              parent.right = node;
          }else {
              parent.left = node;
          }
      }

实现二叉搜索树的删除:

实现思想:
在这里插入图片描述

 public void remove(int val){
        //要删除一个节点,
        //此节点有三种情况,左子树为空,右子树为空,左右子树均不为空,还有一个特殊情况,左右节点均为空,只有一个根节点
        if(root==null){
            return ;
        }
        //需要先找到此节点的位置
        TreeNode cur  = root;
        TreeNode parent = null;
        while (cur !=null) {
            //如果当前cur指向的值即要删除的值
            if (cur.val == val) {
                  //调用删除函数
                delete(cur,parent);
            } else if(cur.val>val) {
                //如果根节点的值小,则访问右子树
                   parent = cur;
                    cur =cur.right;

            }else{
                   parent = cur;
                   cur = cur.left;
            }
        }
    }
    private void delete(TreeNode cur,TreeNode parent) {
         //当要删除的节点为左树为空时,三种情况:
        if(cur.left ==null){
            //先判断此节点是否为根节点,如果是根节点
            if(cur == root){
                root = root.right;
            }else if(cur==parent.left)  {
                //如果当前节点不是根节点,有两种情况,当前节点是父亲节点的左子树
                parent.left = cur.right;

            }else if(cur ==parent.right){
                   //如果当前节点是父亲节点右子树
                parent.right = cur.right;
            }
        }else if(cur.right == null){
            //先判断此节点是否为根节点,如果是根节点
            if(cur == root){
                root = root.left;
            }else if(cur==parent.left)  {
                //如果当前节点不是根节点,有两种情况,当前节点是父亲节点的左子树
                parent.left = cur.left;
            }else if(cur ==parent.right){
                //如果当前节点是父亲节点右子树
                parent.right = cur.left;
            }
        }else {
            //当左右子树均不为空时
            //采用替换删除法:
            //找到当前节点左子树的最左节点或者当前节点右子树的最右节点
            TreeNode cur2 = cur.left;
            //当左子树只有一个节点呢?
            TreeNode parent2 = cur;
            while (cur2.right!=null){
                parent2 = cur2;
                cur2 = cur2.right;
            }
           //用最左节点的值覆盖掉cur节点的值
             cur.val = cur2.val;
            //如果左子树中除去左子树根节点外,其右边没有数据了,
            //那么parent2.left = cur2啊。
            if(parent2.right == cur2){
                parent2.right = cur2.left;
            }else {
                parent2.left = cur2.left;
            }

        }
    }

Map与Set

关于Map与Set的概念

Map与Set是用来搜索的容器或数据结构,我们在之前讲过,查询有静态查询与动态查询,动态查询是指在查询过程中,可能会进行一些插入与删除的操作,而Map与Set即是适合动态查找的容器。

模型的概念

在存储容器中,存储的数据模型有两种:纯Key模型与Key—Value模型
纯Key模型: 例如我们在字典中查询一个单词,看这个单词是否在字典中,字典中只是单纯地存储了这个单词并无别的数据。

Key—Value模型:例如,我们要统计一篇文章中每个单词出现的次数,则我们要存储的数据除去单词本身外,还要存储这个单词出现的次数,形式化为:<单词 , 单词出现的次数>。
在Map中存储的即是Key—Value值,而在Set中只存储Key.

Map与Set在java框架中的位置:

在这里插入图片描述
Map并没有继承Collection类,

Map中常用的方法:

在这里插入图片描述

Set中常用的方法:

在这里插入图片描述

哈希表(散列表)

什么是哈希表(散列表)?

哈希表是一种数据结构,其实现思想是基于通过数据的关键码与所存地址两者之间构建映射关系,能够一次直接从表中得到想要搜索的元素。

数据的关键码与所存地址之间的关系由哈希函数实现,规则为:设定适合的哈希函数之后,输入的关键码通过哈希函数的计算得出所存的地址。由此构造出来的结构称为哈希表。

哈希表相对于二叉搜索树以及其他查询方式,不需要与多个关键码比较,这极大提升了查询的效率

哈希冲突及如何减少冲突率:

当两个不同的关键码通过哈希函数计算得出的哈希地址相同时,此时的情况称为哈希冲突。
哈希冲突如何尽量避免?
首先需要声明的是:哈希表底层数组的容量往往小于需要存储的全部关键码的容量,所以冲突是不可避免的,只能尽量减少冲突率

**

(1)设置合理的哈希函数

**:哈希冲突过大可能是因为哈希函数有问题:哈希函数的设定原则为:

  1. 哈希函数的定义域必须包括全部的关键码。
  2. 哈希函数计算出的地址能够均匀地分布在地址空间之中。
  3. 哈希函数应该比较简单。

哈希函数的几种设计方法:(这里只介绍两种,我们在用哈希函数时,一般不需要自己设计,而是采用java提供给我们的哈希函数)

  1. 直接定制法–(常用)
    取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B 优点:简单、均匀 缺点:需要事先知道关
    键字的分布情况 使用场景:适合查找比较小且连续的情况 面试题:字符串中第一个只出现一次字符
  2. 除留余数法–(常用)
    设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:
    Hash(key) = key% p(p<=m),将关键码转换成哈希地址

(2) 调节负载因子

负载因子概念:所谓负载因子是指: a = 哈希底层数组中的元素个数 / 数组容量大小,
负载因子与冲突率的关系如下:
在这里插入图片描述
所以想要降低冲突率就要降低负载因子,降低负载因子即需要扩展数组容量大小。所以当负载因子超过某一阈值时即应该扩展数组容量。

哈希冲突的解决:

哈希冲突的解决有两种方式:闭散列与开散列。
先学开散列—它比较重要

哈希冲突解决——开散列(哈希桶)(重点掌握)

开散列法又叫作链地址法,首先对关键码集合通过哈希函数计算出其对应的哈希地址,对于具有相同的哈希地址的关键码集合用一个单链表链接起来,称其为,单链表的起始地址存放在哈希表中(一般哈希表由数组实现)。
我们用图来形象化表示:
在这里插入图片描述
哈希桶可以看作将大集合(整个关键码集合)的搜索方法转化为小集合(链表)的搜索方法,当冲突严重时,即链表长度过长时,可以将小集合搜索问题继续进行转化来更好地解决冲突问题。
例如:1. 在每一个桶后跟一个哈希表。
2 .将每一个桶后转化为搜索树。

哈希桶的模拟实现:

我们哈希桶的实现只实现两种操作,插入与获取关于key的value值

哈希桶实现的方法与属性与内部类:
public class HashBuck {
       static class Node{
         //设置key值
         public int key;
         //设置val值
         public int val;
         public Node next;  //指向下一个节点的指针

         //赋值的构造方法
         public Node(int key,int val){
              this.key = key;
              this.val = val;
         }
     }
    private Node[] array  = new Node[10];  //默认设置的数组大小为10
     private int usedsize = 0;             //数组中使用的空间个数
     private  static  final   double  LoadFACTOR = 0.75; //默认的负载因子

   //插入数据——key与相应的val
    public void put(int key,int val);
   //获取数据——获取key相应的val值
    public int getVal(int key);
}
实现插入操作方法:
 public void put(int key,int val){
         //在插入数据时,我们先判断此数据在数组中应放在什么位置。
        int index = key%array.length;
        //然后遍历此位置的链表,如果链表中没有此节点,则插入,有则更新value值.
        Node cur = array[index];
        while (cur !=null){
            if(cur.key == key){
                  //说明此key值存在。
                  cur.val = val;
                  return;
            }
            cur = cur.next;
        }
         //当cur为空时,说明插入的key值在链表中并不存在,则进行头插
         cur = new Node(key,val);
        cur.next = array[index];
        array[index] = cur;
        usedsize++;
        //之后再判断当前数组负载因子是否已经过大
        if(isoverload(array,LoadFACTOR)){
             //如果负载因子超过设定的值,则重新开辟一个数组,并进行后续步骤
             resize();
        }
    }
    private void resize() {
         Node []newarray = new Node[2*array.length];
        //应该遍历所有的链表的节点,重新分配到新的位置上去
        for (int i = 0; i < array.length; i++) {
             Node cur = array[i];
            while (cur!=null){
                    int index  =   cur.key % newarray.length;
                    //在重新获取当前的节点应在的下标后
                   // 将此节点插入到应在的下标处
                  Node curN = cur.next;
                   cur.next = newarray[index];
                   newarray[index] =cur;
                   //cur需要回到原来节点的next节点处
                   cur = curN;
            }
        }
        array = newarray;
    }
    //判断当前负载因子是否过大。
 private boolean isoverload(Node[] array, double loadfactor) {
        return usedsize%array.length>= loadfactor;
    }

实现获取对应key的Value的值

实现思想:先找到当前key值所在的下标,然后遍历此链表即这个桶,当找到值时,返回,未找到则指向下一个节点,直到cur为空

 public int getVal(int key){
         //判断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;
    }

哈希冲突解决—闭散列法(了解即可)

在这里插入图片描述
如图:我们要放置44至数组中,但是当前数组下标4已经有数据,如采用闭散列法:
线性探测
即在逐个遍历数组后面的位置,找到空位,则将44插入空位上。

注:采用闭散列解决哈希冲突时,不能随便删除数据,如下图:如果要删除4,则44则无法再找到
,一般采用伪删除法(比如:采用一个标记置为false,来表示已删除)。
在这里插入图片描述
二次探测:在前面所学的线性检测的缺点为产生冲突的数据都聚集在一起,(这导致了
要查找某一关键码时,需要比较多次,导致搜索效率降低)
举例: 在这里插入图片描述
如图:当所有产生冲突的数据都聚集在一起时,如果要查询5,则不能够直接从5下标找到,而必须从下标5比较到下标8。
二次检测:即在插入冲突的数据时,不再进行依次找位置插入,而找到下一个空位置的方法为:
在这里插入图片描述
(这个了解即可,通常用不到)以此去将相冲突的元素来进行间隔开来。
在进行二次检测后举例:
在这里插入图片描述

oj练习:

1.只出现一次的数字

2.复制带随机指针的链表

3.宝石与石头

4.坏键盘打字

5.前k个高频单词

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

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

相关文章

C++环境配置

目录 1. 前言 2. 正文 2.1 问题 3. 备注 1. 前言 好久不见&#xff0c;最近有些病痛缠身&#xff0c;最近流感有些频发&#xff0c;小生不慎中招&#xff0c;实在是惭愧。当然这不是虚哈&#xff0c;说虚的先出去看个电影再回来。我猜是因为好久没感冒了&#xff0c;所以可…

“最佳行业标杆应用”!亿达科创亮相国际人工智能展

9月8日&#xff0c;2024第五届深圳国际人工智能展会启幕。展会以“智创未来价值链接”为题&#xff0c;汇聚全球人工智能领域的顶尖企业、专家学者及创新成果&#xff0c;亿达科创受邀参展并发表主题演讲&#xff0c;全面展示人工智能技术的最新进展和广泛应用。会上发布“GAIE…

大模型算法岗位面试攻略:100个常见问题详解,高效备战拿下三个offer!

导读 大模型时代很多企业都在开发自己的大模型&#xff0c;这直接刺激了大模型岗位的需求。本文为大家整理了大模型面试相关的知识点&#xff0c;希望对大家面试求职有所帮助。 今天分享大模型面试相关知识点&#xff0c;持续更新。 1. RAG技术体系的总体思路 数据预处理->…

Mysql链接异常 | [08001] Public Key Retrieval is not allowed

Datagrid报错 [08001] Public Key Retrieval is not allowed 这个错误通常是由于 MySQL 8.0 中的新特性导致的。默认情况下&#xff0c;MySQL 8.0 使用 caching_sha2_password 作为认证插件&#xff0c;而你需要在连接 URL 中明确允许公钥检索或者使用老版本的认证方式 mysql…

小型洗衣机什么牌子好又便宜?五款备受好评机型测评,闭眼入

在日常生活中&#xff0c;内衣洗衣机已成为现代家庭必备的重要家电之一。选择一款耐用、质量优秀的内衣洗衣机&#xff0c;不仅可以减少洗衣负担&#xff0c;还能提供高效的洗涤效果。然而&#xff0c;市场上众多内衣洗衣机品牌琳琅满目&#xff0c;让我们往往难以选择。那么&a…

NXOpenC属性操作

1.属性基本介绍 查看属性,文件->属性 使用VS创建项目,找到do_it(),在do_it()里面进行修改 删除属性,使用UF_ATTR_delete(),第一个参数是部件的TAG,第二个参数是属性的类型,第三个属性名字。 下标是属性类型 类型整型UF_ATTR_integer实数类型UF_ATTR_real时间类型U…

strtok函数讲解使用

目录 1.头文件 2.strtok函数介绍 3.解释strtok函数 小心&#xff01;VS2022不可直接接触&#xff0c;否则&#xff01;没这个必要&#xff0c;方源面色淡然一把抓住&#xff01;顷刻炼化&#xff01; 1.头文件 strtok函数的使用需要头文件 #include<string.h> 2.strto…

Chrome 本地调试webrtc 获取IP是xxx.local

浏览器输入 chrome://flags/#enable-webrtc-hide-local-ips-with-mdns并将属性改为disabled修改成功后重启浏览器并刷新网页即可

开放式耳机是什么意思?五款高评分产品极力推荐!

开放式耳机是一种耳机设计&#xff0c;其特点是耳机的耳杯或耳罩不是完全封闭的&#xff0c;允许空气在耳机内部和外部环境之间自由流动。这种设计通常用于头戴式耳机&#xff0c;而不是入耳式耳机。开放式耳机的主要特点包括&#xff1a; 1. 声音传播&#xff1a;开放式耳机允…

Nestjs仿小米商城企业级Nodejs RBAC 微服务项目实战视频教程+Docker Swarm K8s云原生分布式部署教程分享

Nest.js是一个渐进的Node.js框架&#xff0c;可以在TypeScript和JavaScript (ES6、ES7、ES8)之上构建高效、可伸缩的企业级服务器端应用程序。它的核心思想是提供了一个层与层直接的耦合度极小、抽象化极高的一个架构体系。Nest.js目前在行业内具有很高的关注度&#xff0c;所以…

SAP自动付款和自动付款常见错误解决方案

应付账款-自动付款 供应商组(ERP)决定业务伙伴角色 分组决定编号范围;分组也与ERP中的供应商组相映射业务伙伴角色选择BP,分组可以选择BP/VN/CU;业务伙伴角色选择VN,分组只能选择VN 首先创建BP 标识可根据币种进行区别,不设置系统自动排序。其中银行账户22位…

AMD RDNA走到尽头,UDNA合二为一

原文转载修改自&#xff08;更多互联网新闻/搞机小知识&#xff09;&#xff1a; AMD UDNA架构合二为一&#xff0c;取代RDNA和CDNA架构 就在最近&#xff0c;正如我们之前所预料的那样&#xff0c;AMD正式“屈服”了。AMD高级副总裁Jack Huynh直接在IFA2024上宣布&#xff0c…

一、计算机网络的体系结构

1.1 计算机网络的组成 1&#xff09;从组成部分上分为&#xff1a;硬件、软件、协议。硬件是指主机、通信链路、交换设备和通信处理机组曾。软件包括各种实现资源共享的软件以及各种软件工具&#xff08;如网络操作系统、邮件收发程序、FTP程序、聊天软件&#xff09;。 2&…

插装式比例减压阀PPIS04-NG PPRV放大器

比例减压阀/电磁阀操作的方向控制阀能够根据BEUEC比例放大器控制信号控制分别调整/切换先导压力。这自额阀用于控制变量泵的倾斜角度或控制阀的阀芯位置&#xff0c;在作业车辆的电气控制中起着重要的作用。 比例减压阀在行走机械应用和暴露的环境条件实现可靠性、优良的线性度…

blender软件下载地址,blender哪个版本好用

​blender软件下载 不废话&#xff0c;blender软件下载直接点&#xff1a;https://download.blender.org/release/ blender最新稳定版&#xff1a;Blender 4.2.1 LTS 【渲染101云渲染】&#xff1a;如果您希望使用Blender的最新功能&#xff0c;并且愿意接受可能存在的一些小…

《代码整洁之道》-大师眼中的整洁代码是什么样

几个月前写了一篇文章“如何写出难以维护的代码”&#xff0c;从中能大概了解到不好维护的代码是什么样&#xff0c;有哪些坏味道&#xff0c;那肯定有人会反问&#xff0c;难以维护的代码见的太多了&#xff0c;也知道长什么样&#xff0c;但是对于好维护的代码是什么样的比较…

无人机+激光雷达:探索新技术应用场景

无人机与激光雷达技术的结合&#xff0c;为众多领域带来了前所未有的应用可能性和创新解决方案。以下是对无人机激光雷达技术的新应用场景的探索&#xff1a; 一、测绘与地理信息 1. 高分辨率数字表面模型&#xff08;DSM&#xff09;和地形模型&#xff08;DTM&#xff09;&…

关于科研性单位(用电环境)选择工业级插排插座的建议

实验室是科学研究与实验的重要场所&#xff0c;实验环境将直接影响到实验各项数据和结果的可靠性以及实验人员的安全。这类科研建筑内部的设计结构复杂&#xff0c;且有着不同功能类型的区域。根据工作活动的性质来划分不同区域&#xff0c;如&#xff1a;科研通用区&#xff0…

【Unity基础】如何选择脚本编译方式Mono和IL2CPP?

Edit -> Project Settings -> Player 在 Unity 中&#xff0c;Scripting Backend 决定了项目的脚本编译方式&#xff0c;即如何将 C# 代码转换为可执行代码。Unity 提供了两种主要的 Scripting Backend 选项&#xff1a;Mono 和 IL2CPP。它们之间的区别影响了项目的性能、…

小宝宝的好伙伴Baby Buddy

好友 Eduna 前天半夜告诉我&#xff0c;Docker 的下载已经恢复&#xff0c;又可以愉快的玩耍了&#xff0c;大家赶紧去试试吧~ 什么是 Baby Buddy &#xff1f; Baby Buddy 是宝宝的好伙伴&#xff01;能帮助宝爸、宝妈、及护理人员跟踪宝宝的睡眠、喂食、换尿布、趴着的时间等…