登录
注册
开源
企业版
高校版
搜索
帮助中心
使用条款
关于我们
开源
企业版
高校版
私有云
模力方舟
AI 队友
登录
注册
代码拉取完成,页面将自动刷新
捐赠
捐赠前请先登录
取消
前往登录
扫描微信二维码支付
取消
支付完成
支付提示
将跳转至支付宝完成支付
确定
取消
Watch
不关注
关注所有动态
仅关注版本发行动态
关注但不提醒动态
24
Star
56
Fork
2
Java技术交流
/
Java技术提升库
代码
Issues
56
Pull Requests
0
Wiki
统计
流水线
服务
JavaDoc
PHPDoc
质量分析
Jenkins for Gitee
腾讯云托管
腾讯云 Serverless
悬镜安全
阿里云 SAE
Codeblitz
SBOM
我知道了,不再自动展开
更新失败,请稍后重试!
移除标识
内容风险标识
本任务被
标识为内容中包含有代码安全 Bug 、隐私泄露等敏感信息,仓库外成员不可访问
04、数组数据结构特点?
待办的
#I23FHG
wgy
成员
创建于
2020-10-31 11:43
### 典型回答 数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存 储一组具有相同类型的数据。 第一是线性表(Linear List)。顾名思义,线性表就是数据排成像一条线一样的结构。每个 线性表上的数据最多只有前和后两个方向。其实除了数组,链表、队 列、栈等也是线性表结构。   而与它相对立的概念是非线性表,比如二叉树、堆、图等。之所以叫非线性,是因为,在非 线性表中,数据之间并不是简单的前后关系。  第二个是连续的内存空间和相同类型的数据。正是因为这两个限制,它才有了一个堪称“杀 手锏”的特性:“随机访问”。但有利就有弊,这两个限制也让数组的很多操作变得非常低效, 比如要想在数组中删除、插入一个数据,为了保证连续性,就需要做大量的数据搬移工作。 数组是如何实现根据下标随机访问数组元素的? 我们拿一个长度为 10 的 int 类型的数组 int[] a = new int[10]来举例。在我画的这个图中,计 算机给数组 a[10],分配了一块连续内存空间 1000~1039,其中,内存块的 首地址为 base_address = 1000。  我们知道,计算机会给每个内存单元分配一个地址,计算机通过地址来访问内存中的数据。 当计算机需要随机访问数组中的某个元素时,它会首先通过下面的寻址公式,计算出该元素 存储的内存地址: **a[i]_address = base_address + i * data_type_size** 其中 data_type_size 表示数组中每个元素的大小。我们举的这个例子里,数组中存储的是 int 类型数据,所以 data_type_size 就为 4 个字节。 这里要特别纠正一个“错误”。我在面试的时候,常常会问数组和链表的区别,很多人都回答 说,“链表适合插入、删除,时间复杂度 O(1);数组适合查找,查找时间复杂度为 O(1)”。 实际上,这种表述是不准确的。数组是适合查找操作,但是查找的时间复杂度并不为 O(1)。 即便是排好序的数组,你用二分查找,时间复杂度也是 O(logn)。所以,正确的表述应该是, 数组支持随机访问,根据下标随机访问的时间复杂度为 O(1)。 第三低效的“插入”和“删除” : 数组为了保持内存数据的连续性,会导致插入、删除这两个操作比较低效。 现在我们就来详细说一下,究竟为什么会导致低效?又有哪些改进方法呢? 我们先来看插入操作。 假设数组的长度为 n,现在,如果我们需要将一个数据插入到数组中的第 k 个位置。为了把 第 k 个位置腾出来,给新来的数据,我们需要将第 k~n 这部分的元素都顺 序地往后挪一 位。那插入操作的时间复杂度是多少呢?你可以自己先试着分析一下。 如果在数组的末尾插入元素,那就不需要移动数据了,这时的时间复杂度为 O(1)。但如果 在数组的开头插入元素,那所有的数据都需要依次往后移动一位,所以最坏时间复杂度是 O(n)。因为我们在每个位置插入元素的概率是一样的,所以平均情况时间复杂度为 (1+2+…n)/n=O(n)。 如果数组中的数据是有序的,我们在某个位置插入一个新的元素时,就必须按照刚才的方法 搬移 k 之后的数据。但是,如果数组中存储的数据并没有任何规律,数组只是被当作一个存 储数据的集合。在这种情况下,如果要将某个数组插入到第 k 个位置,为了避免大规模的数 据搬移,我们还有一个简单的办法就是,直接将第 k 位的数据搬移到数组元素的最后,把新 的元素直接放入第 k 个位置。 为了更好地理解,我们举一个例子。假设数组 a[10]中存储了 如下 5 个元素:a,b,c,d,e。 我们现在需要将元素 x 插入到第 3 个位置。我们只需要将 c 放入到 a[5],将 a[2]赋值为 x 即 可。最后,数组中的元素如下: a,b,x,d,e,c。  利用这种处理技巧,在特定场景下,在第 k 个位置插入一个元素的时间复杂度就会降为 O(1)。 我们再来看删除操作。 跟插入数据类似,如果我们要删除第 k 个位置的数据,为了内存的连续性,也需要搬移数据,不然中间就会出现空洞,内存就不连续了。 和插入类似,如果删除数组末尾的数据,则最好情况时间复杂度为 O(1);如果删除开头的 数据,则最坏情况时间复杂度为 O(n);平均情况时间复杂度也为O(n)。 实际上,在某些特殊场景下,我们并不一定非得追求数组中数据的连续性。如果我们将多次删除操作集中在一起执行,删除的效率是不是会提高很多呢? 我们继续来看例子。数组a[10]中存储了 8 个元素:a,b,c,d,e,f,g,h。现在,我们要依次删除 a,b,c 三个元素。  为了避免 d,e,f,g,h 这几个数据会被搬移三次,我们可以先记录下已经删除的数据。 每次的删除操作并不是真正地搬移数据,只是记录数据已经被删除。当数组没有更多空间存储数据时,我们再触发执行一次真正的删除操作,这样就大大减少了删除操作导致的数据搬 移。 如果你了解JVM,你会发现,这不就是JVM 标记清除垃圾回收算法的核心思想吗?没错, 数据结构和算法的魅力就在于此,很多时候我们并不是要去死记硬背某个数据结构或者算法,而是要学习它背后的思想和处理技巧,这些东西才是最有价值的。如果你细心留意,不 管是在软件开发还是架构设计中,总能找到某些算法和数据结构的影子。
### 典型回答 数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存 储一组具有相同类型的数据。 第一是线性表(Linear List)。顾名思义,线性表就是数据排成像一条线一样的结构。每个 线性表上的数据最多只有前和后两个方向。其实除了数组,链表、队 列、栈等也是线性表结构。   而与它相对立的概念是非线性表,比如二叉树、堆、图等。之所以叫非线性,是因为,在非 线性表中,数据之间并不是简单的前后关系。  第二个是连续的内存空间和相同类型的数据。正是因为这两个限制,它才有了一个堪称“杀 手锏”的特性:“随机访问”。但有利就有弊,这两个限制也让数组的很多操作变得非常低效, 比如要想在数组中删除、插入一个数据,为了保证连续性,就需要做大量的数据搬移工作。 数组是如何实现根据下标随机访问数组元素的? 我们拿一个长度为 10 的 int 类型的数组 int[] a = new int[10]来举例。在我画的这个图中,计 算机给数组 a[10],分配了一块连续内存空间 1000~1039,其中,内存块的 首地址为 base_address = 1000。  我们知道,计算机会给每个内存单元分配一个地址,计算机通过地址来访问内存中的数据。 当计算机需要随机访问数组中的某个元素时,它会首先通过下面的寻址公式,计算出该元素 存储的内存地址: **a[i]_address = base_address + i * data_type_size** 其中 data_type_size 表示数组中每个元素的大小。我们举的这个例子里,数组中存储的是 int 类型数据,所以 data_type_size 就为 4 个字节。 这里要特别纠正一个“错误”。我在面试的时候,常常会问数组和链表的区别,很多人都回答 说,“链表适合插入、删除,时间复杂度 O(1);数组适合查找,查找时间复杂度为 O(1)”。 实际上,这种表述是不准确的。数组是适合查找操作,但是查找的时间复杂度并不为 O(1)。 即便是排好序的数组,你用二分查找,时间复杂度也是 O(logn)。所以,正确的表述应该是, 数组支持随机访问,根据下标随机访问的时间复杂度为 O(1)。 第三低效的“插入”和“删除” : 数组为了保持内存数据的连续性,会导致插入、删除这两个操作比较低效。 现在我们就来详细说一下,究竟为什么会导致低效?又有哪些改进方法呢? 我们先来看插入操作。 假设数组的长度为 n,现在,如果我们需要将一个数据插入到数组中的第 k 个位置。为了把 第 k 个位置腾出来,给新来的数据,我们需要将第 k~n 这部分的元素都顺 序地往后挪一 位。那插入操作的时间复杂度是多少呢?你可以自己先试着分析一下。 如果在数组的末尾插入元素,那就不需要移动数据了,这时的时间复杂度为 O(1)。但如果 在数组的开头插入元素,那所有的数据都需要依次往后移动一位,所以最坏时间复杂度是 O(n)。因为我们在每个位置插入元素的概率是一样的,所以平均情况时间复杂度为 (1+2+…n)/n=O(n)。 如果数组中的数据是有序的,我们在某个位置插入一个新的元素时,就必须按照刚才的方法 搬移 k 之后的数据。但是,如果数组中存储的数据并没有任何规律,数组只是被当作一个存 储数据的集合。在这种情况下,如果要将某个数组插入到第 k 个位置,为了避免大规模的数 据搬移,我们还有一个简单的办法就是,直接将第 k 位的数据搬移到数组元素的最后,把新 的元素直接放入第 k 个位置。 为了更好地理解,我们举一个例子。假设数组 a[10]中存储了 如下 5 个元素:a,b,c,d,e。 我们现在需要将元素 x 插入到第 3 个位置。我们只需要将 c 放入到 a[5],将 a[2]赋值为 x 即 可。最后,数组中的元素如下: a,b,x,d,e,c。  利用这种处理技巧,在特定场景下,在第 k 个位置插入一个元素的时间复杂度就会降为 O(1)。 我们再来看删除操作。 跟插入数据类似,如果我们要删除第 k 个位置的数据,为了内存的连续性,也需要搬移数据,不然中间就会出现空洞,内存就不连续了。 和插入类似,如果删除数组末尾的数据,则最好情况时间复杂度为 O(1);如果删除开头的 数据,则最坏情况时间复杂度为 O(n);平均情况时间复杂度也为O(n)。 实际上,在某些特殊场景下,我们并不一定非得追求数组中数据的连续性。如果我们将多次删除操作集中在一起执行,删除的效率是不是会提高很多呢? 我们继续来看例子。数组a[10]中存储了 8 个元素:a,b,c,d,e,f,g,h。现在,我们要依次删除 a,b,c 三个元素。  为了避免 d,e,f,g,h 这几个数据会被搬移三次,我们可以先记录下已经删除的数据。 每次的删除操作并不是真正地搬移数据,只是记录数据已经被删除。当数组没有更多空间存储数据时,我们再触发执行一次真正的删除操作,这样就大大减少了删除操作导致的数据搬 移。 如果你了解JVM,你会发现,这不就是JVM 标记清除垃圾回收算法的核心思想吗?没错, 数据结构和算法的魅力就在于此,很多时候我们并不是要去死记硬背某个数据结构或者算法,而是要学习它背后的思想和处理技巧,这些东西才是最有价值的。如果你细心留意,不 管是在软件开发还是架构设计中,总能找到某些算法和数据结构的影子。
评论 (
0
)
登录
后才可以发表评论
状态
待办的
待办的
进行中
已完成
已关闭
负责人
未设置
标签
未设置
标签管理
里程碑
03.算法和数据结构面试题
未关联里程碑
Pull Requests
未关联
未关联
关联的 Pull Requests 被合并后可能会关闭此 issue
分支
未关联
未关联
master
开始日期   -   截止日期
-
置顶选项
不置顶
置顶等级:高
置顶等级:中
置顶等级:低
优先级
不指定
严重
主要
次要
不重要
参与者(1)
1
https://gitee.com/beike-java-interview-alliance/java-interview.git
git@gitee.com:beike-java-interview-alliance/java-interview.git
beike-java-interview-alliance
java-interview
Java技术提升库
点此查找更多帮助
搜索帮助
Git 命令在线学习
如何在 Gitee 导入 GitHub 仓库
Git 仓库基础操作
企业版和社区版功能对比
SSH 公钥设置
如何处理代码冲突
仓库体积过大,如何减小?
如何找回被删除的仓库数据
Gitee 产品配额说明
GitHub仓库快速导入Gitee及同步更新
什么是 Release(发行版)
将 PHP 项目自动发布到 packagist.org
评论
仓库举报
回到顶部
登录提示
该操作需登录 Gitee 帐号,请先登录后再操作。
立即登录
没有帐号,去注册