
随机访问
数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。
线性表:
线性表就是数据排成像一条线一样的结构。每个线性表上的数据最多只有前和后两个方向。
除了数组,链表、队列、栈等也是线性表结构。
和线性表相反的改建是非线性表:二叉树、堆、图等。之所以叫非线性,是因为,在非线性表中,数据之间并不是简单的前后关系。
连续的内存空间和相同类型的数据:
正因为’线性表’和’连续的内存空间和相同类型的数据’,使得数组具有了随机访问的特性。
这两个限制也让数组的很多操作变得非常低效,比如要想在数组中删除、插入一个数据,为了保证连续性,就需要做大量的数据搬移工作。
“链表适合插入、删除,时间复杂度 O(1)
;数组适合查找,查找时间复杂度为 O(1)
”。
实际上,这种表述是不准确的。数组是适合查找操作,但是查找的时间复杂度并不为 ·、O(1)
。即便是排好序的数组,你用二分查找,时间复杂度也是 O(logn)
。所以,正确的表述应该是,数组支持随机访问,根据下标随机访问的时间复杂度为 O(1)
。
低效的’插入’、’删除’
插入操作:
假设数组的长度为 n,现在,如果需要将一个数据插入到数组中的第 k 个位置。为了把第 k 个位置腾出来,给新来的数据,我们需要将第 k~n 这部分的元素都顺序地往后挪一位。
果在数组的末尾插入元素,那就不需要移动数据了,这时的时间复杂度为 O(1)。但如果在数组的开头插入元素,那所有的数据都需要依次往后移动一位,所以最坏时间复杂度是 O(n)。 因为我们在每个位置插入元素的概率是一样的,所以平均情况时间复杂度为 (1+2+…n)/n=O(n)。
如果数组中的数据是有序的,在某个位置插入一个新的元素时,就搬移 k 之后的数据。但是,如果数组中存储的数据并没有任何规律,数组只是被当作一个存储数据的无序集合。在这种情况下,如果要将某个数据插入到第 k 个位置,为了避免大规模的数据搬移,还有一个简单的办法就是,直接将第 k 位的数据搬移到数组元素的最后,把新的元素直接放入第 k 个位置。
删除操作:
如果要删除第 k 个位置的数据,为了内存的连续性,也需要搬移数据,不然中间就会出现空洞,内存就不连续了。
和插入类似,如果删除数组末尾的数据,则最好情况时间复杂度为 O(1);如果删除开头的数据,则最坏情况时间复杂度为 O(n);平均情况时间复杂度也为 O(n)。
实际上,在某些特殊场景下,并不一定非得追求数组中数据的连续性。如果将多次删除操作集中在一起执行,删除效率会提高的很多
可以先记录下已经删除的数据。每次的删除操作并不是真正地搬移数据,只是记录数据已经被删除。当数组没有更多空间存储数据时,我们再触发执行一次真正的删除操作,这样就大大减少了删除操作导致的数据搬移。
警惕数组越界
简而言之,数组越界会给项目带来莫名其妙的逻辑错误,很难去调试,debug难度是很大的。
当然,并非所有语言都像C一样,把数组越界检查的工作丢给程序员来做。
容器
对于业务开发,直接使用容器就足够了,省时省力。毕竟损耗一丢丢性能,完全不会影响到系统整体的性能。
但如果做一些非常底层的开发,比如开发网络框架,性能的优化需要做到极致,这个时候数组就会优于容器,成为首选
- Post title:数组问题
- Post author:Varsion
- Create time:2020-07-30 21:58:14
- Post link:https://blog.varsion.cn/post/6856af34.html
- Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.