今天要介绍的主角就是-数组,数组也是数据呈线性排列的一种数据结构。与前一节中的链表不一样,在数组中,访问数据十分简单,而添加和删除数据比较耗工夫。这和什么是数据结构那篇文章中讲到的姓名按拼音顺序排列的电话簿相似。程序员
如上就是数组的概念图,Blue、Yellow、Red 做为数据存储在数组中,其中 a 是数组的名字,后面 [] 中的数字表示该数据是数组中的第几个数据,该数字也就是数组下标,下标从 0 开始计数,好比 Red 就是数组 a 的第 2 个数据。算法
那么为何许多编程语言中的数组都从 0 开始编号的呢?先别急,能够先本身思考下,将会在文末进行讲解。编程
从图中能够看出来,数组的数据是按顺序存储在内存的连续空间内的。数组
因为数据是存储在连续空间内的,因此每一个数据的内存地址(在内存上的位置)均可以经过数组下标算出,咱们也就能够借此直接访问目标数据,也就是随机访问。数据结构
好比如今咱们想要访问 Red,若是是链表的话,只能使用指针就只能从头开始查找,但在数组中,只须要指定 a[2],便能直接访问 Red。编程语言
可是,若是想在任意位置上添加或者删除数据,数组的操做就要比链表复杂多了。这里咱们尝试将 Green 添加到第 2 个位置上。ide
首先,在数组的末尾确保须要增长的存储空间。学习
为了给新数据 Green 腾出位置,要把已有数据一个个移开,首先把 Red 日后移。优化
而后把 Yellow 日后移。线程
最后在空出来的位置上写入 Green。
添加数据的操做就完成了。
反过来,若是想要删除 Green 呢?
首先,删掉目标数据 Green。
而后把后面的数据一个个往空位移,先把 Yellow 往前移。
接下来移动 Red。
最后再删掉多余的空间,这样一来 Green 便被删掉了。
这里讲解一下对数组操做所花费的运行时间,假设数组中有 n 个数据,因为访问数据时使用的是随机访问(经过下标可计算出内存地址),因此须要的运行时间仅为恒定的 O(1)。
经过数组下标计算出内存地址的寻址公式以下:
a[i]_address = base_address + i * data_type_size
其中 base_address 为内存块的首地址,data_type_size 表示数组中每一个元素的大小。
但另外一方面,想要向数组中添加新数据时,必须把目标位置后面的数据一个个移开。因此,若是在数组头部添加数据,就须要 O(n) 的时间,删除操做同理。
在链表和数组中,数据都是线性地排成一列。在链表中访问数据较为复杂,添加和删除数据较为简单;而在数组中访问数据比较简单,添加和删除数据却比较复杂。
咱们能够根据哪一种操做较为频繁来决定使用哪一种数据结构。
最后,让咱们一块儿来思考下刚开始提到的问题:为何不少编程语言中数组都从 0 开始编号?
从数组存储的内存模型上来看,“下标”最确切的定义应该是“偏移(offset)”。若是用 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 开始。
除此以外还有历史缘由,C 语言设计者用 0 开始计数数组下标,以后的 Java、JavaScript 等高级语言都效仿了 C 语言,或者说,为了在必定程度上减小 C 语言程序员学习 Java 的学习成本,所以继续沿用了从 0 开始计数的习惯。实际上,不少语言中数组也并非从 0 开始计数的,好比 Matlab。甚至还有一些语言支持负数下标,好比 Python。
这篇文章主要介绍了数据结构中经常使用的数组,数组用一块连续的内存空间,来存储相同类型的一组数据,最大的特色就是支持随机访问,但插入、删除操做也所以变得比较低效,平均状况时间复杂度为 O(n)。有一种高效的查找算法是二分查找法,就是利用了数组随机访问的特性。
总得来讲,数组适用于多操做多、写操做少的场景,和咱们上一篇文章中的链表正好相反。
参考
《个人第一本算法书》
数据结构与算法之美
完
●什么是数据结构?
●什么是链表?
●实现线程的方式到底有几种?
武培轩有帮助?在看,转发走一波喜欢做者