数据结构与算法(三) 数组

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lwl2014100338/article/details/84493081
如何实现随机访问?

(1)数组是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据

(2)线性表就是数据排成像一条线一样的结构。每个线性表上的数据最多只有前和后两个方向。其中除了数组,链表、队列、栈等也是线性表结构
在这里插入图片描述

(3)与它相对立的概念是非线性表,比如二叉树、堆、图等。之所以叫非线性,是因为,在非线性表中,数据之间并不是简单的前后关系
在这里插入图片描述

(4)连续的内存空间和相同的数据类型。正是因为这两个限制,它才有一个特性:“随机访问”。但是这两个限制也让数组的很多操作变得非常低效,比如要想在数组中删除、插入一个数据,为了保证连续性,就需要做大量的数据搬移工作

(5)计算机给每个内存单元分配一个地址,计算机通过地址来访问内存中的数据。当计算机需要随机访问数组中的某个元素时,它首先会通过寻址公式,计算出该元素的内存地址。

(6)数组是适合查找操作,但是查找的时间复杂度并不是O(1)。即便是排好序的数组,你用二分查找,时间复杂度也是O(logn)。所以,正确的表述是,数组支持随机访问,根据下标随机访问的时间复杂度为O(1)


低效的“插入”和“删除”

(1)插入平均时间复杂度为(1+2+…+n)/n=O(n)

(2)如果数组只是被当做一个存储数据的集合。在这种情况下,如果将某个数插入到第k个位置,为了避免大规模的数据搬移,我们可以直接将第k位的数据搬移到数组元素的最后,把新的元素直接放到第k个位置,利用这种巧妙的处理,在特定场景下,在第k个位置插入一个元素的时间复杂度就会降为O(1)
在这里插入图片描述

(3)删除平均时间复杂度为也为O(n)

(4)对于多次删除操作可以集中在一起执行,可以先记录下已经删除的数据。每次删除操作并不是真正地搬移数据,只是记录数据已经被删除。当数据没有更多空间存储数据时,再触发执行一次真正的删除操作,这样大大减少了删除操作导致的数据搬移,这也是JVM标记清除垃圾回收算法的核心思想

在这里插入图片描述


容器能否完全替代数组

(1)ArrayList最大的优势是可以将很多数组操作的细节 封装起来,另一个优势就是支持动态扩容

(2)扩容涉及内存申请和数据搬移,是比较耗时的。所以,如果事先可以确定需要存储的数据大小,最好在创建ArrayList的时候指定数据大小

数组更适合的场景
(1)Java中ArrayList无法存储基本类型,比如int 、long等,需要封装为integer、Long类,而Autoboxing则有一定的性能消耗,所以如果特别关注性能,或者希望使用基本类型,就可以使用数组

(2)如果数据大小已知,并且对数据的操作非常简单,用不到ArrayList提供的大部分方法,就可以选用数组

总结:对于业务开发,直接使用容器就足够了。损耗一丢丢性能,完全不会影响到系统整体性能,但是如果你做一些非常底层的开发,比如开发网络框架,性能的优化需要做到极致,这个时候数组优先于容器


数组编号为何要从0开始

(1)从数组存储的内存模型上来看,“下标”最确切的定义应该是“偏移”。如果用a来表示数组的首地址,a[0]就是偏移量为0的位置,也就是首地址,a[K]就表示偏移k个type_size的位置,所以计算a[K]的内存地址只需要用这个公式

a[K]_address=base_address+k* type_size

但是,如果数组从1开始计数,那我们计算数组元素a[K]的内存地址就会变为

a[K]_address=base_address+(k-1)* type_size

从1开始编号,每次随机访问数组都会多一次减法运算,对于CPU来说,就是多了一次减法指令,所以为了减少一次减法操作,数组选择从0开始编号,而不是从1开始

(2)C语言设计者用0开始计数数组下标,之后的高级语言都效仿了C语言,是为了减少C语言程序员学习Java的学习成本,因此沿用了从0开始计数的习惯


猜你喜欢

转载自blog.csdn.net/lwl2014100338/article/details/84493081
今日推荐