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

目录

  • 01 学习数据结构与算法的重要性
  • 02 如何学习
  • 03 复杂度分析
    • 大 O 复杂度表示法
    • 时间复杂度分析方法
    • 常见的时间复杂度
    • 空间复杂度分析
    • 总结
  • 04 最好、最坏、平均、均摊时间复杂度
    • 最好、最坏情况时间复杂度
    • 平均情况时间复杂度
    • 均摊时间复杂度
    • 总结


01 学习数据结构与算法的重要性

虽然现在已经有很多框架可以使用,但是背后的原理不懂,如何取舍选择哪种框架?
代码的可读性、健壮性,还是扩展性固然重要,但我们至少要学会评估代码的性能资源的消耗

虽然现在在学校做科研主要最求的是功能的实现,但到了公司,面对千万级甚至亿级的用户,开发的是 TB、PB 级别数据的处理系统,性能的重要性就体现了出来。比如array和linked list的选择,可能产生巨大的差别。

所以,学习数据结构和算法,目的是建立时间复杂度、空间复杂度意识,写出高质量的代码,能够设计基础架构,提升编程技能~


02 如何学习

  • 数据结构是静态的,它是组织数据的一种方式
  • 算法就是操作数据的一组方法

其中,最重要的概念是复杂度分析
数据结构和算法解决的是如何更省、更快地存储和处理数据的问题,复杂度分析方法就是一个考量效率和资源消耗的方法。

20 个最常用的、最基础数据结构与算法:

  • 10 个数据结构:数组、链表、栈、队列、散列表、二叉树、堆、跳表、图、Trie 树;
  • 10 个算法:递归、排序、二分查找、搜索、哈希算法、贪心算法、分治算法、回溯算法、动态规划、字符串匹配算法

事半功倍的学习技巧:

  • 边学边练,适度刷题
  • 多问、多思考、多互动
  • 给自己设立一个切实可行的目标,去坚持
  • 不要着急,学习知识的过程是反复迭代、不断沉淀的


03 复杂度分析

数据结构和算法本身解决的是“快”和“省”的问题,执行效率是算法一个非常重要的考量指标。把代码跑一遍,通过统计、监控等方法是可以评估的,但是得到的执行效率有很大局限性。一是依赖于测试的硬件环境,二是依赖于测试数据的规模。
所以,是需要一个不用具体的测试数据来测试,就可以粗略地估计算法的执行效率的方法,也就是时间、空间复杂度分析方法

大 O 复杂度表示法

算法的执行效率,粗略地讲,就是算法代码执行的时间。
现在假设把每行代码执行的时间记为 u n i t _ t i m e unit\_time unit_time,来计算一段代码的总执行时间。
【学习】数据结构与算法之美——入门篇笔记_第1张图片
上图中,代码总的执行时间为 T ( n ) = ( 2 n 2 + 2 n + 3 ) ∗ u n i t _ t i m e T(n) = (2n^2+2n+3)*unit\_time T(n)=(2n2+2n+3)unit_time
可以总结出,所有代码的执行时间 T ( n ) T(n) T(n) 与每行代码的执行次数 n n n 成正比。
也就可以写成大O复杂度表示法

T ( n ) = O ( f ( n ) ) T(n) = O(f(n)) T(n)=O(f(n))

  • T ( n ) T(n) T(n) 表示代码执行的时间
  • n n n 表示数据规模的大小
  • f ( n ) f(n) f(n) 表示每行代码执行的次数总和

代码的执行时间 T(n) 与 f(n) 表达式成正比。

大 O 时间复杂度,实际上表示代码执行时间随数据规模增长的变化趋势,所以,也叫作渐进时间复杂度(asymptotic time complexity),简称时间复杂度

n n n 很大时,公式中的低阶、常量、系数不会左右增长趋势,所以可以忽略,只取最大量级即可。那么, T ( n ) = O ( 2 n 2 + 2 n + 3 ) T(n) = O(2n^2+2n+3) T(n)=O(2n2+2n+3) 即可记为 T ( n ) = O ( n 2 ) T(n) = O(n^2) T(n)=O(n2)

时间复杂度分析方法

  1. 只关注循环执行次数最多的一段代码
  2. 加法法则:总复杂度等于量级最大的那段代码的复杂度
    注意,只要是一个已知数无论它多大,都是一个常量,可以直接忽略
    【学习】数据结构与算法之美——入门篇笔记_第2张图片
  3. 乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积

常见的时间复杂度

