数组,是平常开发中用到的最多的数据结构之一,说到这里,可能有人就不赞同,“怎么我学的数组不是数据结构呢?”,看完这篇文章,我相信你可以本身判断!python
以前,本身一度认为数组太简单了,不就是经过下标拿元素,遍历数组,查找排序等操做。直到看了大佬对数组的介绍,才发觉本身真是坐井观天,对数组的理解太浅显了。算法
数组专业一点来讲是属于“线性表”的一种,它用一组连续的内存空间来存储具备相同类型的数据。编程
常见的线性表结构有链表、数组、栈、队列,顾名思义线性表就是数据排列在一条线上,每一个线性表上的数据最多只有先后两个方向。windows
非线性表结构则比线性表更复杂,非线性表上的数据并非简单的先后关系,常见的非线性表数据结构有图、树、堆等。数组
经过上面的图,咱们能明显看到线性表和非线性表的不一样之处。线性表只有先后关系,非线性表则存在多种关系。网络
继续说数组,上面说到来数组是线性表的一种,而且数组是连续的内存空间,相同的数据类型,因此数组有经过下标“随机访问”的特性;数据结构
可是这也带来了一些弊端,就是数组是连续的,致使删除和插入的时候为了保证连续性须要进行大量的数据迁移。编程语言
数据的访问,那到底数组是怎么经过下标随机访问元素的呢?学习
拿一个长度为10的int类型数组 int[] a = new int[10]; 计算机会给数组分配连续的内存空间 1000~1039,其中内存首地址为base_address = 1000;优化
咱们知道计算机会给每一个内存单元分配一个地址,计算机经过这个内存地址访问内存中的数据。
数组中因为内存是连续的,因此咱们能够经过基地址直接计算出对应的下标所在的内存地址。数组中计算某个下标元素的内存地址公示以下:
a[i]_address = base_address + i * data_type_size
在本例子中,数组中存储的是int类型数据(js中数组每一个元素大小能够不一样,是作了特殊处理,js中具体数组在内存中如何存储待研究;
data_type_size为4个字节,因此若是要获取下标为2的元素的地址,经过上面的公式计算就获得来内存地址为1008,因为只计算一次就能获取到准确的内存地址,因此访问数组中某个元素的时间复杂度为:
为何说数组的“插入”和“删除”操做很低效呢?咱们知道数组在内存中是连续的,若是进行插入或者删除操做的话,因为数组要保证内存数据的连续性,因此数据须要进行移动,来保证连续性。
下面举例说明:
插入操做:假设数组的长度为n,如今,若是咱们须要将一个数据插入到数组中的第k个位置。为了把第k个位置腾出来,给新来的数据,咱们须要将第k~n这部分的元素都顺 序地日后挪一位。那插入操做的时间复杂度是多少呢?咱们能够分析一下。
分析:若是第k个位置是数组末尾,那么不须要移动数据,此时最好时间复杂度为:
若是在数组的开头插入数据,则全部的数据都要日后移动一位,因此最坏时间复杂度为:
由于咱们在每一个位置插入的元素的几率是同样的,因此平均时间复杂度为:
经过上面的分析咱们看到,插入操做的平均时间复杂度为:
那么有没有可以优化的地方呢?
实际上是有的,可是只能针对特定的状况进行优化。
好比当数组仅仅做为存储数据的集合而不在意存储的数据的顺序时,咱们能够直接将第k个元素放在数组最后,将要插入的元素放在第k个位置。这样就能作到时间复杂度为
但这也致使了快排是一个不稳定的排序算法
举个例子来看一下上面的优化方式:假设数组a[10]中存储了以下5个元素:a,b,c,d,e。 咱们如今须要将元素x插入到第3个位置。咱们只须要将c放入到a[5],将a[2]赋值为x便可。最后,数组中的元素以下: a,b,x,d,e,c。以下图所示:
删除操做:存在长度为n的数组,如今须要删除第k个元素,删除第k个元素后为了保证数组中第k个位置不出现空洞致使数组内存不连续,因此须要将k~n的元素都要向前移动一位。咱们依旧分析一下时间删除操做的时间复杂度。
分析:
若是删除的是最后一个元素,则不须要移动,因此最好时间复杂度为:
删除的是第一个元素,那么后面的k~n个元素都要向前移动一位,因此最坏时间复杂度为:
在每一个位置删除的几率是同样的,那么平均时间复杂度为:
那么删除操做可否优化呢?
其实也是能够的,咱们经过上面的例子知道每删除一个元素就要移动后面的全部元素,那么咱们能不能把多个删除操做放在一块儿,统一进行一次数据移动呢?
举个例子来看一下上面的优化方式:数组a[10]中存储了8个元素:a,b,c,d,e,f,g,h。如今,咱们要依次删除a,b,c三个元素。
为了不d,e,f,g,h这几个元素被移动三次,每次删除操做的时候并不去直接进行数据搬移,而是把要删除的元素标志为已删除,当数组中须要插入元素可是发现数组已经满了以后,再将标记为删除的元素真正的删除,这样就大大减小了删除操做致使的数据迁移。
咱们以前有计算过数组的内存寻址公式 a[i]_address = base_address + i * data_type_size;
若是从1开始的话,咱们能够获得对应的内存寻址公式为 a[i]_address = base_address + (i - 1) * data_type_size,每次随机访问一个元素都多了一个减法操做。
数组是一种很基础的数据结构,效率须要尽量的高,因此为了减去一次减法操做,因此数组选择了从0开始编号而不是从1开始。
固然也并非非0不可,有的语言甚至支持负数的下标,例如python。可是大多数都是从0开始编号,可能也和C语言以0做为下标有关,后面的语言或多或少的都借鉴了C语言。
咱们今天学习了数组。它能够说是最基础、最简单的数据结构了。
数组用一块连续的内存空间,来存储相同类型的一组数据,最大的特色就是支持随机访问,但插入、删除操做也所以变得比较低效,平均状况时间复杂度为O(n),咱们也了解到了一维数组的寻址公式,和插入和删除操做的优化方法。
今天咱们就学习到这里吧!有什么好的想法和建议欢迎下方评论留言~
无论你是转行也好,初学也罢,进阶也可
——【值得关注】个人C/C++编程学习进阶俱乐部 ——
涉及到:C语言、C++、windows编程、网络编程、QT界面开发、Linux编程、游戏编程、黑客等等......