• 算法的时间复杂度


    ⭐算法效率

    如何衡量一个算法的好坏?
    算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源 。因此衡量一个算法的好坏,一般是从时间空间两个维度来衡量的,即时间复杂度空间复杂度

    ⭐时间复杂度

    一个程序的运行,依赖于其本身的算法设计以及不同的机器性能。单纯地只是从计算其运算时间的角度来评估一个算法是不合理的。
    因此引进了时间复杂度

    🌟时间复杂度

    时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。
    在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度,更关注一个算法的时间复杂度。

    🐟时间复杂度的概念

    一个算法所花费的时间与其中语句的执行次数成正比例。算法中的基本操作的执行次数,为算法的时间复杂度

    🐳例1

    // 请计算一下Func1中++count语句总共执行了多少次?
    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    Func1执行的基本操作次数
    F(N)=N^2+2N+10
    但实际上当N非常大时,最高数量级的N^2对F(N)会起到决定性影响。
    并且在实际中,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们使用大O的渐进表示法

    🐟大O的渐进表示法

    大O符号(Big O notation):是用于描述函数渐进行为的数学符号。

    推导大O阶方法:
    1、用常数1取代运行时间中的所有加法常数
    2、在修改后的运行次数函数中,只保留最高阶项
    3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。

    🐳 例2

    // 计算Func2的时间复杂度?
    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    具体的执行次数F(N)=2N+10
    我们只需要抓大头,for循环这条语句是最主要的。
    因此Func2的时间复杂度为O(N)

    🐳例3

    // 计算Func3的时间复杂度?
    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    Func3函数中,M和N是两个未知数。
    当M>>N时,算法的时间复杂度为:O(M)
    当N>>M时,算法的时间复杂度为: O(N)
    当M=N时,算法的时间复杂度为O(M)或者O(N)
    但由于我们不清楚M和N的具体关系,因此这个算法的时间复杂度为:O(M+N)更准确。

    🐳例4

    // 计算Func4的时间复杂度?
    void Func4(int N)
    {
        int count = 0;
        for (int k = 0; k < 100; ++ k)
        {
            ++count;
        }
        printf("%d\n", count);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    Func4是可以清楚地知道for语句语句会执行100次,因此这个算法的时间复杂度为O(1)

    🐳例5

    // 计算Func5的时间复杂度?
    void Func4(int N)
    {
        int count = 0;
        for (int k = 0; k < 1000000; ++ k)
        {
            ++count;
        }
        printf("%d\n", count);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    如果这里换成1000,000实际上算法的时间复杂度还是O(1)。
    这主要依赖于CPU的强大。CPU的运算速度是十分快的。
    在这里插入图片描述
    这是由于此,对于CPU而言,运行100和1000,1000000 语句,其时间都是一样的。

    🐳例6

    // 计算strchr的时间复杂度?
    const char * strchr ( const char * str, int character );
    
    • 1
    • 2

    这个库函数表示,在字符串s中查找字符c,返回字符c第一次在字符s中出现的位置,如果未找到字符c,则返回NULL。
    这个需要分情况讨论。
    比如说,调用这个函数,并传递参数,字符串"hello,world"。
    最好的情况是,查找的字符,我们传递的是字符"h",那么这样就只进行了1次查找。
    最坏的情况是,查找的字符,我们传递的参数是"d"那么这样就遍历了一次字符数组11次才查找到。或者我们传递的参数是"a",遍历了数组,没有查找到。
    平均情况是,11/2=5次
    在这种情况中我们关注的是算法最坏的情况。

    另外有些算法的时间复杂度存在最好、平均和最坏情况:
    最坏情况:任意输入规模的最大运行次数(上界)
    平均情况:任意输入规模的期望运行次数
    最好情况:任意输入规模的最小运行次数(下界) 例如:在一个长度为N数组中搜索一个数据x
    在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)

    🐳例7

    // 计算BubbleSort的时间复杂度?
    void BubbleSort(int* a, int n)
    {
        assert(a);
        for (size_t end = n; end > 0; --end)
        {
            int exchange = 0;
            for (size_t i = 1; i < end; ++i)
            {
            if (a[i-1] > a[i])
            {
                Swap(&a[i-1], &a[i]);
                exchange = 1;
            }
            }
            if (exchange == 0)
            break;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    这个算法是冒泡排序。
    冒泡排序,是两个相邻元素进行比较,交换;重复这个过程,将最大(重)的沉下去。重复上述,将第二大再沉下去。…
    最好情况是,无需进行交换。
    最坏情况是,假如有n个数。
    第一次交换为n-1次。
    第二次交换为n-2次。
    第三次交换为n-3次。

    第n-1次交换为1次
    所以总次数为
    为(n-1)+(n-2)+(n-3)+…+1=[(n-1)+1]*(n-1)/2=n(n-1)/2次。
    故冒泡算法的时间复杂度为
    O(N^2)

  • 相关阅读:
    2024.3.12
    【面试题 01.08. 零矩阵】
    【Spring Cloud】深入探索 Nacos 注册中心的原理,服务的注册与发现,服务分层模型,负载均衡策略,微服务的权重设置,环境隔离
    Failed to restart **: Unit **.service not found错误,
    QT(37)-mosquitto-MQTT客户端
    Go语言设计与实现 学习笔记 第一章 介绍
    jzoj1382 区间
    【历史上的今天】8 月 9 日:人工智能理论的奠基者诞生;鸿蒙 OS 发布;“云计算”概念被提出
    (二)基于kubernetes(1.25.2) 进行基础prometheus监控
    sql 11
  • 原文地址:https://blog.csdn.net/2302_76305195/article/details/132697032