数据结构是缓存
在解决问题的时候,我们通常不是一下子把数据处理完,更多的时候需要先把它们放在一个容器里,等到一定的时刻再把它们拿出来。使用「数据结构」是一种「空间换时间」思想的体现,「空间换时间」的思想会体现在我们学习「算法与数据结构」的整个过程中,恰当使用数据结构可以帮助我们高效地处理数据。所谓恰当,是指针对具体的问题场景,使用了合适的数据结构。
下面是一些常见的数据结构的经典应用场景:
数据结构 | 应用场景 |
---|---|
栈 | 符合「后进先出」的规律 |
队列 | 符合「先进先出」的规律 |
哈希表(散列表) | 实现「快速存取」数据的功能 |
二分搜索树(红黑树、B 树系列) | 维护了一组数据的顺序性,得到一个数据的上下界 |
并查集 | 用于处理不相交集合的「动态」连接问题 |
优先队列 | 有「动态」添加、删除数据且需要获得最值的场景 |
字典树(Trie) | 用于保存和统计大量的字符串和相关的信息 |
线段树 | 处理数组的区间信息的汇总(求和、最值等)、单点更新、区间更新问题 |
树状数组 | 处理数组的前缀和、单点更新、区间更新问题 |
为什么需要这么多数据结构呢?答案是:没有「完美的」数据结构与算法。
没有「完美的」数据结构与算法
任何算法都是一种交换。这句话的意思是我们没有办法得到所有想要得到的东西,总是要用某种东西交换另一种东西。有些朋友可能听说过分布式系统有三个指标:
- Consistency(一致性)
- Availability(可用性)
- Partition tolerance(分区容错)
它们的第一个字母分别是 C、A、P。这三个指标不可能同时做到,这个结论就叫做 CAP 定理。对于算法与数据结构同样是这样,时间性能和空间消耗通常来说是不可能同时最优的,一个算法和数据结构对于一个任务来说是优点,但是放在另一个场景下就可能是缺点。我们在进行算法设计的时候,或者是用时间换空间,或者是用空间换时间,或者是综合考虑了二者,设计一个性能折中的方案。
总结
在这一小节的最后,我们做一个总结。我们这门课程的定位与目标听众是:算法与数据结构零基础的朋友。算法是解决问题的方法,数据结构是存放、处理数据的容器和缓存。没有「完美的」数据结构与算法,我们需要针对具体问题给出最合适的解决方案。
学习建议
时间复杂度
时间复杂度和空间复杂度是一种事前评估的方法。这种方法叫做大 O 复杂度表示法(大 O 读作:大欧、big 欧)。与此相关的两个概念是「时间复杂度」与「空间复杂度」。
渐进时间复杂度
常见的时间复杂度计算规则
去系数、去常数、去低阶
-
常数加法系数看做 0
一段程序必须要做的操作(常数次操作)不纳入复杂度的计算。一般而言,常数次操作都不会是造成程序性能瓶颈的原因。 -
对于一个多项式,只保留最高次幂的项,并且乘法系数化简成 1
常见结论:-
一次遍历,里面不再有循环的操作,时间复杂度是 O(N)。也就是说,我们把输入数据里所有的元素看一遍,就可以得到结果。这样的算法称为具有线性复杂度的算法。
-
双重循环,内外层都与输入规模相关的时候,时间复杂度是 O(N2)
注意:有些算法形式上是双重循环,但事实上,程序只遍历了数组一次或者若干次,这样的算法时间复杂度为 O(N),
-
-
对数或者是含有对数乘法因子的项,对数底都看作 2
对数级别的时间复杂度,常见且典型的算法是「二分查找」,时间复杂度的表达式为 O(log N)
时间复杂度分析法则
1. 加法法则
如果一段代码有好几个时间复杂度,那么这段代码的时间负责度= 最大的那个时间复杂度
如果有一段代码 有两个时间 复杂度 O(m) O(n),我们并不知道m和n分别是多少,所以这段代码的时间复杂度为T(n)= O(m) + O(n) = O(m+n)
2. 乘法法则
嵌套循环
T(n)= O(n) * O(n) = O(n2)
常用时间复杂度
从上到下依次性能越来越差
常数阶 O(1)
对数阶 O(log N)
线性阶 O(n)
线性对数阶 O(nlogn)
平方阶 O(n2)
立方阶 O(n3)
…
k次方阶 O(nk)
指数阶 O(2n)
阶乘阶 O(n!)
空间复杂度
空间不随着输入的数据规模变化 O(1)
空间随着输入的数据规模变化 O(n)