数据结构与算法-数组

国庆假期就这么过去了,假期没带电脑回家,写文章不太方便,于是就干脆没写。以至于积攒了太多要写的东西,只能慢慢补上了,开写。


提到「数组」,可以说是再熟悉不过了。下面是 维基百科 对数组(array)的定义:An array is a systematic arrangement of similar objects, usually in rows and columns. 意思就是说,数组存储了一些具有相同数据类型的元素。说得更具体一些就是:数组是一种线性表结构,它用一组连续的内存空间,来存储一组具有相同类型的数据

数组的特性

首先看第一个特性- 线性表。数组、链表、队列、栈都是线性表结构,数据之间只有前后两种方向。二叉树、堆、图是非线性表结构,数据之间不仅仅有前后两种方向。

再看剩余的两个特性-连续的存储空间相同类型的数据。正是这两个特性,决定了数组具有根据下标随机访问的功能,根据下标访问的时间复杂度为 O(1)。但是也是由于这两个特性,导致 插入删除 操作的时间复杂度比较高。

例如在数组arr = [ a, b, c, d ] 中的元素 a 前插入一个元素 e,需要将 a、b、c、d 元素依次后移一个存储单元,然后才能完成插入操作。

例如在数组arr = [ a, b, c, d ] 中,将元素 b 删除,在删除 b 后,为了保证存储的连续性,需要将 c,d 往前挪一个存储单元,才能完成删除操作。

有一个面试题是这样的,说一下链表和数组的不同点。可以这么回答:链表 适合删除和插入操作,时间复杂度为 O(1),数组 适合根据下标进行查找操作,时间复杂度为 O(1)。一定要注意 根据下标 这四个字,否则时间复杂度就不一定是 O(1) 了。

数组的访问越界

数组的越界问题需要非常重视,特别是在 C 语言中。先看一段 C 语言代码:

 int main () {
      int i = 0;
      int arr[3];
      for (; i <= 3; i++) {
          arr[i] = 0;
          printf("Hello World!");
      }
      return 0;
  }
      

运行这段代码,会不停地打印 "Hello World!"。这是因为,当 i 等于 3 时,arr[3] 的地址存储的是变量 i ,当 arr[3] = 0时,即 i = 0,所以程序会不停地打印 "Hello World!",这就是数组访问越界的结果。

数组的扩容

一个数组申请了能存储 10 个整型变量的内存空间,当要存储 11 个整型变量时,就需要数组扩容。例如在 Java 中,会用 ArrayList 进行动态扩容,即,当要存储的变量个数超过之前申请的变量个数时,会将数组大小扩充为原来的 1.5 倍 ,需要将原来的数组拷贝到这 1.5 倍的内存空间里,是比较耗时的。一般最好先申请好需要的内存空间,避免数据的拷贝

数组下标从 0 开始

刚开始学 C 语言的时候,我就纳闷,为啥数组的下标要从 0 开始,就是为了与众不同吗,当时也没再往深处想,现在大体知道了里边的原因。

一种说法是,数组可以根据下标进行元素的查找,这是因为存储空间连续、元素数据类型相同。对每个元素的访问是这样的:

Find_address = Base_address +  k* Type_size
  • 要查找元素的地址 Find_address
  • 基地址 Base_address
  • 偏移量 k
  • 数据类型大小 Type_size
    这里的下标就代表偏移量 k ,当下标从 1 开始时,这里的 k 就变成了 k-1 ,所以 CPU 要多进行一次减法操作,所以下标从 0 开始更省计算力。

另一种说法是,C 语言最先规定了数组下标要从 0 开始,之后的 Java 等语言效仿也让数组下标要从 0 开始。但是 Matlab 的下标从 1 开始, Python 的下标还可以是负数。

小结

要明确数组的定义,知道数组的优缺点,特别要注意数组访问越界问题,数组元素的插入和删除操作尽量要用一些算法降低时间复杂度。

你可能感兴趣的:(数据结构与算法-数组)