interview4-集合篇

news2024/11/25 23:35:09

一、算法复杂度分析

为什么要进行复杂度分析?因为可以指导你编写出性能更优的代码和评判别人写的代码的好坏。

(1)时间复杂度分析

时间复杂度是用来评估代码的执行耗时的。

1.假如每行代码的执行耗时一样:1ms

2.分析这段代码总执行多少行?3n + 3

3.代码耗时总时间: T(n) = (3n + 3) * 1ms

  • 大O表示法:不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势

  • T(n)与代码的执行次数成正比(代码行数越多,执行时间越长)

  • 当n很大时,公式中的低阶,常量,系数三部分并不左右其增长趋势,因此可以忽略,我们只需要记录一个最大的量级就可以了

    例:

上图时间复杂度:只要代码的执行时间不随着参数n的增大而增大,这样的代码复杂度都是O(1)

(2)空间复杂度分析

空间复杂度全称是渐进空间复杂度,表示算法占用的额外存储空间数据规模之间的增长关系。

二、List分析

(1)数组底层

数组(Array)是一种用连续的内存空间存储相同数据类型数据的线性数据结构。

为什么数组索引从0开始呢?假如从1开始不行吗?

  • 在根据数组索引获取元素的时候,会用索引和寻址公式来计算内存所对应的元素数据,寻址公式是:数组的首地址+索引*存储数据的类型大小

  • 如果数组的索引从1开始,寻址公式中,就需要增加一次减法操作,对于CPU来说就多了一次指令,性能不高。

查找数组的时间复杂度:

1. 随机查询(根据索引查询)数组元素的访问是通过索引来访问的,计算机通过数组的首地址寻址公式能够很快速的找到想要访问的元素

2. 未知索引查询

查找排序后数组内的元素,查找55号数据

删除和插入数组的时间复杂度:

数组是一段连续的内存空间,因此为了保证数组的连续性会使得数组的插入和删除的效率变的很低。

最好情况下是O(1)的,最坏情况下是O(n)的,平均情况下的时间复杂度是O(n)。

(2)ArrayList剖析

源码如何分析?要从成员变量构造函数关键方法来分析。

1. 底层数据结构

ArrayList底层是用动态的数组实现的

2. 初始容量

ArrayList初始容量为0,当第一次添加数据的时候才会初始化容量为10

3. 扩容逻辑

ArrayList在进行扩容的时候是原来容量的1.5倍,每次扩容都需要拷贝数组

4. 添加逻辑

  • 确保数组已使用长度(size)加1之后足够存下下一个数据

  • 计算数组的容量,如果当前数组已使用长度+1后的大于当前的数组长度,则调用grow方法扩容(原来的1.5倍)

  • 确保新增的数据有地方存储之后,则将新元素添加到位于size的位置上。

  • 返回添加成功布尔值。

(3)数组与ArrayList相关问题

ArrayList list=new ArrayList(10)中的list扩容几次?

参考回答:该语句只是声明和实例了一个 ArrayList,指定了容量为 10,未扩容

如何实现数组和List之间的转换?

  • 数组转List ,使用JDK中java.util.Arrays工具类的asList方法

  • List转数组,使用List的toArray方法。无参toArray方法返回 Object数组,传入初始化长度的数组对象,返回该对象数组

用Arrays.asList转List后,如果修改了数组内容,list受影响吗?

Arrays.asList转换list之后,如果修改了数组的内容,list会受影响,因为它的底层使用的Arrays类中的一个内部类ArrayList来构造的集合,在这个集合的构造器中,把我们传入的这个集合进行了包装而已,最终指向的都是同一个内存地址

List用toArray转数组后,如果修改了List内容,数组受影响吗?

list用了toArray转数组后,如果修改了list内容,数组不会影响,当调用了toArray以后,在底层是它是进行了数组的拷贝,跟原来的元素就没啥关系了,所以即使list修改了以后,数组也不受影响

(4)单向链表与双向链表

单向链表:每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。记录下个结点地址的指针叫作后继指针 next

链表中的每一个元素称之为结点(Node),物理存储单元上,非连续、非顺序的存储结构。

双向链表:顾名思义,它支持两个方向每个结点不止有一个后继指针 next 指向后面的结点有一个前驱指针 prev 指向前面的结点

