数据结构与算法之美学习笔记:03 ~ 05

本文是极客时间推出的数据结构与算法之美课程的学习笔记

  • 复杂度分析
    1. 数据结构和算法解决的是“如何让计算机更快时间、更省空间的解决问题”
    2. 因此需从执行时间和占用空间两个维度来评估数据结构和算法的性能
    3. 分别用时间复杂度和空间复杂度两个概念来描述性能问题,二者统称为复杂度
    4. 复杂度描述的是算法执行时间(或占用空间)与数据规模的增长关系
  • 为什么要进行复杂度分析
    1. 和性能测试相比,复杂度分析有不依赖执行环境、成本低、效率高、易操作、指导性强的特点
    2. 掌握复杂度分析,将能编写出性能更优的代码,有利于降低系统开发和维护成本
  • 如何进行复杂度分析
    1. 大O表示法
      1)来源
      算法的执行时间与每行代码的执行次数成正比,用T(n) = O(f(n))表示,其中T(n)表示算法执行总时间,f(n)表示每行代码执行总次数,而n往往表示数据的规模
      2)特点
      以时间复杂度为例,由于时间复杂度描述的是算法执行时间与数据规模的增长变化趋势,所以常量阶、低阶以及系数实际上对这种增长趋势不产生决定性影响,所以在做时间复杂度分析时忽略这些项
    2. 复杂度分析法则
      1)单段代码看高频:比如循环
      2)多段代码取最大:比如一段代码中有单循环和多重循环,那么取多重循环的复杂度
      3)嵌套代码求乘积:比如递归、多重循环等
      4)多个规模求加法:比如方法有两个参数控制两个循环的次数,那么这时就取二者复杂度相加
  • 常用的复杂度级别
    多项式阶:随着数据规模的增长,算法的执行时间和空间占用,按照多项式的比例增长。包括O(1)(常数阶)、O(logn)(对数阶)、O(n)(线性阶)、O(nlogn)(线性对数阶)、O(n^2)(平方阶)、O(n^3)(立方阶)
    非多项式阶:随着数据规模的增长,算法的执行时间和空间占用暴增,这类算法性能极差。包括O(2^n)(指数阶)、O(n!)(阶乘阶)
    数据结构与算法之美学习笔记:03 ~ 05_第1张图片
    常用的算法复杂度
    数据结构与算法之美学习笔记:03 ~ 05_第2张图片
    常用的算法复杂度
  • 如何掌握好复杂度分析方法
    复杂度分析关键在于多练,所谓孰能生巧

  • 复杂度分析的四个概念
    1. 最好情况时间复杂度:代码在最理想情况下执行的时间复杂度
    2. 最坏情况时间复杂度:代码在最坏情况下执行的时间复杂度
    3. 平均时间复杂度:用代码在所有情况下执行的次数的加权平均值表示
    4. 均摊时间复杂度:在代码执行的所有复杂度情况中绝大部分是低级别的复杂度,个别情况是高级别复杂度且发生具有时序关系时,可以将个别高级别复杂度均摊到低级别复杂度上。基本上均摊结果就等于低级别复杂度
  • 为什么要引入这四个概念
    1. 同一段代码在不同情况下时间复杂度会出现量级差异,为了更全面,更准确的描述代码的时间复杂度
    2. 代码复杂度在不同情况下出现量级差别时才需要区别这四种复杂度。大多数情况下,是不需要区别分析它们的
  • 区别平均、均摊时间复杂度
    平均复杂度:代码在不同情况下复杂度出现量级差别,则用代码所有可能情况下执行次数的加权平均值表示
    均摊复杂度: 两个条件满足时使用:1)代码在绝大多数情况下是低级别复杂度,只有极少数情况是高级别复杂度;2)低级别和高级别复杂度出现具有时序规律。均摊结果一般都等于低级别复杂度

  • 线性表:线性表就是数据排成像一条线一样的结构。每个线性表上的数据最多只有前和后两个方向。数组、链表、队列、栈等都是线性表结构:
    数据结构与算法之美学习笔记:03 ~ 05_第3张图片
    线性表
  • 非线性表:之所以叫非线性,是因为在非线性表中,数据之间并不是简单的前后关系。二叉树、堆、图等都是非线性表:
    数据结构与算法之美学习笔记:03 ~ 05_第4张图片
    非线性表
  • 数组:是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据
  • 数组是如何实现随机访问的
    假设有一个长度为10的int类型的数组,内存块的首地址base_address为1000:
    数据结构与算法之美学习笔记:03 ~ 05_第5张图片
    长度为10的int类型数组a
    当计算机需要随机访问数组中的某个元素时,会通过下面的寻址公式,计算出该元素存储的内存地址:
// 第i个元素的地址 = 首地址 + (i * 每个元素所占用的字节)
a[i]_address = base_address + (i * data_type_size)
  • 数组和链表:数组支持随机访问,根据下标进行随机访问的时间复杂度为O(1)。链表适合插入、删除,时间复杂度为O(1)
  • 数组插入元素的时间复杂度:如果在数组的末尾插入元素,那就不需要移动数据了,这时的时间复杂度为O(1)。但如果在数组的开头插入元素,那所有的数据都需要依次往后移动一位,所以最坏时间复杂度是O(n)。因为我们在每个位置插入元素的概率是一样的,所以平均情况时间复杂度为(1+2+…n) / n = O(n)
  • 数组插入元素的一种优化:如果数组中存储的数据没有任何规律,当要将某个数据插入到第k个位置,可以直接将第k个元素移到数组的最后,把新数据放到第k个位置,此时时间复杂度就会降低为O(1):
    数据结构与算法之美学习笔记:03 ~ 05_第6张图片
  • 数组删除元素的时间复杂度:如果删除数组末尾的数据,则最好情况时间复杂度为O(1)。如果删除开头的数据,则最坏情况时间复杂度为O(n)。平均情况时间复杂度也为O(n)
  • 数组删除操作的一种优化思想:我们可以先记录下已经删除的数据。每次的删除操作并不是真正地搬移数据,只是记录数据已经被删除。当数组没有更多空间存储数据时,我们再触发执行一次真正的删除操作,这样就大大减少了删除操作导致的数据搬移。这也是JVM标记清除垃圾回收算法的核心思想
  • 为什么大多数编程语言中,数组要从0开始编号,而不是从1开始:1) 从数组存储的内存模型上来看,“下标”最确切的定义应该是“偏移”。如果数组从1开始计数,那我们计算数组元素a[i]的内存地址就会变为:
a[i]_address = base_address + ((i - 1) * data_type_size)

每次随机访问数组元素都多了一次减法运算,对于CPU来说,就是多了一次减法指令。2) 有一定的历史原因,C语言设计者用0开始计数数组下标,之后的语言有效仿性

你可能感兴趣的:(数据结构与算法之美学习笔记:03 ~ 05)