动态数组

数组的局限性

目前为止所实现的数组类,有一个非常严重的局限性,就是这个数组实际使用的还是一个静态数组,内部容量有限。在实际使用的时候,我们往往无法预估要在这个数组中存入多少个元素

解决方案

在这种情况下,如果容量首次开太大,可能会浪费很多空间,但容量太小,又有可能不够用。这时候,需要有一种解决方案使得这个数组的容量是可伸缩的,也就是所谓的动态数组

思路

  1. 首先,原数组data,容量capacity为4,数组中元素size为4
  2. 然后新开一个数组new data(原数组data),开的空间要比原来大一些(从4–>8)
  3. 遍历原数组data,赋值到new data中。此时容量capacity为8,数组中的元素size为4
  4. 本身data指向4个空间的数组,现在指向8个空间的数组(new data也指向它)

图解操作:

  • 总结
    整个过程封装在一个函数内,对于new data这个变量在函数执行完便失效了,而data由于是类的成员变量,与整个类的生存周期一致(只要类还在使用,data就是有效的)
  • 细节
    对于原来4个空间的数组,由于没有对象指向它,所以利用java的垃圾回收机制将其回收

具体代码实现

 
// 将数组空间的容量变成newCapacity大小
public void resize(int newCapacity) {E[] newData = (E[]) new Object[newCapacity];for (int i = 0; i < size; i++) {newData[i] = data[i];}// 由newData指向datadata = newData;
}

动态数组的优势

使用上述方法,让我们这个数组类拥有容量的动态伸缩的能力,所以在用这个数组类的时候,使用者不必关心数组容量是否够用的问题

简单的时间复杂度分析

前提

到目前为止,都主要以编程的思想来实现我们代码的逻辑,而对于这段代码的性能方面,我们一无所知。因此才需要使用复杂度分析的方式,来解析我们的代码

定义

  • 通常我们用O(1),O(n),O(logn),O(n^2)来描述一个算法的时间复杂度
  • O描述的是算法的运行时间和输入数据之间的关系

以代码做演示:

注:实际上,我们忽略了很多常数。比如for循环里,从nums数组中取数,还有sum相加的过程等等,所花的时间都是常量。

  • 实际时间T=c1*n+c2,c1表示n次操作每次所耗费的时间常数,c2表示完成算法内其他操作所耗费时间
  • 为什么用大O,叫O(n)? ,因为忽略常数,实际时间T=c1*n+c2
  • 举例:
  • T = 2*n  + 2                     O(n)
  • T = 2000*n  + 10000       O(n)
  • T =1*n*n  + 0                   O(n^2)  渐进时间复杂度,描述n趋近无穷的情况
  • T =2*n*n  + 300n + 10      O(n^2)

分析动态数组的时间复杂度

添加操作

  • addLast(e) 向数组末尾添加一个元素,O(1)意味着,消耗时间跟数据的规模大小无关,无论数组中有多少个元素,都能在常数时间里完成
  • addFirst(e)** O(n)
  • add(index, e) 取决于index的位置,考虑极端情况则是演变成addLast(e)的O(1)操作,亦或者是退变成addFirst(e)的O(n)的操作,平均操作O(n/2)=O(n)
  • 在算法时间复杂度分析上,通常我们关注的是最坏最糟糕的情况。

综上所述,对于我们的动态数组来说,添加操作时间复杂度是O(n)级别的。

删除操作

  • removeLast(e) 在末尾删除一个元素,O(1)意味着,消耗时间跟数据的规模大小无关,无论数组中有多少个元素,都能在常数时间里完成
  • removeFirst(e)** O(n)
  • remove(index, e) 取决于index的位置,考虑极端情况则是演变成removeLast(e)的O(1)操作,亦或者是退变成removeFirst(e)的O(n)的操作,平均操作O(n/2)=O(n)
  • 在算法时间复杂度分析上,通常我们关注的是最坏最糟糕的情况。

综上所述,对于我们的动态数组来说,删除操作时间复杂度是O(n)级别的。

修改操作

  • set(index, e) 是O(1)
  • 修改操作在动态数组中非常简单,只需要知道要修改的元素所对应的索引,直接利用set(index, e)。这个时间复杂度是O(1)级别的,这是数组最大的优势,专业术语是,支持随机访问。只要知道索引是谁,便可以一下子访问到它

查询操作(根据索引和元素进行查找)

  • get(index) :O(1)
  • contains(e) : O(n)
  • find(e) :        O(n)

总结:增: O(n); 删: O(n); 当只对最后一个元素操作依然是O(n),因为存在resize()

改:已知索引O(1),未知索引O(n);  查 : 已知索引O(1),未知索引O(n)

我们可以轻松的使用索引,去检索数组中的元素,那么在性能上便有非常强的优势。

resize()复杂度分析

防止复杂度震荡

复杂度震荡

  1. 假设现在我们有一个数组,容量是n,并且装满了元素。
  2. 这时候,我想添加一个元素,显然是需要进行扩容,容量变为2n,耗时O(n)的时间。
  3. 但是此时,我又删除了一个元素触发了缩容操作,耗时O(n)的时间。
  4. 当我们每次触发缩容或扩容操作,都会耗费O(n)额复杂度,那么这便是复杂度的震荡

分析

在特殊情况下,我们频繁的添加和删减操作,导致过于着急的去扩容或缩容
图解:

解决方案

