您现在的位置是:首页 > 文章详情

Learning algorithem the hard way array (part 2)

日期:2019-10-20点击:359

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

上述定义当中有有几个较为关键的概念:

线性表 (Linear List)
线性表是指数据排成一个线型的结构。每个线性表上的数据最多只有前后两个方向。

除了数组之外,链表、队列、栈也是线性表结构。
11
而与其对应的概念是非线性表,例如二叉树、图、堆等。
22
连续的内存空间和相同的数据类型
数组的随机访问(根据索引下标访问)时间复杂度为 O(1) 。其随机访问的实现原理是:

a[i]_address = base_address + i * data_type_size 

为什么大部分编程语言的数组下标是从 0 开始设计,而不是从 1 开始?
我们来思考另外一个问题,为什么大部分编程语言的数组索引下标是从 0 开始,而不是从 1 开始?如果从 1 开始的话,上述的公式必须转化为:

a[i]_address = base_address + ((i - 1) * data_type_size) 

对比从 0 开始的方案,需要多做一次减法运算。因此大部分编程语言都选择从 0 开始作为数组的初始下标。

低效的插入和删除操作

插入操作
假设数组的长度为 n ,我们需要将新元素 e 插入到数组中的第 k 个位置,那我们需要将后续的 n - k 个元素顺序地往后进行移动,因此在这种场景下插入操作的时间复杂度为 O(n) 。

但是有一种特殊场景下:数组仅仅作为一种容器去存储数据,而不用关心数组中存储元素的顺序,能够将数组插入操作的算法复杂度提升为 O(1) 。

举个例子,假设数据 a[10] 中存储了 5 个元素: a、b、c、d、e 。我们将元素 x 插入到第三个位置,只需要将原有的第三个元素 c 移动到最后,再将第三个元素替换为 x ,具体的实现代码如下:

a[5] = c; a[2] = x; 

33

删除操作
与插入操作类似,如果我们要删除第 k 个位置的数据,为了内存的连续性,也需要搬移数据,因此数组的删除操作时间复杂度为 O(n) 。

有一种数组删除操作的优化方案是,不需要每一次删除操作时都移动数据,而是先将被删除的索引位置记录下来,等到数组容量不足时,再一次性地从内存当中移除数据,并移动剩余元素的位置。其核心思想与 JVM 的标记清除内存回收算法非常相似。

数组与容器类型的对比

许多编程语言中都提供了高级的容器类型,例如 Java 的 ArrayList , C++ 的 Vector 等,数组与这些高级容器类型相比,有哪些适合的应用场景了?

适合使用容器类型的场景
高级容器类型支持动态扩容,适合存储元素的大小无法事先确定的场景。例如 ArrayList 会在存储空间不足时,自动扩容 1.5 倍。需要注意的是,我们应该养成设置 ArrayList 初始容量的习惯,因为这样能够节省掉很多次内存分配空间和数据搬移的操作。

//假设从 db 当中获取用户的数据为 10000条 //指定初始容量为 10000 会比不指定容量获得更好的性能 ArrayList<User> list = new ArrayList<User>(10000); for (int i = 0; i < db.getUserList(); i++) { list.add(db[i]); } 

适合使用数组的场景
ArrayList 无法存储基本类型:int 、long 等,只能存储包装类型: Integer 、Long ,而 Boxing 和 UnBoxing 会有一定的性能损耗,如果特别关注性能,就可以使用数组。

Java implemention
初步实现了类似于 ArrayList 的代码。ref here

JDK 1.8 ArrayList 的实现代码。ref here

原文链接:https://yq.aliyun.com/articles/721489
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章