时间复杂度计算方法

在学习数据结构阶段,认识的第一个概念应该就是时间复杂度了。时间复杂度是一个数学上的一个函数式,该函数式计算的基本执行次数,就是时间复杂度。通俗的说,时间复杂度就是衡量这个算法的运行时间长短,效率高低的一个手段,它是通过粗略的计算算法的执行次数来衡量的。那时间复杂度该怎么计算呢?

一、计算算法的时间复杂度:

下面先看这段代码:

//判断算法的时间复杂度
#include<stdio.h>

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

int main()
{
    int N = 0;
    scanf("%d", &N);
    Func1(N);
    return 0;
}

上面的代码中,Func1函数效率的高低,就能衡量这个函数的好坏。观察出该函数有四个循环,前两个循环是嵌套的形式,每个循环的循环次数是N次,所以一共是N×N次。第三个循环的循环次数是2×N次,第四个循环的循环次数是10次,所以该函数一共执行了N×N+2×N+10次,写成数学表达式就是Func1(N) = N² + 2×N + 10。

带入具体值是:

N值

准确值(Func1(N) = N² + 2 × N + 10)

估算值(Func1(N) = N²)

N = 1

Func1(N) = 13

Func1(N) = 1

N = 10

Func1(N) = 130

Func1(N) = 100

N = 100

Func1(N) = 10210

Func1(N) = 10000

N = 1000

Func1(N) = 1002010

Func1(N) = 1000000

N = 10000

Func1(N) = 100020010

Func1(N) = 100000000

N = 100000

Func1(N) = 10000200010

Func1(N) = 10000000000

N值越大,估算值与准确值之间相差的就越少,也就是说估算值就越接近准确值,那么就可以用估算值代替准确值,得到的执行次数就是算法Func1的大致执行次数。

实际上这样做有些过于细节了,时间复杂度求的是一个算法的大致运行次数,所以并不是精确到到底是多少次,那么时间复杂度就会存在着等级,比如O(1)、O(N)、O(2×N)、O(N²)、O(log₂N)、O(N!)、O(Nlog₂N)等,N就代表该算法大致执行了多少次,1代表该算法执行的是常数次,也就是确定的次数,这种表示的方法是大O渐进表示法

上面的Func1函数的时间复杂度是O(N²),也就是大致执行了N²次,比如当N = 10时,Func1(N) = 100,这显然是一个大致的数字,之所以舍弃后面的次数,是因为当N无限大时,能决定具体的执行次数的是N²,不是2×N+10,所以会舍弃后面的执行次数,这点在当N无限大时就很明显。

如果算法执行次数是确定的呢,那时间复杂度又是多少?其实,不管这个确定的执行次数有多大,时间复杂度都是O(1),“1”也代表常数次,比如下面的代码:

//判断算法的时间复杂度
#include<stdio.h>

void Func3(int N)
{
    int count = 0;
    for (int k = 0; k < 100; ++k)
    {
        ++count;
    }
    printf("%d\n", count);
}

int main()
{
    int N = 0;
    scanf("%d", &N);
    Func3(N);
    return 0;
}

代码就是循环100次,所以时间复杂度是O(1)。

只要是O(1)就说明该算法执行次数是常数次,不管确定的常数值多大或多小都是O(1)。这是因为CPU的处理速度已经非常快了,所以即使几十亿次的执行,也不过是几秒钟而已,当然如果一个O(1)的算法需要执行几百万亿甚至几千万亿次,这也需要很长时间的,即使这种算法的时间复杂度是O(1),实际上也是不太可能存在的。

时间复杂度大小关系为:O(log₂N) < O(N) < O(Nlog₂N) < O(N²) < O(2^N) < O(N!)。

二、时间复杂度的量级:

从上面的分析就可以看出,时间复杂度是分等级的,最快的也是最优的是O(1)、O(log₂N),其次是O(N)、O(Nlog₂N),这些时间复杂度都是比较不错的。然后是O(N²),这种时间复杂度的算法效率就很低了,在实际生活中一般是不用的,最后是O(N!),这种时间复杂度的算法也就是看看而已,很少很少存在,因为它的效率实在是太低了。