可以采用一种相对懒惰的策略。

  • 比如说,一个满的数组,容量n,添加元素需要进行扩容,容量变为2n
  • 但在这时,在进行删除元素后,不立即进行缩容操作,而是再等等
  • 如果后面一直有删除操作的话,删除到整个数组容积的1/4,再触发缩容操作。缩容数组的1/2,而不是直接缩容到1/4
  • 此时,数组中存在1/4的元素,还预留了1/4的空间

通过这样的策略,防止了复杂度的震荡,从而有效的提升整体的性能

具体代码实现

 
 / 从数组中删除index位置的元素,返回删除的元素public E remove(int index) {if (index < 0 || index >= size) {throw new IllegalArgumentException("Remove failed. Index is illegal.");}E ret = data[index];for (int i = index + 1; i < size; i++) {data[i - 1] = data[i];}size--;// 端点(最后一位)要置空data[size] = null; // loitering objects != memory leakif (size == data.length / 4 && data.length / 2 != 0) {resize(data.length / 2);}return ret;}
  • 本文链接:https://loubobooo.com/2018/07/29/初学数据结构-动态数组/
  • 版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN 许可协议。转载请注明出处!

数据结构基础之动态数组相关推荐

  1. 2021-9-下旬 数据结构-线性表-动态数组-java代码实现

    信管的数据结构讲的太水了,遂重学(看的网课:恋上数据结构与算法,讲的very good),为算法学习打基础(都大三了还在打基础),顺便在leetcode上跟进一些相关的题,记录在这里,这里最方便复习 ...

  2. golang数据结构初探之动态数组slice

    动态数组slice slice 又称动态数组,依托于数组实现,可以方便的进行扩容和传递,实际使用时比数组更灵活.但正是因为灵活,实际使用时更容易出错,避免出错的最好方法便是了解其实现原理. 特性速览 ...

  3. 算法与数据结构基础四----数据结构基础之动态数据结构基础:链表上

    接着上一次https://www.cnblogs.com/webor2006/p/15195969.html的数据结构继续往下学习,这次会进入一个非常重要的数据结构的学习----链表,这个是未来学习复 ...

  4. java动态数组的实现的_Java实现数据结构之【动态数组】

    数组是学习编程语言时较先接触到的一种数据结构,本章基于Java的静态数组实现动态数组,并进行简单的复杂度分析 数组相信各位都知道,那什么是动态数组呢?我们定义一个数组后,一般长度会直接定义好,如果数组 ...

  5. 【数据结构基础】之数组介绍,生动形象,通俗易懂,算法入门必看

    前言 本文为数据结构基础数组相关知识,下边将对数组的定义.性质及结构,数组的各种玩法如循环遍历数组.查找数组最大值.数组元素的位移等,二维数组的定义及用法等进行详尽介绍~ Java全栈学习路线可参考: ...

  6. 数据结构基础之动态顺序表详解

    文章目录 前言 一.动态顺序表的概念 二.顺序表的结构体 三.基本接口 1.SeqListInit(初始化数组) 2.SeqListDestory(销毁数组) 3. SeqListCheckCapac ...

  7. 基于java的数据结构学习——泛型动态数组的封装

    public class Array<E> {private E[] data;private int size;// 构造函数public Array(int Capacity){dat ...

  8. 数据结构与算法 —— 动态数组

    一.基础与细节 1. 扩容策略 无论是 C++ STL 中的向量 vector 还是 Java Collections 中的 ArrayList,采用的扩容策略是_capacity <<= ...

  9. 基础数据结构【二】————动态数组,单向链表及链表的反转

    DEMO1:     动态分配变量(链表,而静态数组是线性表,意味着动态数组访问和遍历复杂度为O(n),而插入和删除复杂度为O(1),而静态数组线性表则完全相反) int* intptr = new ...

最新文章

  1. 谷歌Waymo自建车厂,L4级无人车量产指日可待
  2. CSS之window的视图属性
  3. MITRE 发布防御知识库 Shield
  4. 中文短文本的实体识别实体链接,第一名解决方案
  5. fasta文件中序列的排序
  6. 小程序中上传图片并进行压缩(二)
  7. 可以下载MapInfo地图吗?
  8. 【考研】2021年哈尔滨工业大学计算机考研「复试」经验贴
  9. 怎么在html中加水印,如何在h5网页添加水印
  10. SNMP原理和MIB库
  11. Android App应用市场功能的框架图
  12. 用文氏图表示映射满射单射及函数的关系
  13. php网页显示中文乱码的解决办法!
  14. 响度与响度处理经验谈(上)响度测量
  15. 为什么成年人都不学习了
  16. mac上Latex的安装及使用教程
  17. Zephyr参考文档
  18. 20051111: 最近玩WOW好像太多了
  19. 成都中科院计算机研究所招聘,2017年中科院成都计算机应用研究所考研复试分数线以及复试通知...
  20. 买网站服务器需要什么软件有哪些东西,建网站的软件有哪些,哪些常用?

热门文章

  1. sugarcrm_通过SugarCRM休息
  2. 做人要大方包容、善解人意,凡事包容,凡事相信,凡事盼望,凡事忍耐
  3. 一、pycharm的使用技巧和好用插件
  4. 内存溢出的几种原因和解决办法是什么?
  5. AI智能检测识别平台EasyCVR出现卡顿及反应慢的原因分析以及解决方法
  6. python安装pyparsing后提示No module named ‘pyparsing‘
  7. linux下搭建HTTP代理服务器
  8. 使用Docker加速器
  9. TYVJ 2054 [Nescafé29]四叶草魔杖 最小生成树 状态压缩/背包DP
  10. [CSS] CSS实现鼠标移入图片放大效果