数据结构与算法笔记(三)数组

3.数组

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

3.1 特性

  • 线性表

    数据排成像一条线的结构,如数组、链表、队列、栈等。

    与之相对立的是非线性,如二叉树、堆、图等,其数据之间并不是简单的前后关系。

  • 连续的内存空间和相同类型的数据

    • 可以实现随机访问

      数组支持随即访问,通过寻址公式,计算该元素存储的内存地址

      a[i]_address = base_address + i * data_type_size

      根据下标随即访问的时间复杂度为 O ( 1 ) O(1)

    • 低效的插入和删除

      为保证数组的连续性,就要做大量的数据搬移工作。

      插入
      若在第k个位置插入数据,则k-n位置的数据需往后移。

      • 最好情况时间复杂度: O ( 1 ) O(1) (末尾插入)
      • 最坏情况时间复杂度: O ( n ) O(n)
      • 和平均情况时间复杂度: O ( n ) O(n)

      改进:如果数组中数据是无序的,也就是无规律的情况下,在插入元素时,可以直接将第k位的数据搬移到数组元素的最后,把新的元素直接放入第k个位置。这样时间复杂度就会降为 O ( 1 ) O(1)

      删除
      和插入类似。

      • 最好情况时间复杂度: O ( 1 ) O(1) (删除末尾数据)
      • 最坏情况时间复杂度: O ( n ) O(n)
      • 平均情况时间复杂度: O ( n ) O(n)

      改进:在某些特殊场景下,不追求数组中数据的连续性时,可以将多次删除操作集中在一起执行,减少数据搬移次数。先记录下已经删除的数据,但不进行数据迁移,仅仅是记录,当数组没有更多空间存储数据时,才触发执行一次真正的删除。这也是JVM标记清除垃圾回收算法的核心思想。

3.2 数组访问越界问题

要警惕数组的访问越界问题,例如在C语言中的数组越界是一种未决行文,循环越界访问会导致死循环bug。

3.3 用数组还是容器

数组需要预先指定了空间大小,容器如ArrayList可以将很多数组操作的细节封装起来,支持动态扩容。

  • 希望存储基本类型数据,可以用数组
  • 事先已知数据大小,并且对数据操作简单,用不到ArrayList提供的大部分方法,可以用数组
  • 表示多维数组时,可以用数组
  • 业务开发,使用容器足够;开发框架,追求性能,首先数组

3.4 为什么数组要从0开始编号

从数组存储的内存模型,下标最确切的定义是偏移,如果a来表示数组的首地址,a[0]就是偏移为0的位置,a[k]表示偏移k个type_size的位置,所以a[k]的内存地址计算如下:
a[i]_address = base_address + i * data_type_size
如果数组是从 1 开始计数,那么就会变成:
a[i]_address = base_address + (i-1)* data_type_size

对于CPU来说,多了一次减法的指令。
当然,还有一定的历史原因。

猜你喜欢

转载自blog.csdn.net/weixin_43004311/article/details/84575227
今日推荐