成为编程大佬!!----->数据结构与算法(1)——算法复杂度!!

前言:解决同一个程序问题可以通过多个算法解决,那么要怎样判断一个算法的优劣呢?🤔

目录

算法复杂度

时间复杂度

T(N)函数式

大O渐进表示法

基本规则:

其他规则:

空间复杂度

T(N)函数式

❀​​​​​​​结语


算法复杂度

算法复杂度是对某个程序运行时的时空效率粗略估算,常用来判断一个算法的好坏。

我们通过两个维度来看算法复杂度——时间复杂度 和 空间复杂度

时间复杂度

随着计算机科学技术的发展,计算机的内存从原来的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*N
        printf("%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 = 2N^{2}+N+5

这段代码,对执行次数有影响的是time,所以把 time 作为T(N)函数式的变量,用大写字母N来替换。最终  2N^{2}+N+5 就表示这段代码的执行语句次数。

大O渐进表示法

T(N)函数式还不是我们需要的最终结果,复杂度的最终结果,是一个数量级结果。那我们就要根据T(N)函数式,并运用大O渐进表示法来求得数量级结果。(下面用上文求得的T(N)函数式作例子)

基本规则:

①先求得T(N)函数式。(若对执行语句次数有影响的变量不止一个,函数式就为T(N,M,……)。)已求得T(N)= 2N^{2}+N+5 。

②取T(N)中最高阶项。取得2N^{2}

③最高阶项舍去系数(系数取1)。得N^{2}

④放入O()的括号中。得O(N^{2})。

O(N^{2})就是上述代码的时间复杂度结果。根据O(N^{2})我们可知,上述代码的时间复杂度是平方级的。

其他规则:

其他规则也务必得遵守其他规则才ok喔。

⑤若最高阶项为常数,则均表示为O(1),表示数量级为常数级。

⑥对于被多个变量的影响执行次数的程序来说:

i.T(N)函数式就有多个变量,我们一般依次用N,M,L,……来替换表示,最终得到一个多元函数式。如T(N,M) = N + M。

ii.不同的变量,各自遵守②③进行取舍。若有如取舍后的结果为 N^{2}+N*M就要分情况讨论

N远大于M,则把M看作1;

M远大于N,就把N看作1;

如果N、M相差不大,可以把 N看成M 或者 M看成N

再进一步取舍,得到最终答案。

当然,不分情况讨论也是正确的大O表示法结果,但是我们更需要的是一个数量级结果,所以,我们最好如上进行进一步讨论

对数级复杂度,即O(\lg N)、O(\log_{2}N)等等,可以写做O(\log N)省去底数

会出现对数级复杂度的例子:

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);

可得程序结束时,有2^{k+1} >  2^{k} > n,可求得循环次数N = \log_{2}n,即k乘了N次2。T(N)=\log_{2}n,时间复杂度为O(\log N),或者O(\log_{2}N)皆可(因为底数对于对数得最终结果影响很小,所以一般都写成第一种情况)

递归情况

如下代码,求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.栈数组的空间个数为1。

ii.堆数组——或者更宽泛得说——动态申请的空间,如malloc(sizeof(int)*n),这个语句的T(N)=N。

​
int func(int** arr, int n)
{
    *arr = (int*)malloc(sizeof(int) * n);//-------------n
    if(!*arr)
    {
        perror("malloc");
        return 0;
    }
    return 1;   
}

int main()
{
    int* arr = NULL;//----------------1
    scanf("%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博客❤❤

  • 39
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值