什么是算法时间复杂度?

作者: 我犟不过你 | 来源:发表于2021-10-11 15:49 被阅读0次

    一、时间复杂度的定义

    在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级。

    算法的时间复杂度,也就是算法的时间量度,记作:T(n)=O(f(n))。它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度,简称为时间复杂度。其中f(n)是问题规模n的某个函数。这样用大写O( )来体现算法时间复杂度的记法,我们称之为大O记法。

    一般情况下,随着n的增大,T(n)增长最慢的算法为最优算法。

    显然,由此算法时间复杂度的定义可知,我们分别给常见的时间复杂度取了非官方的名称,O(1)叫常数阶O(n)叫线性阶O(n²)叫平方阶O(logn)对数阶,当然,还有其他的一些阶,我们之后会介绍。

    二、大O阶推导

    我们可以通过下面的方法推导出算法的时间复杂度,称之为推导大O阶

    1)用常数1取代运行时间中的所有加法常数。
    2)在修改后的运行次数函数中,只保留最高阶。
    3)如果最高阶项存在且不是1,则去除与这个项相乘的常数。

    以上得到的结果就是大O阶

    2.1 常数阶

    int sum = 0,n = 100; /* 执行一次 */
    sum = (1 + n) * n / 2; /* 执行一次 */
    printf("%d", sum); /* 执行一次 */
    

    假设有如上的一个算法,没学过算法的同学可能会认为该算法的时间复杂度是O(3),但实际上是O(1)。根据我们的上面的大O阶推导,第一步,用1取代3,就成了1,第二步,该算法根本没有最高阶,所以算法时间复杂度就是O(1)。

    假设有如下的算法,其中有十句 sum = (1 + n) * n / 2; /* 执行一次 */

    int sum = 0,n = 100; /* 执行一次 */
    sum = (1 + n) * n / 2; /* 执行一次 */
    sum = (1 + n) * n / 2; /* 执行一次 */
    sum = (1 + n) * n / 2; /* 执行一次 */
    sum = (1 + n) * n / 2; /* 执行一次 */
    sum = (1 + n) * n / 2; /* 执行一次 */
    sum = (1 + n) * n / 2; /* 执行一次 */
    sum = (1 + n) * n / 2; /* 执行一次 */
    sum = (1 + n) * n / 2; /* 执行一次 */
    sum = (1 + n) * n / 2; /* 执行一次 */
    sum = (1 + n) * n / 2; /* 执行一次 */
    printf("%d", sum); /* 执行一次 */
    

    在上面的算法中,代码执行了12行,实际与最初的3行没有差别,因为与问题的本质:n的大小无关,所以最终该算法的时间复杂度仍然是O(1)。

    关于上面的两种我们称之为具有O(1)的时间复杂度,又称为常数阶

    2.2 线性阶

    int i;
    for (i = 0; i < n; i++)
    {
    /* 时间复杂度为O(1)的程序步骤序列 */
    }
    

    如上面的代码所示,在循环内部的代码,其复杂度是O(1),重点在于n的大小,它决定循环的次数,则整个算法的时间复杂度是O(n)。

    2.3 对数阶

    int count = 1;
    while (count < n)
    {
    count = count * 2;
    /* 时间复杂度为O(1)的程序步骤序列 */
    }
    

    如上代码所示,count的增长速度是每次乘以2,假设count = 10 ,n=100,则有以下等式:

    10 * 2 = 20
    20 * 2 = 40
    40 * 2 = 80
    

    最终就是10 * 2的三次方 = 80。所以我们得到结论 count * 2的x次方 = n,根据大O阶推导,将count用常数1替换,则x = log2n。所以这个循环的时间复杂度是O(logn)。

    2.4 平方阶

    int i, j;
    for (i = 0; i < n; i++)
    {
    for (j = 0; j < n; j++)
    {
    /* 时间复杂度为O(1)的程序步骤序列 */
    }
    }
    

    有如上的嵌套循环,其内存循环是一个线性阶,则其时间复杂度是O(n),外部循环的时间复杂度同样是O(n),相当于内存的O(n)循环了n次,则此代码的时间复杂度就是O(n²)。

    如果有如下的算法:

    int i, j;
    for (i = 0; i < m; i++)
    {
    for (j = 0; j < n; j++)
    {
    /* 时间复杂度为O(1)的程序步骤序列 */
    }
    }
    

    上面的时间复杂度就是O(mn)。

    下面看一种复杂的情况如何推导其时间复杂度:

    int i, j;
    for (i = 0; i < n; i++)
    {
    /* 注意j = i 而不是0 */
    for (j = i; j < n; j++)
    {
    /* 时间复杂度为O(1)的程序步骤序列 */
    }
    }
    

    当i = 0 时,内部执行了n次;当i = 1时,内部执行了n-1,以此类推:

    n + (n-1) + (n-2) + .... .... + 1
    

    对以上公式进行转化,首项加尾项是n+1,总共有n个n+1,则等到以下公式:

    (n(n+1))/2 = n²/2 + n/2
    

    下面根据大O阶推导:
    1)没有加法常数,所以不替换
    2)保留最高项,即n²/2
    3)去除常数,就是去掉1/2,则等到最终的结果是n²。

    如上可以得到一些结论,其实其推导过程不难,难在如何将数列进行转化,如上的等差数列等。

    2.5 其他的时间复杂度

    除了前面介绍的之外,还有一些其他的时间复杂度。

    立方阶:O(n³)
    指数阶:2ⁿ
    n成对数阶:nlogn
    阶乘阶:n!
    n的n次方:nⁿ

    这些所有的时间复杂度耗时大小如下所示:
    O(1) < O(logn) < O(n) < O(nlogn) < O(n²) < O(n³) < O(2ⁿ) < O(n!) < O(nⁿ)

    三、算法空间复杂度

    空间换时间的方式。

    算法空间复杂度的计算公式记作:S(n)=O(f(n)),其中,n为问题的规模,f(n)为语句关于n所占存储空间的函数。

    我们都使用“时间复杂度”来指运行时间的需求,使用“空间复杂度”指空间需求。当不用限定词地使用“复杂度”时,通常都是指时间复杂度。

    本文重点是时间复杂度,关于空间复杂度就不多做介绍了。

    相关文章

      网友评论

        本文标题:什么是算法时间复杂度?

        本文链接:https://www.haomeiwen.com/subject/lxvloltx.html