极客算法02 | 数组

版权声明:本文为博主原创文章,转载请附上博文链接! https://blog.csdn.net/weixin_40862011/article/details/88208377

为什么数组要从0编号,而不是从1开始呢?

如何实现随机访问?

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

这个定义里有几个关键词:

第一是线性表。顾名思义,线性表就是数据排成像一条线一样的结构。每个线性表上的数据最多只有前和后两个方向。其实除了数组,链表、队列、栈等也是线性表结构。

而与它相对立的概念是非线性表,比如二叉树、堆、图等。之所以叫非线性,是因为在非线性表中,数据之间并不是简单的前后关系。

第二是连续的内存空间和相同类型的数据。正是因为这两个限制,它才有了一个堪称“杀手锏”的特性:“随机访问”。但有利就有弊,这两个限制也让数组的很多操作变得非常低效,比如在数组中插入、删除一个数据,为了保证连续性,需要做大量的数据搬移工作。

数组是如何实现根据下标随机访问数组元素?

我们知道,计算机会给每个内存单元分配一个地址,计算机通过地址来访问内存中数据。当计算机需要随机访问数组中的某个元素时,它会首先通过下面的寻址公式,计算出该元素存储的内存地址:

a[i]_address = base_address + i * data_type_size

其中 data_type_size 表示数组中每个元素的大小。

这里要纠正一个错误,数组适合查找,查找时间复杂度为O(1)的表述是不准确的。

数组是适合查找操作,但是查找的时间复杂度并不为O(1)。即使是排序好的数组,用二分查找,时间复杂度也是O(logn)。所以,正确的表述应该是,数组支持随机访问,根据下标随机访问的时间复杂度为O(1)。

低效的插入和删除

  • 插入操作

最坏时间复杂度为O(n),平均时间复杂度为(1+2+…n)/n=O(n),最好时间复杂度为O(1)

  • 删除操作

最坏时间复杂度为O(n),平均时间复杂度为(1+2+…n)/n=O(n),最好时间复杂度为O(1)

警惕数组的访问越界问题

       数组越界在 C 语言中是一种未决行为,并没有规定数组访问越界时编译器应该如何处理。因为,访问数组的本质就是访问一段连续内存,只要数组通过偏移计算得到的内存地址是可用的,那么程序就可能不会报任何错误。

容器能否完全替代数组?

相比于数组,java中的ArrayList封装了数组的很多操作,并支持动态扩容。一旦超过容量,扩容时比较耗内存,因为涉及到内存申请和数据搬移。

如果使用 ArrayList,我们就完全不需要关心底层的扩容逻辑,ArrayList 已经帮我们实现好了。每次存储空间不够的时候,它都会将空间自动扩容为 1.5 倍大小。

不过,这里需要注意一点,因为扩容操作涉及内存申请和数据搬移,是比较耗时的。所以,如果事先能确定需要存储的数据大小,最好在创建 ArrayList 的时候事先指定数据大小

数组适合的场景:

  1.   Java ArrayList 无法存储基本类型,其使用涉及装箱拆箱,有一定的性能损耗,如果特别关注性能,可以考虑数组。
  2.   若数据大小事先已知,并且涉及的数据操作非常简单,可以使用数组。
  3.   表示多维数组时,数组往往更加直观。
  4.  业务开发使用容器即可。底层开发,如网络框架,性能优化需要做到极致,优先选择数组。

解答开篇

数组从0开始计数:a[k]_address = base_address + k * type_size          

数组从1开始计数:a[k]_address = base_address + (k - 1) * type_size        (CPU多了一次减法指令)

  1.   下标最确切的定义应该是偏移。从偏移角度理解a[0],0为偏移量。
  2.   如果从1计数,会多出K-1,增加cpu负担。
  3.   也有一定的历史原因

猜你喜欢

转载自blog.csdn.net/weixin_40862011/article/details/88208377