/*依旧写在前面:
欢迎讨论,本节内容主要就复习算法的一些数学基础以及程序设计的一些概念
参考文献:数据结构与算法分析(黑皮书)
新编数据结构
参考课程:慕课网玩转算法面试(?是这个名字吗dbq)
最后,部分图片参考网络
*/
本讲主要侧重的问题:
1.算法届主要讨论的问题是什么?
2.时间复杂度是什么?什么是时间频度?大O和f(n)之间的关系?
3.如何去计算一个算法的时间复杂度?
4.关于时间复杂度,业界和学术界的看法分别是什么?
5.关于时间复杂度,实际操作中容易出现的理解错误
6.算法的时间复杂度,实际上是与用例相关的
引言:算法届讨论的问题到底是什么?
我们先来看两个问题,
-
第一个问题是选择问题,通常我们将有一组N个数而要确定其中第k个最大者的问题称之为选择问题,诚然面对这个问题我们有很多解决办法:
-
把这些数据读进数组中,然后通过递减的方式一个个排序,然后返回位置k上的元素
如图所示:
-
或者可以把前k个元素读入数组并(以递减的顺序)对其排序,接着,再把剩下的元素逐个读入。当新元素被读到的时候,如果它小于数组中第k个元素则忽略,否则就将其放在数组中正确的位置上,同时将数组中的第一个元素挤出数组
-
这两种算法都很简单,也可以解决问题,所以我们自然要问:到底那个算法更好?哪个算法更高效?诚然,如果我们的数据量足够小,那么这些算法的运行时间不到几毫秒,但是如果我们把数据量加到百万千万级别的,那么这些算法处理的时间很可能超过一天。
-
-
所以在许多问题中,可以写出一个解决问题的方式并不够,有的时候还需要考虑在巨大数据量上运行的时间问题,而这个,则就是重多工程师们需要搞定并且优化的问题了
什么是大O(时间复杂度)
一,一些关于时间复杂度的基本概念
-
大O的形式定义:
-
频度T(n):算法的时间复杂度以算法中重复执行的次数(简称为频度)作为算法的时间度量,一般不必精确计算,只需计算大概就好了,如O(1),O(logn),O(N),O(n^2)
-
因此,我们可以得出f(n),T(n)与大O的关系:
O(f(N))表示算法所需执行的指令数,并且和f(n)成正比M(这个常数M=T(n)/F(n)),所以O(f(n))是f(n)的一个上届。 -
这里的概念不用深究,只需要理解就好
这里举个例子:
二分查找法的大O是O(logn)级别的---------------------所以它所需执行的指令数为alogn
寻找数组中的最大/最小值O(n)级别的--------------------------所以它需要执行的指令数为bn
假设有一个算法的是O(n)级别的,但是它的常数是10000n,算法B是O(n^2)级别的,所需执行指令数为10n ^ 2,在10-1000级别的时候,算法A都是比算法B高效的,然而随着数据量的加大,知道10 ^ 6级别的数据的时候,算法A的执行效率是算法B的0.001倍
同时这样的例子也印证了一个概念,算法的运行效率是和常数M没有关系的,而是和大O中的n的级别有关
二,如何确定时间复杂度
-
确立算法时间复杂度的方法一般如下:
- 1.确定问题规模n:通常在形参中给出
- 2.计算算法的语句频度T(n),通常以算法中的基本运算为核心(循环)求算法执行的次数
- 3.用大O表示,只保留T(n)的最高阶项,如果这个最高项目的序数不为1,除去这个序数
-
例题1:分析以下算法的时间复杂度:
void func(int n){
int i=1,k=100;
while(i<=n)
{
k++;
i+=2;
}
}
- 那么,对于这种问题,我们怎么进行分析呢?
- 1.从数学定义入手:
设while循环执行的次数为T(n)次,i从1开始递增,最后取值为1+2T(n),因此有1+2T(n)<=n,因此可推导出T(n)<=(n-1)/2=O(n) - 2.从执行多少步入手:
i=1,到最后i=n,每一次循环i加2,所以执行的步骤也就是为(尾项-首项)/每一次改变的量的大小,所以T(n)=(n-1)/2,也就是i从1到n,执行了n-1/2步,通过执行的步数我们可以判断出这是一个O(n)级别的算法 - 所以该算法的时间复杂度为O(n),也就是随着数据规模的增加,所用时间与数据规模成线性关系
- 例题2:让我们再分析一下下面这个算法的时间复杂度
void func(int n){
int i=1;
while(i<n)
{
i=i*2;
}
}
- 同样的,让我们再次分析这个算法的时间复杂度
- 定义法:
假设while循环里i执行了T(n)步,那么i=1+T(n)^ 2,因此有1+2^T(n) < n,所以 2 ^ T(n)<n-1(为了简化运算可以近似看为n),所以T(n)=log2(n-1)≈log2N,所以复杂度为O(log2N) - 执行步数法:
i的初始值为1,i为了停止循环最终的运行结果是i=n,每一次循环i*2,那么为了达到n,i需要执行log2(N-1)步,
- 定义法:
- 这种方法各有利弊,大家可以权衡使用
三,不同时间复杂度的算法的执行效率与量级
- 关于不同时间复杂度的算法,他们执行效率如图所示
四,关于学术界和业界对于算法的一些不同看法:
- 在学术界,严格的讲,O(f(n))表示算法执行的上界 归并排序的时间复杂度是O(logn),但是也可以说是O(n^2)的
- 但是在业届,一般值得是算法执行的最低上界
五,一个在算法问题中容易出错的问题
- 在我们设计算法的时候,经常很容易设计出AlogA+B,或者其他组合的算法,这时候我们往往容易把A和B混淆
- 例如我们对邻接表实现的图进行遍历,时间复杂度为O(V+E)
- 这里我们举出一个例子
有一个字符串数组,将数组中的每一个字符按照字母排序,之后再将整个字符串数组按照字典序排序,整个操作的时间复杂度是多少?
错误思路:
O(nlogn+nlogn)= O(n^2logn)
这一步混淆了字符串的长度和数组中有多少数组的参数搞混淆了,这两个不是一个n
正确思路:
假设最长的字符串长度为s,数组中有n个字符串
对每一个字符串按照字母序排序O(slogs)
将数组中的每一个元素按照字母序排序:O(nslog(s))
将整个字符串按照字典序排序:O(snlog(n))
综上所述:
O(nslog(s))+O(snlog(n))=O(nslogs+sn*logn)=O(sn(logs+logn))
六,算法复杂度在有些情况是用例相关的
- 例如插入排序算法O(n^2),最差情况下它的执行是O(n ^ 2),最好情况是O(n),平均情况下是O(n ^ 2)
- 其他算法同理,一些O(nlogn)级别的算法在一些很恶劣的情况下会退化到O(n^2)级别的算法
- 严谨的算法分析,需要分析最好和最差的算法复杂度,然而大多数时候,我们需要关注的只是平均的算法复杂度。
本来想打算写关于算法的时间复杂度实验的,现在想想还是放在后面去阐述比较好,所以下面是关于本讲的一个自我检测
- 算法届讨论的问题是什么?
- 时间复杂度是什么?
- 大O与f(n)的关系是什么?
- 如何去计算算法的时间复杂度?
- 不同算法的时间复杂度是一个怎样的概念?是否能用图形说明?
- 学术界和业界对于算法的时间复杂度有怎样的概念?
- 关于算法的时间复杂度的分析需要注意什么问题?
- 算法的时间复杂度是与用例相关的吗?如果是,请举出例子
写在最后:依旧,一起进步!!😃