数据结构之数组
本文创作灵感来源于 极客时间 王争老师的《数据结构与算法之美》课程,通过课后反思以及借鉴各位学友的发言总结,现整理出自己的知识架构,以便日后温故知新,查漏补缺。
是什么
什么是数组
- 数组是一种线性表数据结构,在内存空间中占据连续的空间,并且存储着相同类型的数据。
为什么
为什么要出现数组这种数据结构
- 把相同类型的一系列数据统一编制到某一个组别中,这样就可以通过数组名+索引号简单快捷的操作大量数据。
怎么办
怎么学习数组
- 学习数组前,我们先理解数组定义中的两个概念。
- 线性表:线性表结构规定了数组只有前后两个方向。链表,队列,栈也属于线性表数据结构。而与之相对立的就是非线性表,例如:二叉树,堆,图等,这些数据结构中,数据与数据之间的关系,并非简单的前后关系。
- 连续的内存空间和相同的数据类型:连续的内存空间,说明了数组中的元素可以随机访问,相同的数据类型表明了数组用法的限制,只能存储相同类型数据。
数组的插入和删除
- 有序数组的插入:在有序数组中插入数据,如果在数组第一个位置插入数据,那么数组中原有的所有元素都需要往后移动一位,时间复杂度为 O(n) 。如果在数组的最后一个位置插入数据,时间复杂度为 O(1) 。平均时间复杂度就是 O(n) ,出现这种现象是因为数组的存储空间是连续的。
- 无序数组的插入:首先,我们可以按照有序数组的插入方法插入数据,但是时间复杂度为 O(n) ,接着我们对插入方法进行如下优化。当我们往数组中除末尾以外的任何位置插入数据的时候,可以把要插入位置的已有元素放在数组末尾,要插入的数据放在盖插入位置,那么时间复杂度就一直是 O(1)
- 直接删除:由于数据的存储空间是连续的,所以我们删除数组中除末尾以为的任何元素都需要将后续元素往前移动一位,那么平均时间复杂度就是 O(n) 。
- 高效删除:对直接删除方法进行优化,我们可以对要删除的元素做删除标记,等到要删除的元素个数积累到一定数量的时候,我们再进行统一删除,这样平均时间复杂度一定小于 O(n) ,删除操作变得高效了。这种标记删除正是JVM标记清除回收垃圾算法的核心思想。
数组的容器
- 在JAVA语言中,数组的容器类就是 ArrayList 。ArrayList 封装了数组的基本操作,同时提供了数组的动态扩容。所谓的动态扩容就是在当前数组内存空间用完之后,重新申请比当前空间大1.5倍的新数组,并把旧数组的元素搬移到新的数组中。
- 优缺点:数组的容器封装了数组的基本操作方法,使得我们操作数据更方便快捷。但是容器类不能存储基本数据类型,需要对基本类型进行装箱然后才能存储,这样就增加了装箱拆箱的性能损耗。
- 用法建议:王争老师给出的建议:如果开发的是一些底层框架,追求性能,那就选择数组。如果事先已知数组的大小,无需扩容,那就直接使用数组。当表示多维数组的时候,用数组更加直观。但是在业务开发过程中,直接使用容器类更加方便省时,损耗一丢丢性能,带来工作上的方便,还是很划得来。
数组的下标为什么是从0开始
- 一个是历史原因:C语言设计之初就是从0开始,后续的高级语言也遵从了C语言的设计风格。
- 二:数组中的下标其实代表的是当前元素地址的偏移量,由于数组是连续空间,后续元素存储的位置都是相对于首地址的偏移量,第一个元素偏移量就是0,第二个元素偏移量就是1,这样CPU寻址的时候:地址=占用内存 数组下标+首地址。如果下标是从1开始,那么:地址=占用内存 (数组下标-1)+首地址。
总结
数组的查找时间复杂度并不是 O(1) ,数组支持随机访问,根据下标随机访问的时间复杂度才是 O(1) 。