对比单链表:

  • 双向链表需要额外的两个空间来存储后继结点和前驱结点的地址

  • 支持双向遍历,这样也带来了双向链表操作的灵活性

单向链表只有一个方向,结点只有一个后继指针 next。双向链表它支持两个方向,每个结点不止有一个后继指针next指向后面的结点,还有一个前驱指针prev指向前面的结点。

链表操作数据的时间复杂度:

查询、新增、删除
单向链表头O(1),其他O(n)
双向链表头尾O(1),其他O(n),给定节点O(1)

(5)ArrayList和LinkedList的区别

1、底层数据结构

  • ArrayList 是动态数组的数据结构实现

  • LinkedList 是双向链表的数据结构实现

2、操作数据效率

  • ArrayList按照下标查询的时间复杂度O(1)【内存是连续的,根据寻址公式:数组的首地址+索引*存储数据的类型大小】, LinkedList不支持下标查询

  • 查找(未知索引): ArrayList需要遍历,链表也需要遍历,时间复杂度都是O(n)

  • 新增和删除:

    ArrayList尾部插入和删除,时间复杂度是O(1);其他部分增删需要挪动数组,时间复杂度是O(n);

    LinkedList头尾节点增删时间复杂度是O(1),其他都需要遍历链表,时间复杂度是O(n)

3、内存空间占用

  • ArrayList底层是数组,内存连续,节省内存

  • LinkedList 是双向链表需要存储数据,和两个指针,更占用内存

4、线程安全

ArrayList和LinkedList都不是线程安全的。如果需要保证线程安全,有两种方案:

  1. 在方法内使用,局部变量则是线程安全的

  2. 使用线程安全的ArrayList和LinkedList

List<Object> syncArrayList = Collections.synchronizedList(new ArrayList<>());
List<Object> syncLinkedList = Collections.synchronizedList(new LinkedList<>());

三、数据结构

(1)二叉树

二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只有左子节点,有的节点只有右子节点。二叉树每个节点的左子树和右子树也分别满足二叉树的定义。

Java中有两个方式实现二叉树:数组存储,链式存储。基于链式存储的树的节点可定义如下:

在二叉树中,比较常见的二叉树有:满二叉树、完全二叉树、二叉搜索、树红黑树

二叉搜索树(Binary Search Tree,BST)又名二叉查找树,有序二叉树或者排序二叉树,是二叉树中比较常用的一种类型二叉查找树要求,在树中的任意一个节点,其左子树中的每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值(左小右大)。

实际上由于二叉查找树的形态各异,时间复杂度也不尽相同, 看一下插入,查找,删除的时间复杂度:

(2)红黑树

红黑树(Red Black Tree):也是一种自平衡的二叉搜索树(BST),之前叫做平衡二叉B树(Symmetric Binary B-Tree)

红黑树的特征:

  1. 节点要么是红色,要么是黑色

  2. 根节点是黑色

  3. 叶子节点都是黑色的空节点

  4. 红黑树中红色节点的子节点都是黑色

  5. 从任一节点到叶子节点的所有路径都包含相同数目的黑色节点

保证平衡: 在添加或删除节点的时候,如果不符合这些性质会发生旋转,以达到所有的性质。

红黑树的时间复杂度:查找、添加、删除都是O(logn)

(3)散列表

散列表(Hash Table)又名哈希表/Hash表,是根据键(Key)直接访问在内存存储位置值(Value)的数据结构,它是由数组演化而来的,利用了数组支持按照下标进行随机访问数据的特性。

将键(key)映射为数组下标的函数叫做散列函数。可以表示为:hashValue = hash(key)

散列函数的基本要求:

  • 散列函数计算得到的散列值必须是大于等于0的正整数,因为hashValue需要作为数组的下标。

  • 如果key1==key2,那么经过hash后得到的哈希值也必相同即:hash(key1) == hash(key2)

  • 如果key1 != key2,那么经过hash后得到的哈希值也必不相同即:hash(key1) != hash(key2)

散列冲突:

实际的情况下想找一个散列函数能够做到对于不同的key计算得到的散列值都不同几乎是不可能的,即便像著名的MD5,SHA等哈希算法也无法避免这一情况,这就是散列冲突(或者哈希冲突,哈希碰撞,就是指多个key映射到同一个数组下标位置)

