一、🍑 数组的定义
数组(Array)是一种线性表数据结构。它用一组连续的内存空间 ,来存储一组具有相同类型的数据。
1. 线性表数据结构
就是把数据排成像一条线一样的结构,每个线性表上的数据只有前和后两个方向,除了数组,像链表、队列、栈都是线性表结构,而二叉树、堆、图叫做非线性表,在非线性表中,数据不是简单的前后关系。
2. 连续的内存空间和相同类型的数据
正是因为有了这两个特性,所以数组能够'随机访问(随机选择下标,进行数据访问)',但是这两个限制也让数组的很多操作变得低效,比如在数组中删除或者插入输入,为了保证连续性,就需要做大量的数据搬移工作。
二、🍑 数组是如何实现根据下标随机访问数组元素
计算机给每个内存单元分配一个地址,计算机通过地址来访问内存中的数据,当计算机要随机访问数组中的元素时,它会首先通过寻址公式,计算出该元素的内存地址,举一个字符数组的例子,如图所示:
注意:
- 数组下标都是从0开始的。
- 数组内存空间的地址是连续的
三、🍑 数组和链表的区别
- 链表适合插入、删除,时间复杂度 O(1);
- 数组适合查找,因为数组支持随机访问,根据下标随机访问的时间复杂度为 O(1)。
四、🍑 数组低效的"插入"和"删除"
为了保证数据在内存存储时的连续性,在插入和删除时,需要对目标位置后面的数据进行整体后移和前移,所以插入和删除这两个操作比较低效。
1. 插入
假设数组的长度为 n,现在,如果我们需要将一个数据插入到数组中的第 k 个位置。为了把第 k 个位置腾出来,给新来的数据,我们需要将第 k~n 这部分的元素都顺序地往后挪一位。可以看出来插入的位置不同,会导致针对同一段代码,时间复杂度有量级的差距。
- 最好时间复杂度:我们插入的位置位于数组的最后一位,不需要移动任何元素,最好时间复杂度为O(1)。
- 最坏时间复杂度:我们插入的位置位于数组的第一位,需要移动n个元素,最坏时间复杂度为O(n)。
- 平均时间复杂度:先把概率算清, 插入到任一位置的可能性都是一样的,n个位置,所以插入到每一个位置的概率都是1/n. 插入到数组的第一个位置需要移动 n个元素. 插入到数组的第二个位置需要移动 n-1 个元素,...,插入到数组中的最后一个位置,需要移动1个元素,(n+n-1+n-2+...+1)/n=(n+1)/2=O(n) 所以: 数组插入操作的平均时间复杂度为O(n)
注意:如果数组中存储的数据并没有任何规律,数组只是被当作一个存储数据的集合。在这种情况下,如果要将某个数据插入到第 k 个位置,为了避免大规模的数据搬移,我们还有一个简单的办法就是,直接将第 k 位的数据搬移到数组元素的最后,把新的元素赋值给第 k 个位置。所以特定场景下,直接调换指定的位置数据就可以了,不需要把后面的数据整体移动,这样在第 k 个位置插入一个元素的时间复杂度就会降为 O(1)。
2. 删除
跟插入数据类似,如果我们要删除第 3个位置的数据,为了内存的连续性,需要对下标为3的元素后面的所有元素都要做移动操作,否则内存就不连续了。如下图
- 最好时间复杂度:删除最后一位,长度-1,复杂度O(1)。
- 最坏时间复杂度:删除第一位,剩下每个数据都向左移动1个位置一共n-1个元素共执行了n-1次操作,复杂度O(n)。
- 平均时间复杂度:概率是每个元素被删除的概率是1/n,平均操作次数:(n-1 + n-2 + n-3 + ... + 1 + 0)* 1/n = (n-1)/2。 去掉系数->复杂度O(n)。
注意:在某些特殊场景下,我们并不一定非得追求数组中数据的连续性. 将多次删除操作集中在一起执行,数组的删除效率也会提高很多。
五、🍑 为什么很多编程语言中,数组的下标都从0开始
数组"下标"最确切的定义应该是"偏移(offset)"。前面也讲到,如果用 a 来表示数组的首地址,a[0]就是偏移为 0 的位置,也就是首地址,a[k]就表示偏移 k 个 type_size 的位置,所以计算 a[k]的内存地址只需要用这个公式:
js
a[k]_address = base_address + k * type_size
但是,如果数组从 1 开始计数,那我们计算数组元素 a[k]的内存地址就会变为:
js
a[k]_address = base_address + (k-1) * type_size
对比一下,从 1 开始编号,每次随机访问数组元素都多了一次减法运算,对于 CPU 来说,就是多了一次减法指令。(人像机器妥协),数组作为基础的数据结构,通过下标访问数组元素也是基础的编程操作,所以为了减少一次减法操作,提升效率优化,数组选择了从 0 开始编号,而不是从 1 开始。
总结
数组作为最基础、最简单的数据结构了。数组用一块连续的内存空间,来存储相同类型的一组数据,最大的特点就是支持随机访问,但插入、删除操作也因此变得比较低效,平均情况时间复杂度为 O(n)。