目录
1. 算法效率
2. 时间复杂度
2. 1 大O的渐近表示法
实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们使用大O的渐进表示法。
- 用常数1取代运行时间中的所有加法常数。
- 在修改后的运行次数函数中,只保留最高阶项。
- 如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。
比如:F(N) = 2*N^2 + 2*N + 10,使用大O表示法之后,时间复杂度为O(N^2)
- 最坏情况:任意输入规模的最大运行次数(上界)
- 平均情况:任意输入规模的期望运行次数
- 最好情况:任意输入规模的最小运行次数(下界)
在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)
2.2 时间复杂度举例
代码一:二分查找
int BinarySearch(int* a, int n, int x)
{
assert(a);
int begin = 0;
int end = n - 1;
while (begin < end)
{
int mid = begin + ((end - begin) >> 1);
if (a[mid] < x)
begin = mid + 1;
else if (a[mid] > x)
end = mid;
else
return mid;
}
return -1;
}
二分查找是在一个有序数组中查找某一个元素时的比较高效的方法,在进行二分查找时,每次都取数组的中间值,然后再根据中间值与查找值的大小来确定我们要查找的元素在那一半,然后在对找到的哪一半数组进行重复的操作,直到找到我们需要的元素,使用二分查找时,最坏的情况是把数组除的只剩最后一个元素。
这时表达式为 N÷2÷2÷2÷2…÷2÷2=1
也即 N=1×2×2×2…×2×2
每相乘一次,就进行了一次基本操作,一共进行了log₂N次,所以时间复杂度为O(log₂N)。
代码二:递归
long long Fibonacci(size_t N)
{
return N < 2 ? N : Fibonacci(N-1)+Fibonacci(N-2);
}
上述是使用递归来计算斐波那契数列的函数,递归的方法来计算斐波那契数列是非常低效的,最好还是使用循环的方法,但是递归的时间复杂度是多少呢?
每一次调用这个函数时,时间复杂度是一个常数,那么递归的时间复杂度就是一共调用了多少次函数,下面分析一下调用了多少次函数:
其调用结构如图所示
当输入N时,函数会进行两调用,然后不断地调用,其调用的结果如图所示,调用的总次数为F(N)=2⁰+2¹+2²+…+2^(N-1)=2^N,所以该算法的时间按复杂度为O(2^N)
用递归来算斐波那契数的算法的时间复杂度太高了,也说明了这个算法的低效。
3. 空间复杂度
同样地, 所有常数个都用1来替代,多数情况下空间复杂度都是O(1)
比如:循环,变量个数不会增加(时间会累积,空间不会累积)。
malloc(n); // 开辟了n个元素的数组,这时空间复杂度为O(n)