在散列表中,数组的每个下标位置我们可以称之为桶(bucket)或者槽(slot),每个桶(槽)会对应一条链表,所有散列值相同的元素我们都放到相同槽位对应的链表中。

散列冲突-链表法(拉链)

  • 数组的每个下标位置称之为桶(bucket)或者槽(slot)

  • 每个桶(槽)会对应一条链表

  • hash冲突后的元素都放到相同槽位对应的链表中或红黑树中

四、HashMap分析

(1)HashMap的实现原理

HashMap的数据结构: 底层使用Hash表数据结构,即数组和链表或红黑树

  1. 当我们往HashMap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标

  2. 存储时,如果出现hash值相同的key,此时有两种情况。

    a. 如果key相同,则覆盖原始值;

    b. 如果key不同(出现冲突),则将当前的key-value放入链表或红黑树中

  3. 获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。

HashMap的jdk1.7和jdk1.8有什么区别?

  1. jdk1.8之前采用的拉链法,数组+链表

  2. jdk1.8之后采用数组+链表+红黑树,链表长度大于8且数组长度大于64则会从链表转化为红黑树

(2)HashMap的put方法的具体流程

  1. 判断键值对数组table是否为空或为null,否则执行resize()进行扩容(初始化)

  2. 根据键值key计算hash值得到数组索引

  3. 判断table[i]==null,条件成立,直接新建节点添加

  4. 如果table[i]==null ,不成立

    4.1 判断table[i]的首个元素是否和key一样,如果相同直接覆盖value

    4.2 判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对

    4.3 遍历table[i],链表的尾部插入数据,然后判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操 作,遍历过程中若发现key已经存在直接覆盖value

  5. 插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold(数组长度*0.75),如果超过,进行扩容。

(3)HashMap的寻址算法

为何HashMap的数组长度一定是2的次幂?

  1. 计算索引时效率更高:如果是 2 的 n 次幂可以使用位与运算代替取模

  2. 扩容时重新计算索引效率更高: hash & oldCap == 0 的元素留在原来位置 ,否则新位置 = 旧位置 + oldCap

HashMap在1.7情况下的多线程死循环问题

jdk7的的数据结构是:数组+链表。在数组进行扩容的时候,因为链表是头插法,在进行数据迁移的过程中,有可能导致死循环

比如说,现在有两个线程

线程一:读取到当前的hashmap数据,数据中一个链表,在准备扩容时,线程二介入

线程二:也读取hashmap,直接进行扩容。因为是头插法,链表的顺序会进行颠倒过来。比如原来的顺序是AB,扩容后的顺序是BA,线程二执行结束。

线程一:继续执行的时候就会出现死循环的问题。

线程一先将A移入新的链表,再将B插入到链头,由于另外一个线程的原因,B的next指向了A,所以B->A->B,形成循环。当然,JDK 8 将扩容算法做了调整,不再将元素加入链表头(而是保持与扩容前一样的顺序),尾插法,就避免了jdk7中死循环的问题。

(4)HashMap扩容机制

扩容流程:

1.在添加元素或初始化的时候需要调用resize方法进行扩容,第一次添加数据初始化数组长度为16,以后每次每次扩容都是达到了扩容阈值(数组长度 * 0.75)

2.每次扩容的时候,都是扩容之前容量的2倍

3.扩容之后,会新创建一个数组,需要把老数组中的数据挪动到新的数组中

  • 没有hash冲突的节点,则直接使用 e.hash & (newCap - 1) 计算新数组的索引位置

  • 如果是红黑树,走红黑树的添加

  • 如果是链表,则需要遍历链表,可能需要拆分链表,判断(e.hash & oldCap)是否为0,该元素的位置要么停留在原始位置,要么移动到原始位置+增加的数组大小这个位置上

(5)HashMap与HashSet、HashTable的区别

