此为自己学习课程的笔记,供自己复习用。
一、数组定义
数组(Array):是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。
线性表:是指数据排成像一条线一样的结构。每个线性表上的数据最多只有前和后两个方向。
非线性表中,数据之间并不是简单的前后关系。
链表、队列、栈等都是线性表结构。
二叉树、堆、图等都是非线性结构
数组支持随机访问,根据下标随机访问的时间复杂度为 O(1)
通过寻址公式访问内存中的数据:
a[k]_address = base_address + k * type_size
二、插入和删除操作
1.插入操作
在数组的非末尾位置插入一个元素时,需要对其后面位置的元素进行移动。此时,插入操作的时间复杂度为O(n)。
例如:
假设数组的长度为 n,现在,需要将一个数据插入到数组中的第 k 个位置。为了把第 k 个位置腾出来,给新来的数据,我们需要将第 k~n 这部分的元素都顺序地往后挪一位。
为了减少插入操作的时间复杂度,可以通过如下方法巧妙的解决:
假如数组中的元素是无序的,只是被当作一个存储数据的集合,可以直接将第 k 位的数据搬移到数组元素的最后,把新的元素直接放入第 k 个位置。
这样,在第 k 个位置插入一个元素的时间复杂度就会降为 O(1)。
2.删除操作
当我们要删除第 k 个位置的数据时,为了保证内存的连续性,需要将其后面的元素全部往前移。此时,删除操作的时间复杂度为O(n)。
例如:有一个数组,依次存储着元素a, b, c, d, e, f,现在有两个删除操作,分别删除元素a和元素b。
为了防止c, d, e, f 移动两次,可以先标记好每次要删除的元素,当数组满了的时候再一起将那些要删除的元素删掉,可以降低移动带来的时间复杂度。
三、防止数组的访问越界
数组越界在 C 语言中是一种未决行为,并没有规定数组访问越界时编译器应该如何处理。
因为,访问数组的本质就是访问一段连续内存,只要数组通过偏移计算得到的内存地址是可用的,那么程序就可能不会报任何错误。
而像Java语言本身就会做越界检查。
四、容器类
Java中的 ArrayList、C++ STL 中的 vector都属于对数组的封装和扩展。
它们支持动态扩容:当要存储的数据超过了预先设定的数组的大小,就要重新分配一块更大的内存空间,将原来的数组中的数据复制过去,然后再将新的数据插入。
动态扩容涉及到内存申请和数据搬移,会比较耗时。
注意:
为了减少扩容操作,在使用容器的时候最好事先指定数据的大小。
总结
对于业务开发,可以直接使用容器,虽然会损耗一些性能,但是不会对系统的整体性能造成影响。
对于底层的开发,非常注重性能的开发,此时数组要优于容器。