登录
注册
开源
企业版
高校版
搜索
帮助中心
使用条款
关于我们
开源
企业版
高校版
私有云
模力方舟
AI 队友
登录
注册
Gitee 年度开源项目评选中~
代码拉取完成,页面将自动刷新
捐赠
捐赠前请先登录
取消
前往登录
扫描微信二维码支付
取消
支付完成
支付提示
将跳转至支付宝完成支付
确定
取消
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 帐号,请先登录后再操作。
立即登录
没有帐号,去注册