只是这么说可能不太直观的感受,下面使用具体数值举例,时间复杂度每个量级之间的差距:

O(log₂N)O(N)O(Nlog₂N)O(N²)O(2^N)O(N!)
111111
710070010,0002^100100!
1410,000140,000100,000,000没有计算必要了没有计算必要了
17100,0001,700,000

10,000,000,000

没有计算必要了没有计算必要了
201,000,00020,000,000没有计算必要了没有计算必要了没有计算必要了
2410,000,000240,000,000没有计算必要了没有计算必要了没有计算必要了
27100,000,0002,700,000,000没有计算必要了没有计算必要了没有计算必要了
301,000,000,00030,000,000,000没有计算必要了没有计算必要了没有计算必要了

这下就可以看出效率了,时间复杂度为O(N)的算法还算可以,之后的算法就比较拉胯了,到了O(N²)的算法基本没人使用了。

三、求时间复杂度时的预期管理:

有如下程序:

//判断算法的时间复杂度
#include<stdio.h>

const char* strchr(const char* str, int character);

int main() {}

上面的函数没有具体的实现,也就不知道是执行常数次还是N次,也就是说,最好的情况是执行1次(常数次),最坏的情况是执行N次,平均的情况(最常见的情况)是执行次数在区间[2,N-1]之之间,那就不知道这个算法到底执行多少次,这时可以采用预期管理,按照最坏的情况考虑。

遇到不确定的执行次数时,均按照最坏情况考虑。

所以上面算法的时间复杂度是O(N)。

四、求算法的时间复杂度的方法:

1、最简单的情况,直接数循环次数即可,根据循环次数判断是在哪个量级。

2、大部分的算法都需要特殊考虑,到底执行次数是在哪个量级,比如下面的代码:

//判断算法的时间复杂度
#include<stdio.h>

long long Fib(size_t N)
{
    if (N < 3)
        return 1;
    return Fib(N - 1) + Fib(N - 2);
}

int main()
{
    size_t N = 0;
    scanf("%d", &N);
    Fib(N);
    return 0;
}

这个算法看起来很简单,功能是求第N个斐波那契数,但是想要计算出时间复杂度还是比较复杂的。首先算法中含有函数递归,每一次函数递归都会开辟一个函数栈帧,这也算执行了一次,那问题就转换成这个算法到底递归了多少次即可。

函数递归过程如下:

Fib(N)函数递归过程:
                                                             Fib(N)                                                                  2º
                            Fib(N-1)                                              Fib(N-2)                                     2¹
           Fib(N-2)                Fib(N-3)                   Fib(N-3)                  Fib(N-4)                     2²
Fib(N-3)  Fib(N-4)    Fib(N-4)  Fib(N-5)    Fib(N-4)  Fib(N-5)    Fib(N-5)  Fib(N-6)            2³
                                                                 ...                                                                      ...
                                                               Fib(5)
                               Fib(4)                                                       Fib(3)
                 Fib(3)                return = 1                     return = 1           return = 1 
return = 1       return = 1

从上面的递归过程发现,前期的调用大概是按照2ⁿ的形式调用的,后面当N<3时,就会返回1。所以如果是按照量级看来,大致计算执行次数应该是2º + 2¹ + 2² + 2³ + ... + 2^(n - 1) = 2ⁿ - 1。所以时间复杂度就是O(2ⁿ)。

错位相减计算法:
S(n) = 2º + 2¹ + 2² + 2³ + ... + 2^(n - 1)
2×S(n) = 2¹ + 2² + 2³ + ... + 2ⁿ
可知S(n) = 2ⁿ - 2º = 2ⁿ - 1。

还有一种求法,就是画出前几个递归图后,就大概判断出执行次数差不多是2ⁿ,少算的那部分根本不用管,这样就推测出时间复杂度是O(2ⁿ)了,这个也是求算法的时间复杂度中比较难的一个了。

以上就是我这次分享的内容啦!写的不好,请多担待!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值