数据结构之——数组

概况

数组是最基础的数据结构之一,它是是一种线性结构(线性表就是数据排成像一条线一样的结构,每个线性表上的数据最多只有前和后两个方向),通过固定连续的内存块存储相同类型的数据javascript除外,可以存储多种类型数据),常见的线性结构还有链表(包括单向链表、双向链表、循环链表)。

有线性结构,自然也有非线性结构,比如:二叉树、堆和图等。。。

特性

数组最大的特点是根据下标随机访问数据,数组寻址公式为:

a[i]_address = base_address + i * data_type_size

另外,笔试题面试中经常会说到数组与链表的区别,如下:

  • 数组方便根据下标进行数据随机访问,根据下标访问数据的时间复杂度为O(1),数组不方便插入与删除元素,时间复杂度为O(n);
  • 链表不支持数据随机访问,因为访问任何位置的元素都需要从链表的开头一直遍历,直到访问到相应的元素,时间复杂度为O(n),链表对插入和删除操作比较方便,时间复杂度为O(1);

数组的插入与删除

数组的插入与删除类似,以插入为例,最好的情况是在数组末尾新增一个元素,最坏的情况是在数组首部插入一个元素,那么后面的元素都要向后挪一位,同样的假如数组中有n个元素,在k下标中插入一个元素,则需要将n - k个元素向后挪动一个位置,那么数组插入的时间复杂度为:

O(insert) =
O((1 + 2 + … + n) / n) =
O(((1 + n) * n) / 2) / n) =
O((1 + n) / 2) =
O(n)

删除类似,最好情况为在数组末尾删除一个元素,无需挪动任何元素位置,最差情况为删除首部的元素,后面n个元素都要往前挪动一位。。。

时间复杂度同样为O(n)

如何提高数组插入数据效率

如果数组中的元素是要求有序的

O(insert) = O(n)

如果数组中的元素不要求有序

如果需要将某个元素插入到数组中的第k个位置,只需要将array[k]挪到数组末尾的位置,而将要插入的元素直接赋给array[k],这样,向数组中插入元素的时间复杂度仅为O(1)。

如何提高数组删除数据的效率

在某些场景下,可以将多次删除操作集中在一起进行执行,即每次删除操作并非将元素真正地删除,而仅仅是将删除地目标元素的下标记录下来,当某一时机来临时(比如数组中已经没有空间了),统一将相应的元素删除,这样可以大大节省程序运行成本。

分析

数组:a   b   c   d   e
需要删除如下几个元素:"a   b   c"   d   e
那么数组中元素需要挪的位置如下:
第一次:
a   b   c   d   e
0   1   1   1   1 次
第二次:
a   b   c   d   e
0   1   2   2   2
第三次:
a   b   c   d   e
0   1   2   3   3

接下来,集中删除:
a   b   c   d   e
0   0   0   1   1

数组越界问题

int main (int argc, char* argv[]) {
  int i = 0;
  int arr[3] = {0};
  for (let i = 0; i <= 3; i++) {
    arr[i] = 0;
    printf("hello world\n");
  }
  return 0;
}

这段C语言代码其实越界了,在C语言中,如果越界访问的地址是可访问的,那么程序是不会报错的,这通常会增加我们debug的负担,其他语言如java如果越界则会报异常。

上面的代码可能会出现死循环的情况,比如arr[3]对应的地址刚好用来存储i变量。

容器与数组

容器即高级程序语言中对基础数据类型的封装类。

容器如java中的ArrayList将很多数组操作的细节封装起来,方便操作,与数组相比,其另外的优势是支持数组的动态扩容。

但动态扩容实际上是非常消耗性能的,所以如果能事先确定好要存储的数据量,最好先事先分配数组容量大小。

什么时候用容器和数组

  1. java ArraList比较消耗性能,如果目前比较关注程序性能,可以选用数组;
  2. 如果数据量实现已知,并且对数据的操作比较简单,可以选用数组;
  3. 定义多维数组时,数组会比较直观;
  4. 业务开发,设备性能允许时选用容器,底层开发选用数组;

思考

对于多维数组,其寻址方式是怎样的:

比如:a[4][4]

a[m][n]_address = a[0][0]_address + m * 4 * data_type_size + n * data_type_size

参考

《数据结构与算法之美》—— 王争 on 极客时间

你可能感兴趣的:(算法学习)