【学习】数据结构与算法之美——入门篇笔记_第3张图片
  1. O ( 1 ) O(1) O(1)
    常量级时间复杂度
    只要算法中不存在循环语句、递归语句,即使有成千上万行的代码,其时间复杂度也是Ο(1)

  2. O ( l o g n ) O(logn) O(logn) O ( n l o g n ) O(nlogn) O(nlogn)
    对数阶时间复杂度
    在这里插入图片描述

  3. O ( m + n ) O(m+n) O(m+n) O ( m ∗ n ) O(m * n) O(mn)
    无法事先评估 m 和 n 谁的量级大,则都保留。
    【学习】数据结构与算法之美——入门篇笔记_第4张图片
    附课代表姜威总结笔记:
    【学习】数据结构与算法之美——入门篇笔记_第5张图片

空间复杂度分析

空间复杂度全称就是渐进空间复杂度(asymptotic space complexity),表示算法的存储空间与数据规模之间的增长关系,与时间复杂度的全称“渐进时间复杂度”类似。

常见的空间复杂度有 O ( 1 ) O(1) O(1) O ( n ) O(n) O(n) O ( n 2 ) O(n^2 ) O(n2)
【学习】数据结构与算法之美——入门篇笔记_第6张图片

总结

越高阶复杂度的算法,执行效率越低。

附课代表姜威总结笔记:
【学习】数据结构与算法之美——入门篇笔记_第7张图片



04 最好、最坏、平均、均摊时间复杂度

【学习】数据结构与算法之美——入门篇笔记_第8张图片
从以上代码可以看出,代码的时间复杂度不能用简单的 O ( n ) O(n) O(n) 概括。

为了表示代码在不同情况下的不同时间复杂度,引入三个概念:最好情况时间复杂度、最坏情况时间复杂度和平均情况时间复杂度。


最好、最坏情况时间复杂度

  • 最好情况时间复杂度:在最理想的情况下,执行这段代码的时间复杂度
  • 最坏情况时间复杂度:在最糟糕的情况下,执行这段代码的时间复杂度

注意,他们对应的都是极端情况,并不是一般情况。


平均情况时间复杂度

对于以上极端情况,发生的概率并不大,所以引入平均时间复杂度。表示代码在所有情况下执行的次数的加权平均值表示。

  • 计算方法
    使用概率论中的加权平均值,也叫期望值,进行计算,把每种情况的概率也考虑进去。

以上一段代码为例子,设 x x x 在与不在数组中的概率为 1 2 \frac{1}{2} 21,要查找的位置概率为 1 n \frac{1}{n} n1,那么它在某位置的概率就是 1 2 n \frac{1}{2n} 2n1。求期望值:

1 ∗ 1 2 n + 2 ∗ 1 2 n + . . . + n ∗ 1 2 n + n ∗ 1 2 = 3 n + 1 4 1 *\frac{1}{2n} + 2*\frac{1}{2n} + ... +n*\frac{1}{2n} + n*\frac{1}{2} = \frac{3n+1}{4} 12n1+22n1+...+n2n1+n21=43n+1

按照大O标记法,去掉常数项,该段代码的时间复杂度为 O ( n ) O(n) O(n)

注意,大部分情况不需要区分这三种复杂度,只有同一块代码在不同的情况下,时间复杂度有量级的差距,才会使用他们来区分。


均摊时间复杂度

与平均时间复杂度不同,均摊时间复杂度应用场景更加特殊、更加有限。
【学习】数据结构与算法之美——入门篇笔记_第9张图片
跟前面例子不同的是,代码执行在大部分情况下都是 O ( 1 ) O(1) O(1),只有一种额外的情况,复杂度是 O ( n ) O(n) O(n)。数组长度为1,所以这 n + 1 n+1 n+1 种情况发生概率相同,即 1 n + 1 \frac{1}{n+1} n+11。按前面平均时间复杂度计算,得到平均时间复杂度为 O ( 1 ) O(1) O(1)

跟前面find()函数极端情况下复杂度为 O ( 1 ) O(1) O(1) 不同,这个insert()函数是在大部分情况下复杂度为 O ( 1 ) O(1) O(1);而且它还有规律,一个 O ( 1 ) O(1) O(1) 插入之后,紧跟着 n − 1 n-1 n1 O ( 1 ) O(1) O(1) 的插入操作。

  • 摊还分析法计算均摊时间复杂度
    想象把耗时的 n n n 次操作,均摊到后面的 n − 1 n-1 n1 次操作上,所以连续的一组操作均摊时间复杂度就是 O ( 1 ) O(1) O(1)

注意,在能够应用均摊时间复杂度分析的场合,一般均摊时间复杂度就等于最好情况时间复杂度。


总结

  • 引入几个复杂度概念原因:
    同一段代码,在不同输入的情况下,复杂度量级有可能是不一样的。
  • 引入目的:
    在引入这几个概念之后,我们可以更加全面地表示一段代码的执行效率

你可能感兴趣的:(课程学习)