时间复杂度作用
算法效率的目的是看算法实际是否可行,当同一问题存在多种算法时,可进行时间复杂度和空间性能的比较,以便从中挑选出较优算法。
时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。在计算 机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。
时间复杂度的定义
在计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。一个算法执行所耗费的时间,从理论上说,是不能算出来的,只有你把你的程序放在机器上跑起来,才能知道。但是我们需要每个算法都上机测试吗?是可以都上机测试,但是这很麻烦,所以才有了时间复杂度这个分析方式。一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的时间复杂度。 即:找到某条基本语句与问题规模N之间的数学表达式,就是算出了该算法的时间复杂度。
影响算法的时间代价最主要因素是问题规模,问题规模是算法求解问题输入量的多少,是问题大小的本质表示,一般用整数n表示,n越大算法的执行时间越长。
Func1 执行的基本操作次数 :F(N)=N^2+2*N+10
N = 10 F(N) = 130 N = 100 F(N) = 10210 N = 1000 F(N) = 1002010
实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这 里我们使用大O的渐进表示法。
大O的渐进表示法:
大O符号(Big O notation):是用于描述函数渐进行为的数学符号。
推导大O阶方法:
1、用常数1取代运行时间中的所有加法常数。
2、在修改后的运行次数函数中,只保留最高阶项。
3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶
使用大O的渐进表示法以后,Func1的时间复杂度为:O(N^2)
通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。 另外有些算法的时间复杂度存在最好,平均和最坏情况:
最坏情况:任意输入规模的最大运行次数(上界) 平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界) 例如:在一个长度为N数组中搜索一个数据x 最好情况:1次找到 最坏情况:N次找到
平均情况:N/2次找到
在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)
常见时间复杂度计算举例
// 计算Func2的时间复杂度?
void Func2(int N)
{
int count = 0;
for (int k = 0; k < 2 * N ; ++ k)
{
++count;
}
int M = 10;
while (M--)
{
++count;
}
printf("%d\n", count);
}
基本操作执行了2N+10次,通过推导大O阶方法知道,时间复杂度为 O(N)
void Func4(int N)
{
int count = 0;
for (int k = 0; k < 100; ++ k)
{
++count;
}
printf("%d\n", count);
}
基本操作执行了M+N次,有两个未知数M和N,时间复杂度为 O(N+M)
int BinarySearch(int* a, int n, int x)
{
assert(a);
int begin = 0;
int end = n-1;
// [begin, end]:begin和end是左闭右闭区间,因此有=号
while (begin <= end)
{
int mid = begin + ((end-begin)>>1);
if (a[mid] < x)
begin = mid+1;
else if (a[mid] > x)
end = mid-1;
else
return mid;
}
return -1;
}
二分查找算法的步骤如下:
-
确定查找范围:开始时查找范围是整个数组。
-
计算中间位置:计算查找范围内中间位置的元素索引。
-
比较中间元素:将中间元素与目标值进行比较。
-
调整查找范围:
- 如果中间元素正好是目标值,则查找成功。
- 如果目标值小于中间元素,则在数组的左半部分(中间元素之前的部分)继续查找。
- 如果目标值大于中间元素,则在数组的右半部分(中间元素之后的部分)继续查找。
-
重复步骤2到4:在新范围内重复这过程,直到找到目标值或范围为空。
-
结束查找:当查找范围为空时,表示整个数组已经搜索完毕,目标值不存在于数组中。
时间复杂度一般都是最坏情况,二分查找法最坏的情况是数组中只剩下一个元素或者找不到元素,在这之前,二分查找法会不断缩小查找范围,即查找范围不断变为原来的1/2。
查找区间不断变化,N/2/2/2〰〰,查找了多少次就是除以几个2,假设循环走了x次,就是x个2相乘,即2^x=N,循环次数x=logN。所以二分查找法的时间复杂度为O(logN)。
// 计算阶乘递归Fac的时间复杂度?
long long Fac(size_t N)
{
if(0 == N)
return 1;
return Fac(N-1)*N;
}
在递归中,从N到(N-1)最后到0次一共调用了N+1次,由于时间复杂度省略常数,所以递归的时间复杂度为O(N)。总的来说递归时间复杂度就是所有的递归调用次数累加。
long long Fib(size_t N)
{
if(N < 3)
return 1;
return Fib(N-1) + Fib(N-2);
}
斐波那契数列的时间复杂度fib(N)到fib(N-1)和fib(N-2),一个变两个,两个变四个,直到出现fib1和fib2,第一层一个有2^0,第二层有2^1个,第三层有2^2个,最后一层有2^n-2个,累计调用次数为2^0+2^1+……+2^(N-2),为等比数列,总和为2^(N-1)-1次,省略常数,斐波那契数列的时间复杂度为O(2^N)。注意:O(2^N)只有理论意义,实践中太慢了。