《数据结构与算法之美》-数组

《数据结构和算法之美》专题陆陆续续看了好几篇了,看到数组这篇了。刚看的时候心想着数组这东西基本都会用,还能讲出花来?看完后发现我还是有蛮大收获的,下面大概总结下。

数组是再寻常不过的数据结构了,几乎所有的编程语言应该都有数组这种结构吧,比如C、Java、Python。

什么是数组

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

两个要点

线性表

好久没有听过这个名词了,但是多少还有些印象。线性即表示是一条线,只有前和后,区别于非线性。

线性数据结构包括数组、链表、队列和栈等。

非线性,显然就不是一条线,其表现形式有多种,比如我们数据结构中学到的树、堆和图等。

连续内存空间和相同类型数据

可以简单的将随机访问的特性和连续内存空间和相同类型数据等价。上面的两个条件的限制,好比一个萝卜一个坑,从而使数组可以支持随机访问。

没有什么东西是完美的,数组也不例外。有了随机访问的优点,自然也有自己的不足,那就是插入和删除比较麻烦,时间复杂度较高。

数组插入和删除的时间复杂度

文中提到在面试的时候,经常会问到数组和链表的区别。很多人会提到一点:链表适合插入和删除,时间复杂度为O(1),数组适合查找,时间复杂度为O(1)。

这个解答不够严谨和准确。数组的查找时间复杂度不是O(1)。即使是排好序的数组,使用二分查找,时间复杂度也是O(logn)。

所以严谨表述应该是,数据支持随机访问,根据小标访问的时间复杂度为O(1)。

同时,链表插入的时间复杂度也不能单纯认为是O(1),因为首先需要定位到要插入或删除元素的节点位置,这个时间复杂度为O(n)。所以,严谨的说,是找到了要插入或删除的元素再进行操作的时间复杂度为O(1)。

重新看数组的插入和删除

数组插入

对于数据的插入,我的第一反应是找到要插入的位置,然后将所有后面的位置平移一个位置,这样的操作的时间复杂度是O(n)。

但是实际上,我们可以不用这么墨守成规,或者我们换个思路,把要插入的位置比如是k,该位置对应的元素移到数组的最后一位,然后将要插入的元素插入原来的k位置。

对于可能引起的数据无序可以通过其他高效算法接口,可以将时间复杂度降为O(logn)甚至O(1)。

所以算法是个很有趣的事情,一点思想的转变,可能就会带来性能的提升。

数组删除

数组删一般来说也是找到要删除的元素,将后面的数据都平移一个位置,时间复杂度为O(n)。

对于需要删除的多个元素,我们可以不用那么实诚,每次都切切实实的平移受影响的元素。我们可以标记需要删除的元素,最后一次性调整所有需要移动的元素。该思想和前段时间看JVM垃圾回收中的标记清楚的算法如出一辙,秒!

其他注意事项

  • 数据越界是常见的问题,要警惕

  • 容器和数组各有所长,写底层框架还是用数组好,顺带提醒,使用容器时如果能确认容器大小,最好声明下,因为扩容设计的内存申请和数据搬迁成本较高

  • ArrayList等无法存储基本类型,多维数组的标识要比容器标识更易于理解

为什么大多数编程语言,数组从0开始编号,而不是1开始

鉴于前面说的数组是采用连续的内存地址存放相同类型的数据。

所以,从内存层面我们访问数据其实是在平移到不同的内存地址,然后获取该内存地址对应的数组元素值。

那么,数组是如何获取指定位置的元素值的?

我们通过偏移量和首地址访问需要访问的元素。如果数组从0开始,那么a[0]就是偏移量为0的位置,a[k]就是偏移量为k的位置。所以计算a[k]的内存地址的公式如下


a[k]_address = base_address + k*type_size 

如果数组从1开始,那么公式就变为


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

这样,每次随机访问元素时就会多一次减法指令运算,显然,应该减少或避免这样的可优化的指令,所以数组从0开始。

    原文作者:Jackie_Zheng
    原文地址: https://www.jianshu.com/p/0e6ba7eb6c6d
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