本文由 简悦 SimpRead 转码, 原文地址 programmercarl.com

数组介绍

数组是非常基础的数据结构

数组是存放在连续内存空间上的相同类型数据的集合。

数组可以方便的通过下标索引的方式获取到下标下对应的数据。

举一个字符数组的例子,如图所示:

数组理论基础 - 图1

需要两点注意的是

  • 数组下标都是从 0 开始的。
  • 数组内存空间的地址是连续的

正是因为数组的内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址。

例如删除下标为 3 的元素,需要对下标为 3 的元素后面的所有元素都要做移动操作,如图所示:

数组理论基础 - 图2

而且大家如果使用 C++ 的话,要注意 vector 和 array 的区别,vector 的底层实现是 array,严格来讲 vector 是容器,不是数组。

数组的元素是不能删的,只能覆盖。

时间复杂度

如果数组需要保持有序,在数组中删除、插入一个数据,且为了保证连续性,就需要做大量的数据搬移工作。平均情况时间复杂度为 O(n),
如果数组并不需要保持有序,只是被当做一个存储数据的集合,可以把需要插入位置的元素放到数组最后一位,把新数据插入到这个位置。
如果只在数组中的空闲位置插入元素,并不需要搬移数据,插入的时间复杂度是o(1)
散列表中事先声明了数组的大小,使用散列函数随机往数组的某个位置插入元素,并不需要搬移操作。时间复杂度是o(1),查找元素是利用下标,时间复杂度也是o(1)

二维数组

那么二维数组直接上图,大家应该就知道怎么回事了

数组理论基础 - 图3

那么二维数组在内存的空间地址是连续的么?

不同编程语言的内存管理是不一样的,以 C++ 为例,在 C++ 中二维数组是连续分布的。

我们来做一个实验,C++ 测试代码如下:

  1. void test_arr() {
  2. int array[2][3] = {
  3. {0, 1, 2},
  4. {3, 4, 5}
  5. };
  6. cout << &array[0][0] << " " << &array[0][1] << " " << &array[0][2] << endl;
  7. cout << &array[1][0] << " " << &array[1][1] << " " << &array[1][2] << endl;
  8. }
  9. int main() {
  10. test_arr();
  11. }

测试地址为

  1. 0x7ffee4065820 0x7ffee4065824 0x7ffee4065828
  2. 0x7ffee406582c 0x7ffee4065830 0x7ffee4065834

注意地址为 16 进制,可以看出二维数组地址是连续一条线的。

一些录友可能看不懂内存地址,我就简单介绍一下, 0x7ffee4065820 与 0x7ffee4065824 差了一个 4,就是 4 个字节,因为这是一个 int 型的数组,所以两个相邻数组元素地址差 4 个字节。

0x7ffee4065828 与 0x7ffee406582c 也是差了 4 个字节,在 16 进制里 8 + 4 = c,c 就是 12。abc分别是10,11,12

如图:

数组理论基础 - 图4

所以可以看出在 C++ 中二维数组在地址空间上是连续的

像 Java 是没有指针的,同时也不对程序员暴漏其元素的地址,寻址操作完全交给虚拟机。

所以看不到每个元素的地址情况,这里我以 Java 为例,也做一个实验。

public static void test_arr() {
    int[][] arr = {{1, 2, 3}, {3, 4, 5}, {6, 7, 8}, {9,9,9}};
    System.out.println(arr[0]);
    System.out.println(arr[1]);
    System.out.println(arr[2]);
    System.out.println(arr[3]);
}

输出的地址为:

[I@7852e922
[I@4e25154f
[I@70dea4e
[I@5c647e05

这里的数值也是 16 进制,这不是真正的地址,而是经过处理过后的数值了,我们也可以看出,二维数组的每一行头结点的地址是没有规则的,更谈不上连续。

所以 Java 的二维数组可能是如下排列的方式:

数组理论基础 - 图5