前言:解决同一个程序问题可以通过多个算法解决,那么要怎样判断一个算法的优劣呢?🤔
算法复杂度
算法复杂度是对某个程序运行时的时空效率的粗略估算,常用来判断一个算法的好坏。
我们通过两个维度来看算法复杂度——时间复杂度 和 空间复杂度。
时间复杂度
随着计算机科学技术的发展,计算机的内存从原来的256MB、512MB直到现在的16G甚至32G,存储空间的限制逐渐减小。因此,现在对于程序算法复杂度,更多地关注到时间上面去。
不难知道,假设每条语句的执行时间相同,则有:
程序运行所需要的总时间 = 每条语句的执行时间 x 执行语句的次数——①
这条公式里,执行次数是可以确定的,但是执行时间缺难以确定。
int begin = clock();//clock()函数返回程序运行到当前位置已花费的时间。
int count = 0;
for(int i = 0; i < 100000000; ++i)
{count++;
}
int end = clock();
printf("%d",begin - end);//打印循环完毕后总共消耗的时间
PS:clock()函数包含在头文件time.h中,返回值的时间单位是毫秒。
我们可以试着在本地多次运行这段代码,然后就可以发现,并不是每一次的运行时间都是相同的,但是相差并不大。
而对于复杂度来说,我们只需要进行粗略的估算即可,因此,我们省略公式①中对执行时间的计算,只关注可以准确计算得出的执行次数。
T(N)函数式
时间复杂度的T(N) 函数式用来表示程序的执行语句次数。
int time = 0;//------------1
scanf("%d", &time);//-----------1
int count = 0;//---------1
for(int i = 0; i < time; ++i)
{for(int j = 0; j < time; ++j){count++;//-------------N*Nprintf("%d ", count);//---------N*N}
}
for(int i = 0; i < time; ++i)
{count--;//------------N
}
printf("%d",count);//--------1
count = 0;//-----------1
如上代码,我们来用T(N)表达式来表示执行语句次数,可得: T(N)=1+1+1+n*n+n*n+n+1+1 =
这段代码,对执行次数有影响的是time,所以把 time 作为T(N)函数式的变量,用大写字母N来替换。最终 就表示这段代码的执行语句次数。
大O渐进表示法
T(N)函数式还不是我们需要的最终结果,复杂度的最终结果,是一个数量级结果。那我们就要根据T(N)函数式,并运用大O渐进表示法来求得数量级结果。(下面用上文求得的T(N)函数式作例子)
基本规则:
①先求得T(N)函数式。(若对执行语句次数有影响的变量不止一个,函数式就为T(N,M,……)。)已求得T(N)= 。
②取T(N)中最高阶项。取得。
③最高阶项舍去系数(系数取1)。得。
④放入O()的括号中。得O()。
O()就是上述代码的时间复杂度结果。根据O()我们可知,上述代码的时间复杂度是平方级的。
其他规则:
其他规则也务必得遵守其他规则才ok喔。
⑤若最高阶项为常数,则均表示为O(1),表示数量级为常数级。
⑥对于被多个变量的影响执行次数的程序来说:
i.T(N)函数式就有多个变量,我们一般依次用N,M,L,……来替换表示,最终得到一个多元函数式。如T(N,M) = N + M。
ii.不同的变量,各自遵守②③进行取舍。若有如取舍后的结果为 ,就要分情况讨论
N远大于M,则把M看作1;
M远大于N,就把N看作1;
如果N、M相差不大,可以把 N看成M 或者 M看成N。
再进一步取舍,得到最终答案。
当然,不分情况讨论也是正确的大O表示法结果,但是我们更需要的是一个数量级结果,所以,我们最好如上进行进一步讨论。
⑦对数级复杂度,即O()、O()等等,可以写做O()省去底数。
会出现对数级复杂度的例子:
int k = 2;//-------------1
int n = 0;//------------1
scanf("%d", &n);//----------1
while(k < n)
{k *= 2;//------------2^k < n时循环,结束时2^k > n
}
printf("%d", k);
可得程序结束时,有 > > n,可求得循环次数N = ,即k乘了N次2。T(N)=,时间复杂度为O(),或者O()皆可(因为底数对于对数得最终结果影响很小,所以一般都写成第一种情况)。
⑧递归情况
如下代码,求n的阶乘:
int fac(int n)
{if(n == 0){return 1;}return fac(n - 1) * n;
}
可知求阶乘要进行n层递归。同时,肉眼可见,每次递归的时间复杂度为O(1),因此,完成n层递归的时间复杂度为 N * O(1)= O(N)。(也就是直接在数量级层面做乘法即可)
以上规则已足够应对对大多数程序的算法判断了。
空间复杂度
即使存储空间现在已经不是重头问题了,但是存储空间也不能随意浪费。空间复杂度仍然是算法好坏的评判标准之一。
T(N)函数式
空间复杂度的T(N)函数式用来表示 程序运行时 创建的 空间个数。
运行时创建的空间——编译完成之后,创建的所有空间。包括全局变量,局部变量,函数栈帧……
空间个数——不考虑空间的大小,只考虑开辟空间的个数。
注意:
i.数组的空间个数为数组长度。
ii.动态申请的空间,如malloc(sizeof(int)*n),这个语句的T(N)=N。
int func(int** arr, int n)
{*arr = (int*)malloc(sizeof(int) * n);//-------------nif(!*arr){perror("malloc");return 0;}return 1;
}int main()
{int* arr = NULL;//----------------1scanf("%d", &n);func(&arr, n);//------------1——函数栈帧(里面包含了形参的开辟空间)return 0;
}
得T(N)=1+1+n=N+2,其中两个1,分别是指针arr的空间,和func函数栈帧的空间;n为func中动态申请的空间。
然后同时间复杂度一样,通过大O渐进表示法来求得最终空间复杂度结果。
最终上述代码的空间复杂度是O(N)。
结语
看完这篇博客,相信你已经对算法复杂度有了深刻认识了。有什么疑问和困惑欢迎来评论区留言!!🤩我一定尽力及时解答!!制作不易,求关注!!求点赞!!之后还会有更多有用的干货博客会发出哦!!欢迎做客我的主页!!❤❤Elnaij-CSDN博客❤❤