写在前面的话: 这里写logN
就表示以2为底数
,因为2为底数打不出来哈哈哈
时间复杂度
- 用于评价一个算法流程的好坏.
- 先看时间复杂度的指标(再看常数项),即
(N^2)/100 + 10N + 1
我们也看成时间复杂度是O(N^2)
,其中N就是样本量 . - 然后再分析不同数据样本下的实际运行时间.
- 先看时间复杂度的指标(再看常数项),即
- 常数时间的操作:
- 概念:一个操作如果和数据量没有关系,每次都是固定时间内完成的操作,就叫做常数操作,例如加减乘除,位运算操作,数组寻址(就是根据下标获取数组中的元素,比如a[1])等.
- ***时间复杂度为一个算法流程中,
最差情况下
常数操作数量的指标.常用O
表示(读作big O),具体来说,在常数操作数量的表达式中,不要低阶项,只要高阶项,并且忽略掉高阶项的系数,剩下的部分如果记作f(N)
,那么时间复杂度为O(f(N))
***. - 几个简单的理解时间复杂度的例子:
- 一个有序数组A,另一个无序数组B,请打印B中的所有不在A中的数,A数组长度为N,B数组长度为M.
- 算法流程1:
- 对于数组B中的每一个数,都在A中通过***遍历***的方式找一下.
- 很明显该算法的时间复杂度特别大,遍历一次代价为
O(N)
,一共M个数,所以总的代价为O(M*N)
.
- 算法流程2:
- 对于数组B中的每一个数,都在A中通过***二分***的方式找一下.
- 二分搜索,比如针对一个***有序(假定是从小到大)***的数组,假设我要找
2
有没有在这个数组里.- 我先找到
中间位置
的数,看比2大还是小.- 比2大,因为是从小到大排序的,所以中间右边的数字一定比2大,我们就在左边继续划分一半一半地找,直到没有了或者找到了为止.
- 比2小,说明中间左边的数字一定比2小,我们就在右边继续划分一半一半地找,直到没有了或者找到了为止.
- 等于2,就直接返回
- 我先找到
- 在这个过程中,我们
数组寻址和两个数比较
的过程都是常数操作时间的O(1),每次砍一半,这样下来,常数操作次数为logN(以2为底)
,即一个数组长度为8,最多砍3次. - 由此可见,二分查找的时间复杂度为O(logN),以2为底
- 因此我们也可以推出,查找一次的时间复杂度为
O(logN)
,一共有M个数,时间复杂度则为(MlogN)
.
- 算法流程3:
- 先把B数组排序,然后用类似***外排***的方式打印所有在A中出现的数.
- 思路就是,B排序后,两个数组同时从0位置开始,即
A[i]
和B[i]
互相比较.- 若A[i] > B[i],则B[i]肯定不在A数组中,打印B[i],然后B数组下标++.
- 若A[i] < B[i],B数组下标不动,A数组下标++.
- 若A[i] == B[i],A数组下标不动,B数组下标++.
- 针对这个流程
- 第1步:排序的时间复杂度,可以达到
NlogN
. - 第2步:快排,时间复杂度为
O(N + M)
(N,M为A和B数组长度).
- 第1步:排序的时间复杂度,可以达到
- 由此可见,该流程的时间复杂度为
NlogN + O(N + M)
.
- 三个流程,三种时间复杂的表达…我们应该如何分析其好坏?
- 算法流程1:
- 给长度为N的数组排序(其中寻址,比较操作都是常数级别的).
- 第一次从
0-(N-1)
中找出最小值min放在第0个位置,过N个数. - 第二次从
1-(N-1)
中找出最小值min放在第1个位置,过N-1个数. - …以此类推.
- 一共需要找的次数为
(N + (N-1) + ... + 2 + 1)
,这是一个等差数列,其和为n(n + 1) / 2
(这个就可以理解为常数操作数量的表达式),再乘以一个O(1)
(它是单次操作所需要的时间),即(n(n + 1) / 2) O(1)
. - 估算其时间复杂度是这样子的:
- 不要低阶项,只要高阶项,并且忽略掉高阶项的系数.
- 由此推出上述排序算法时间复杂度为
O(n^2)
.
- 第一次从
- 一个有序数组A,另一个无序数组B,请打印B中的所有不在A中的数,A数组长度为N,B数组长度为M.
空间复杂度
- 空间复杂度其实很好想,开辟了几个新的空间,那就是几,比如我一个循环,循环过程中我新开辟了一个长度为10的数组来存储数据,那么空间复杂度就是O(10),以冒泡排序为例,当两个数需要交换时,我们就会定义一个中间变量
temp
,这就是我们唯一新开辟空间的地方,这时我们就说冒泡排序的空间复杂度为O(1).