HashMap、HashSet和HashTable都是Java集合框架中的一部分,它们主要在实现的接口、线程安全性、执行效率、对空值的处理方式以及添加元素的方式上存在区别。

  1. 实现的接口:HashMap和HashTable是Map接口的实现类,而HashSet是Set接口的实现类。

  2. 线程安全性:HashTable中的方法加了同步锁(synchronized),因此它是线程安全的;而HashMap是异步的,所以存放的对象并不是线程安全的。HashSet的底层是用HashMap实现的,因此它也不是线程安全的。

  3. 执行效率:因为HashTable是同步的,而HashMap是异步的,所以HashMap的执行效率比HashTable要高。三者的执行效率由快到慢为:HashMap>HashSet>HashTable。

  4. 对空值的处理:HashMap的key、value是可以为null,而HashTable的key、value则不能存放Null。HashSet只能存放value,其底层使用了hashmap,因此它也是可以放Null的。

  5. 添加元素的方式:HashMap是通过put方法添加元素的,而HashSet是通过add方法来添加元素的。

总的来说,HashMap、HashSet和HashTable各有其特点和使用场景,选择使用哪个集合类主要取决于具体的业务需求。

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

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

相关文章

跟随算网超人,深度解析算力网络!

随着数字时代全面开启 算力网络已成为当下热点议题 作为信息社会两大基石 算力、网络为何如此重要&#xff1f; 又将如何影响社会发展脉动&#xff1f; 为帮助大家深入了解算力网络 我们特别推出“算网超人”系列科普 下面&#xff0c;请跟随算网超人的步伐 来到该系列的…

uni-app H5使用 tabbars切换,echartst图表变小 宽度只有100px问题解决

问题&#xff1a; 跳转到别tabbars页面之后&#xff0c;再回来&#xff0c;echarts图显示缩小小团子。 原因分析&#xff1a; 在tabs切换中有echarts的话&#xff0c;我们会发现初始化的那个echarts是有宽度的&#xff0c;当点击tabs切换之后&#xff0c;切换过来的echarts只…

Python+requests编写的自动化测试项目

框架产生目的&#xff1a;公司走的是敏捷开发模式&#xff0c;编写这种框架是为了能够满足当前这种发展模式&#xff0c;用于前后端联调之前&#xff08;后端开发完接口&#xff0c;前端还没有将业务处理完毕的时候&#xff09;以及日后回归阶段&#xff0c;方便为自己腾出学(m…

Biome-BGC生态系统模型与Python融合技术:揭秘未来生态预测新趋势

Biome-BGC是利用站点描述数据、气象数据和植被生理生态参数&#xff0c;模拟日尺度碳、水和氮通量的有效模型&#xff0c;其研究的空间尺度可以从点尺度扩展到陆地生态系统。 在Biome-BGC模型中&#xff0c;对于碳的生物量积累&#xff0c;采用光合酶促反应机理模型计算出每天…

手机提词器有哪些?简单介绍这一款

手机提词器有哪些&#xff1f;手机提词器在现代社会中越来越受欢迎&#xff0c;原因是它可以帮助人们提高演讲和朗读的效果。使用手机提词器可以让人们更加自信地面对演讲和朗读&#xff0c;不至于出现口误或读错字的情况。此外&#xff0c;手机提词器还可以帮助人们节省时间和…

了解稀疏数组

稀疏数组&#xff08;一种数据结构&#xff09; package com.mypackage.array;public class Demo08 {public static void main(String[] args) {//1.创建一个二维数组 11*11// 0&#xff1a;没有棋子 1&#xff1a;黑棋 2&#xff1a;白棋int[][] array1 new int[11][11];…

OpenCV(四十一):图像分割-分水岭法

1.分水岭方法介绍 OpenCV 提供了分水岭算法&#xff08;Watershed Algorithm&#xff09;的实现&#xff0c; 使用分水岭算法对图像进行分割&#xff0c;将图像的不同区域分割成互不干扰的区域。分水岭算法模拟了水在图像中的扩散和聚集过程&#xff0c;将标记的边界被看作是阻…

Android Shadow 插件化原理演示

工程目录图 请点击下面工程名称&#xff0c;跳转到代码的仓库页面&#xff0c;将工程 下载下来 Demo Code 里有详细的注释 代码&#xff1a;LearnShadow

【Linux指令】Centos7 touch修改Access/Modify/Change 时间与恢复系统时间

文章目录 前言正文1. 查看文件状态2.只更新Access Time2.只更新Modify Time3. 修改Acess Time 与Modify Time为指定时间4. 修改Change时间5. 恢复系统时间 总结 前言 本篇主要讲解touch与时间相关的操作&#xff0c;关于touch创建文件&#xff0c;就不再赘述。 正文 1. 查看…

