【学习】数据结构与算法之美——基础篇笔记

目录

  • 05 数组
    • 什么是数组
      • 数组的下标随机访问
      • 二维数组
    • 低效的“插入”和“删除”与巧思
      • 1. 插入操作
      • 2. 删除操作
    • 注意访问越界问题
    • 容器 VS 数组
      • 容器的优点:
      • 何时用数组?
    • 总结
  • 06


05 数组

什么是数组

  • 数组是线性表中的一种
  • 数组使用连续的存储空间,存储一组相同类型的数据

数组的下标随机访问

通过寻址公式,计算出该元素存储的内存地址
a[i]_address = base_address + i * data_type_size

值得注意的是,数组的的内存地址分配是[0]在低位,[n-1]在高位。
【学习】数据结构与算法之美——基础篇笔记_第1张图片

如果数组从1开始计数,那么内存地址计算就会变成
a[i]_address = base_address + (i-1) * data_type_size

相当于每次随机访问数组元素都多了一次减法运算,对于 CPU 来说,就是多了一次减法指令。
所以当然还是0开始合适!

二维数组

二维数组大小为m*n,该元素存储的内存地址
a[i][j]_address = base_address + (i * n + j)*data_type_size

@Neuject 同学解释:
【学习】数据结构与算法之美——基础篇笔记_第2张图片

低效的“插入”和“删除”与巧思

1. 插入操作

一般看来,插入元素到数组的第k位,要将数组k~n位后移。这样的平均时间复杂度是 1 + 2 + . . . + n n → O ( n ) \frac{1+2+...+n}{n} \to O(n) n1+2+...+nO(n),因为在每个位置插入元素的概率是一样的。

  • 一种巧妙的插入方法:
    直接将第 k 位的数据搬移到数组元素的最后,把新的元素直接放入第 k 个位置。时间复杂度降为 O ( 1 ) O(1) O(1)
    【学习】数据结构与算法之美——基础篇笔记_第3张图片

2. 删除操作

在我之前看来,删除一个元素,就要把它后面的所有元素都向前挪,不然中间就会出现空洞,内存就不连续了。这样操作的平均情况时间复杂度也为 O ( n ) O(n) O(n)

  • 一种巧妙的删除方法:
    将多次删除操作集中在一起执行!每次的删除操作并不是真正地搬移数据,只是记录数据已经被删除。当数组没有更多空间存储数据时,再触发执行一次真正的删除操作。此算法类似JVM的标记清除垃圾回收算法的核心思想。
    【学习】数据结构与算法之美——基础篇笔记_第4张图片

注意访问越界问题

首先,我提醒自己平时写代码和刷题时要注意这些边界条件~

再来,看以下这段C语言代码。在C语言中,只要不是访问受限的内存,所有的内存空间都是可以自由访问的。数组越界在 C 语言中是一种未决行为,并没有规定数组访问越界时编译器应该如何处理。当然,其它语言就可能会报错。
【学习】数据结构与算法之美——基础篇笔记_第5张图片
用图来解释以上代码的死循环,就是程序内存分布问题,这里i和a为局部变量,被储存在栈区,栈区是从高地址往地址值扩展的。
【学习】数据结构与算法之美——基础篇笔记_第6张图片

附操作系统笔记,程序内存分布:
【学习】数据结构与算法之美——基础篇笔记_第7张图片

容器 VS 数组

针对数组类型,很多语言都提供了容器类,如 Java 中的 ArrayList、C++ STL 中的 vector

容器的优点:

  • 将很多数组操作的细节封装起来,如数组插入、删除数据时需要搬移其他数据等
  • 支持动态扩容,每次存储空间不够的时候,它都会将空间自动扩容为 1.5 倍大小

注意,对于扩容大小,gcc是2倍,VS是1.5倍。两者的区别是,2倍扩容时间复杂度更优,可以保证时间复杂度 O ( n ) O(n) O(n) ,而1.5倍扩容时,空间可重用。(具体讨论看我另一篇文章:C++ vector 关于容器扩容思考)

虽然扩容很方便,但是它涉及内存申请和数据搬移,是比较耗时的。所以,如果事先能确定需要存储的数据大小,最好在创建的时候事先指定数据大小

何时用数组?

  • 数据大小已知,操作简单,可直接使用数组
  • 表示多维数组时比较直观,像 int a[][],而不是 vector> a

总结

  • 数组用一块连续的内存空间,来存储相同类型的一组数据
  • 最大的特点就是支持随机访问,但插入、删除操作也因此变得比较低效,平均情况时间复杂度为 O ( n ) O(n) O(n)
  • 对于业务开发,可直接使用容器,省时省力,但会损耗一丢丢性能
  • 对于底层开发,比如开发网络框架,性能的优化需要做到极致,则选用数组合适


06



你可能感兴趣的:(课程学习,算法,数据结构)