-
如何衡量一个算法的好坏?
算法效率分为两种:第一种是时间效率,第二种是空间效率.时间效率称为时间复杂度,而空间效率称为空间复杂度.时间复杂度主要衡量的是一个算法的运行速度,而空间复杂度主要衡量的是一个算法所需要的额为空间.` -
什么是时间复杂度?
算法的时间复杂度是一个函数,它定量描述了该算法的运行时间.一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的时间复杂度. -
时间复杂度为什么不使用时间来衡量而使用基本语句的运行次数来衡量?
如果我们使用时间来表示算法的时间复杂度,那么我们只有在程序运行起来才能测算时间,每个算法都要上机测试的话就会很麻烦,为了在不运行时就能看出来一个算法的效率,就有了时间复杂度这一分析方式.一个算法所花费的时间与基本语句执行次数成正比,所以可以让基本操作的执行次数为算法的时间复杂度. -
时间复杂度的O渐进表示法
实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这 里我们使用大O的渐进表示法。
大O符号(Big O notation):是用于描述函数渐进行为的数学符号。
推导大O阶方法:
1、用常数1取代运行时间中的所有加法常数。 2、在修改后的运行次数函数中,只保留最高阶项。
3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。 使用大O的渐进表示法以后,Func1的时间复杂度为: O(N^2) -
时间复杂度的:最优、平均、最差情况,为什么时间复杂度看的是最差情况?
最坏情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界)
例如:在一个长度为N数组中搜索一个数据x 最好情况:1次找到 最坏情况:N次找到
平均情况:N/2次找到
在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N) -
如何求解:二分查找、递归求阶乘、递归斐波那契的时间复杂度?
二分查找:
最好1次,最坏O(logN)次,时间复杂度为 O(logN) ps:logN在算法分析中表示是底 数为2,对数为N。有些地方会写成lgN。(建议通过折纸查找的方式讲解logN是怎么计算出来的)
二分查找每次排除一半不适合的值:
一次二分查找剩下的值: n / 2
二次二分查找剩下的值: n / 4
三次二分查找剩下的值: n / 8
…
直到m查找到最后只剩下一个元素
n / (2 ^ m) = 1;
2 ^ m = n;
所以最差的情况是查找log n次(是以二为底n的对数);
二分查找的时间复杂度为O(log n);
递归求阶乘 :
基本操作递归了N次,时间复杂度为O(N)。
算法的递归方程为: T(n) = T(n - 1) + O(1);
迭代展开: T(n) = T(n - 1) + O(1)
= T(n - 2) + O(1) + O(1)
= T(n - 3) + O(1) + O(1) + O(1)
= …
= O(1) + … + O(1) + O(1) + O(1)
= n * O(1)
= O(n)
这个例子的时间复杂性是线性的。
递归斐波那契:
递归斐波那契数列时间复杂度为 O (2^n); -
什么是空间复杂度?
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度 。空间复杂度不是程序占用了多少 bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。空间复杂度计算规则基本跟实践 复杂度类似,也使用大O渐进表示法。 -
如何求空间复杂度? 普通函数&递归函数
计算BubbleSort的空间复杂度?
void BubbleSort(int* a, int n) {
assert(a);
for (size_t end = n; end > 0; --end) {
int exchange = 0;
for (size_t i = 1; i < end; ++i) {
if (a[i-1] > a[i]) {
Swap(&a[i-1], &a[i]);
exchange = 1;
}
}
if (exchange == 0)
break;
}
}
计算Fibonacci的空间复杂度?
long long* Fibonacci(size_t n) {
if(n==0)
return NULL;
long long * fibArray = new long long[ n+1];
fibArray[0] = 0;
fibArray[1] = 1;for (int i = 2; i <= n ; ++i) {
fibArray[i ] = fibArray[ i - 1] + fibArray [i - 2];
}
return fibArray ;
}
计算阶乘递归Factorial的时间复杂度?
long long Factorial(size_t N) {
return N < 2 ? N : Factorial(N-1)*N;
}
实例1使用了常数个额外空间,所以空间复杂度为 O(1) 2.
实例2动态开辟了N个空间,空间复杂度为 O(N) 3.
实例3递归调用了N次,开辟了N个栈帧,每个栈帧使用了常数个空间。空间复杂度为O(N) -
分析递归斐波那契数列的:时间、空间复杂度,并对其进行优化,伪递归优化—>循环优化
第n个数执行的次数为 20+21+…+2^(n - 2)
是一个等比数列: S = [a1 + (1 - q^n)] / (1 - q) = 2^(n - 2) - 2;
时间复杂度: O(2^n)
优化为伪递归:
long long fib(long long first,long long second,long long n){
if(n < 3)
return 1;
if(n == 3)
return first + second;
return fib(second,first+second,n-1);
}
优化为循环:
long long fib(long long n){
int i = 0;
long long first = 1,second = 1;
long long ret = 0;
for(i=3;i<=n;++i){
ret = first + second;
first = second;
second = ret;
}
return second;
} -
总结常见时间复杂度
第一种
O(1)
常数复杂度, 最快的算法
取数组第1000000个元素
字典和集合的存取都是O(1)
数组的存取是O(1)
第二种
O(logN)
对数复杂度
假设有一个有序数组, 以二分法查找
第三种
O(n)
线性复杂度
假设有一个数组, 以遍历的方式在其中查找元素
第四种
O(nlogn)
求两个数组的交集, 其中一个是有序数组
A数组每一个元素都要在B数组中进行查找操作
每次查找如果使用二分法则复杂度是 logN
第五种
O(n2)
平方复杂度
求两个无序数组的交集