IP地址定位基础数据采集

在互联网时代&#xff0c;IP地址定位技术已经成为了广泛应用的一项重要技术。无论是用于网络安全、广告投放、市场调研还是用户体验优化&#xff0c;IP地址定位技术都发挥着关键作用。 什么是IP地址定位&#xff1f; IP地址定位是一种技术&#xff0c;它通过IP地址来确定设备…

行云管家全面适配信创国产化平台 助力政企信创环境下数字化转型与安全运维

近日&#xff0c;作为云计算管理及信息安全领域优秀的产品服务提供商&#xff0c;深圳市行云绽放科技有限公司宣布旗下行云管家系列产品已全面适配信创国产化平台&#xff0c;包括CPU、服务器、数据库、浏览器等&#xff0c;为政企客户提供符合信创环境要求的云计算管理与信息安…

排序算法-堆排序

思路 堆排序(Heapsort)是指利用堆积树&#xff08;堆&#xff09;这种数据结构所设计的一种排序算法&#xff0c;它是选择排序的一种。它是通过堆 来进行选择数据。需要注意的是排升序要建大堆&#xff0c;排降序建小堆。 我们先将要排序的数据建成堆&#xff0c;然后通…

【数据分享】上海市道路中心线数据(无需转发\单线\shp格式)

道路数据是我们在各项研究中经常使用的数据&#xff0c;我们一般获取到的数据都是多线道路&#xff08;也就是一条道路上有多条线来表示&#xff09;&#xff0c;这种多线道路并不适用于交通网络分析等操作中&#xff0c;很多时候我们需要单线道路数据&#xff0c;也就是道路中…

基于协同过滤算法的旅游推荐系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

接口自动化测试中解决接口间数据依赖

在实际的测试工作中&#xff0c;在做接口自动化测试时往往会遇到接口间数据依赖问题&#xff0c;即API_03的请求参数来源于API_02的响应数据&#xff0c;API_02的请求参数又来源于API_01的响应数据。 因此通过自动化方式测试API_03接口时&#xff0c;需要预先请求API_02接口&a…

马来西亚市场最全开发攻略

东盟有十个国家&#xff0c;人口接近6亿&#xff0c;已连续13年成为我国最大贸易伙伴之一。数据显示&#xff0c;1-7月中国与东盟贸易总值为3.59万亿元&#xff0c;同比增长2.8%。东盟成员国中&#xff0c;与中国前三大贸易伙伴依次为越南、马来西亚和印度尼西亚。 今天来聊一…

2023.8.13百度之星(第二场)第一题官方题解注释说明

第一题&#xff1a;星际航行 #include<bits/stdc.h> using namespace std; int n, a[3][100010]; int tmp[100010]; long long calc(int *a,int opt){//opt等于0表示回合到同一个点花费的最小代价&#xff0c;opt等于1表示将数列排序成连续的整数数列for(int i1;i<n;i…

Spring基础(2w字---学习总结版)

目录 一、Spirng概括 1、什么是Spring 2、什么是容器 3、什么是IoC 4、模拟实现IoC 4.1、传统的对象创建开发 5、理解IoC容器 6、DI概括 二、创建Spring项目 1、创建spring项目 2、Bean对象 2.1、创建Bean对象 2.2、存储Bean对象&#xff08;将Bean对象注册到容器…

DevEco Studio中如何设置HarmonyOS/OpenHarmony应用开发

DevEco Studio内置有帮助中心&#xff0c;初学HarmonyOS 及OpenHarmony应用、元服务的开发者&#xff0c;通过内置的帮助中去系统的学习相关内容&#xff0c;是边练边学&#xff0c;快速上手的最佳方式。 一、帮助 二、快速开始 三、HarmonyOS应用、元服务开发相关 四、OpenHa…

c语言练习57:浮点数在内存中的存储

浮点数在内存中的存储 上⾯的代码中&#xff0c; num 和 *pFloat 在内存中明明是同⼀个数&#xff0c;为什么浮点数和整数的解读结果会差别 这么⼤&#xff1f; 要理解这个结果&#xff0c;⼀定要搞懂浮点数在计算机内部的表⽰⽅法。 根据国际标准IEEE&#xff08;电⽓和电⼦⼯…