- 什么是时间复杂度和空间复杂度?
- 大O的渐进表示法
- 常见时间复杂度计算举例
- 常见空间复杂度的计算
简单来说,时间复杂度和空间复杂度是衡量一个算法效率的。
时间复杂度对应时间效率,空间复杂度对应空间效率
。
其中时间效率并不是说程序具体执行了多久,单给一个程序一个算法是不能确定它的执行时间,因为你需要在机器上跑起来才能确定。
而一个算法所花费的时间与其中语句的执行次数成正比,所以就定义算法中基本 *** 作的执行次数为该算法的时间复杂度
。
其中空间效率也不是说一个算法在运行过程中临时占用多少具体大小的存储空间。而一个算法临时占用的空间和它运行过过程中定义变量所开辟的空间有关,它定义的变量越多,要开辟的空间也越多,所以空间复杂度算的是变量的个
数。
总的来说,时间复杂度主要衡量的是一个算法的运行速度,而空间复杂度主要衡量一个算法所需要的额外空间。但是现在随着技术的发展,计算机的存储容量已经达到了很高的程度,空间不再是限制一个算法的条件,所以我们更多关注一个算法的时间复杂度
。这也是经常出现用空间换时间
的做法的原因。
大O的渐进表示法
在计算一个算法的时间复杂度和空间复杂度的时候,我们不会计算程序具体的执行次数和具体开辟了多少变量,而是用大O的渐进表示法去描述。
大O符号 (Big O notation) 是用于描述函数渐进行为的数学符号
。
推导大O阶方法:
用常数1取代运行时间中的所有加法常数。
在修改后的运行次数函数中,只保留最高阶项。
如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。
我们通过一个具体的实例去理解它:
void Func1(int N)
{
int count = 0;
for (int i = 0; i < N ; ++ i)
for (int j = 0; j < N ; ++ j)
count++;
for (int k = 0; k < 2 * N ; ++ k)
count++;
int M = 10;
while (M--)
count--;
printf("%d\n", count);
}
很容易计算,Func1 函数共执行了 N2+2×N+10 次。
用 大O的渐进表示法 表示如下:
- 用常数 1 取代运行时间中的所有加法常数:
N2+2×N+1 - 在修改后的运行次数函数中,只保留最高阶项:
N2 - 如果最高阶项存在且不是 1 ,则去除与这个项目相乘的常数:
N2
所以, Func1 函数的时间复杂度就是 O(N2) 。
同理,它的空间复杂度就是 O(1) 。
大O的渐进表示法 的合理之处在于,当 N 特别大的时候,整个算法的执行次数是取决于最高阶项的,且它的常数系数对结果的影响并不大。
还是以 Func1 举例:
- 当 N=10 时,要执行 130 次
- 当 N=100 时,要执行 10210 次
- 当 N=1000 时,要执行 1002010 次
总而言之,大O的渐进表示法 去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。
但是,也有算法的时间复杂度不唯一的情况,比如二分查找,可能上来就找到了,也可能找了一遍都没找到。这时我们关注的是算法的最坏运行情况
,这点是很重要的。
常见时间复杂度计算举例
实例1:
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);
}
Func2 具体执行次数为 2×N+10 ,
用 大O的渐进表示法 表示就是 O(N) 。
实例2:
void Func3(int N, int M)
{
int count = 0;
for (int k = 0; k < M; ++k)
count++;
for (int k = 0; k < N; ++k)
count++;
printf("%d\n", count);
}
Func3 基本 *** 作执行了 M+N 次,
有两个未知数 M 和 N ,
时间复杂度为 O(N+M) 。
实例3:
void Func4(int N)
{
int count = 0;
for (int k = 0; k < 100; k++)
count++;
printf("%d\n", count);
}
基本 *** 作执行了 10 次,通过推导大O阶方法,
时间复杂度为 O(1)。
实例4:
int strchr(const char* str, char character)
{
int count = 0;
while (*str != ')'if
{
( *==str ) characterreturn
; count++
str;++
count;}
return
- 1;}
str
这段代码是实现在字符串 character 中找到字符 1 首次出现的位置的下标并返回。
基本 *** 作执行最好 N(N = strlen(str)) 次,
最坏 O(N) 次,
时间复杂度一般看最坏,所以是 void 。
实例5:
BubbleSort (int*, arrint ) szfor
{
( int= i 0 ;< i - sz 1 ;++ i)for
( int= j 0 ;< j - sz 1 - ; i++ j)if
( [arr]j[ > arr+j 1 ])int
{
= tmp [ arr]j;[
arr]j= [ arr+j 1 ];[
arr+j 1 ]= ; tmp}
}
N+(N-1)+…+1
这段代码就是基本的冒泡排序,它的时间复杂度该怎么算呢?
首先基本 *** 作要执行 N * (N+1) / 2 次,
也就是 2,
所以时间复杂度就是 O(NBinarySearch) 。
实例6:
(int* , numsint , rightint , leftint ) targetwhile
{
(<=left ) rightint
{
= mid + left ( -right ) left/ 2 ;if
([nums]mid== ) targetreturn
; midif
([nums]mid) > target=
right - mid 1 ;else
if ([nums]mid< ) target=
left + mid 1 ;}
return
- 1;}
x
这是一个简单的二分查找算法。
考虑它最坏的执行次数:
最坏情况是遍历整个有序数组,
设执行次数为 x ,
则有 2N = N,
2 为数组大小,
那么 x = log2N。
所以它的执行次数最坏就是 logO(logN)N。
而在实际表示时是忽略它的底数的,
所以它的时间复杂度就是 O(logN) 。
而对于很多题目来说,可能会限制时间复杂度不低于 long ,这种情况一般就要考虑用到二分查找的算法。
实例7:
long Factorial (size_t) Nreturn
{
< N 2 ? : N Factorial (-N 1 )* ; N}
N
这是一个求 N = 1 的阶乘的算法,用的递归实现。
那它执行了多少次呢?
它的递归出口是 N ,
所以在走到出口之前递归了 2×N 次;
返回又是一次,所以函数总共执行了 2×N×1 次;
每次调用函数,函数内部只进行一次基本 *** 作,
所以总基本 *** 作次数就是 O(N)。
综上,这个算法的时间复杂度就是 A。
时间复杂度是区分一个算法好坏的最直观的量,一般算法效率高低有如下之分:
O(1) > O(logN) > O(N) > O(NlogN) > O(NN) > O(Avoid) > O(N!)
直观一点就是这样:
常见空间复杂度的计算
实例1:
BubbleSort (int*, arrint ) szfor
{
( int= i 0 ;< i - sz 1 ;++ i)for
( int= j 0 ;< j - sz 1 - ; i++ j)if
( [arr]j[ > arr+j 1 ])int
{
= tmp [ arr]j;[
arr]j= [ arr+j 1 ];[
arr+j 1 ]= ; tmp}
}
tmp
像冒泡排序这样的算法,
它只有 1 需要额外开辟空间,
所以它只使用了 O(1)
个额外空间,
那它的空间复杂度就是 long 。
实例2:
long *Fibonacci (size_t) nif
{
( ==n 0 )return
NULL ;long
long *= fibArray ( longlong *)malloc((+n 1 )* sizeof (longlong ));[
fibArray0]= 0 ;[
fibArray1]= 1 ;for
( int= i 2 ;<= i ; n++ )i[
fibArray]i= [ fibArray-i 1 ]+ [ fibArray-i 2 ];return
; fibArray}
n
这段代码是列举前 fibArray 个斐波那契数。
它给 n 动态开辟了 n 个空间,
也就是额外开辟了 O(N)
个空间。
所以它的时间复杂度就是 long 。
实例3:
long Factorial (size_t) Nreturn
{
< N 2 ? : N Factorial (-N 1 )* ; N}
N
还是上面的那段计算阶乘的算法。
函数每次调用都要开辟一个函数栈帧,
这里一共调用了 瞬时开辟的最大额外空间 次,
N的个数就是 O(N)
,
所以算法的时间复杂度就是 某一时刻开辟的最大空间的个数。
对于空间复杂度的计算,需要注意的是我们并不在意它有多少个变量,我们只在意它在。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)