深入理解算法的时间效率衡量标准–时间复杂度

一.什么是时间复杂度

        很多同学在程序开发和算法调优的过程中,经常会接触到时间复杂度的概念,那究竟什么是时间复杂度呢?

        在回答这个问题之前,我们先举一个例子,我们把编写一个程序的过程类比成指挥一场战役,程序开发人员就扮演着指挥者的角色,编写的代码就是被指挥的战士,那么算法就是指挥战役的”兵法”。

       在实际开发过程中,为了满足业务需求,实现业务目的的各种方法和思路就是算法,而时间复杂度是衡量算法在处理输入数据时所需的时间量级的参数。它是用来描述算法执行时间效率的指标,是衡量”兵法”好坏的重要指标。

        如果我们的业务目的是获取5个4的和,那么有两种实现思路:

                算法1:  4+4+4+4+4=20
                算法2:  4*5=20

        同样的数据,同样的目的,不同的算法,不同的方法和思路,效率就会不同,而在实际开发过程中,实现目的时所需时间越短的算法相对而言越有优势,而时间的度量标准就是时间复杂度。

二.算法的时间效率衡量

        在解释时间复杂度如何计算之前,我们来看一下算法的时间效率衡量标准,现有需求:

        如果 a+b+c=1000,且 a^2+b^2=c^2(a,b,c 为自然数),如何求出所有a、b、c可能的组合?

        解题算法一:穷举法

                将问题的所有可能的答案一一列举,然后根据条件判断此答案是否合适,合适就保留,不合适就丢弃       

#列举a,b,c的所有可能的数值
for a in range(0,1001):                   
    for b in range(0,1001):                
        for c in range(0,1001):            
#判断是否满足条件
            if a**2+b**2+c**2 and a+b+c==1000:
                print('a,b,c:%d,%d,%d' % (a,b,c))

        解题算法二:

                知道a,b,c他们三者是有一个关系的,就可以不用对c进行遍历,直接把c列成一个条件即可:

#注意这里是两层循环
for a in range(0,1001):
    for b in range(0,1001):
        c=1000-a-b
        if a**2 +b**2==c**2:
            print('a,b,c:%d,%d,%d' %(a,b,c))

        对于同一问题,我们给出了两种解决算法,在两种算法的实现中,我们对程序执行的时间
进行了测算,发现两段程序执行的时间相差悬殊(方法一3.5分钟 相比于 方法二2秒),
由此我们可以得出结论:实现算法程序的执行时间可以反应出算法的效率,即算法的优劣。
        但是如果方法一使用天河二号超级计算机来运行,方法二使用十年前的老旧计算机来运行,结果可能会有出入,因此单纯的依靠时间值来比较算法优劣并不一定是客观准确的!

        那么如何才能客观的评价一个算法的优劣呢?

        我们假定计算机执行算法每一个基本操作的时间是固定的一个时间单位,那么有多少个基本操作就代表会花费多少时间单位,由此可以忽略机器环境的影响,进而客观的反映算法的时间效率。
        时间效率:

        代码执行总时间(T)=操作步骤数量*操作步骤执行时间

        T=(大整体*子整体*基本操作)*操作步骤执行时间

        我们假定计算机执行算法每一个基本操作的时间是固定的一个时间单位

        T=操作步骤总数(大整体*子整体*基本操作)

三.时间复杂度的计算

        算法一的循环次数:

for a in range(0,1001):                    #1001次
    for b in range(0,1001):                #1001次
        for c in range(0,1001):            #1001次
            if a**2+b**2+c**2 and a+b+c==1000:
                print('a,b,c:%d,%d,%d' % (a,b,c))

        算法一每次循环的步骤:

        1000==a+b+c 为 :3次(两次相加,一次判断)

        a**2+b**2=c**2 为 :5次(三次平方,一次相加,一次判断)

        print(‘a,b,c:%d,%d,%d’ % (a,b,c)) :2次(一次字符串格式化,一次打印)

        每个循环的步骤次数为:3+5+2=10次

        代码步骤总数量:1001*1001*1001*10 次

        假定计算机执行算法每一个基本操作的时间是固定的一个时间单位
        则T = 1001*1001*1001*10

        但是算法是一种独立的存在,它是解决问题的方法和思想,算法一的优劣是不能简单的用T = 1001*1001*1001*10衡量的,需求中要求a+b+c=1000 , 这个 1000 是这个题目的计算范围 , 我们称之为问题规模

        当问题规模为1000的时候
        T = 1001 * 1001 * 1001 * 10 = 10013 * 10
        当问题规模为2000的时候
        T = 20013 * 10
        当问题规模为3000的时候
        T = 30013 * 10
        当问题规模为n的时候
        T = n3 * 10
        我们就将上面的算数,总结为一个表达式:T(n) = n3 * 10 这个表达式称为:时间复杂度
        即:时间复杂度T是关于n的函数

四.时间复杂度的计算规则

时间复杂度的计算规则为:

        1.基本操作:时间复杂度为O(1)

        2.顺序结构:时间复杂度按加法进行计算

        3.循环结构:时间复杂度按乘法进行计算

        4.分支结构:时间复杂度取最大值

        5.判断一个算法的效率时,往往只需要关注操作数量的最高次项,其它次要项和常数项可以忽略

        6.在没有特殊说明时,我们所分析的算法的时间复杂度都是指最坏时间复杂度

基本操作:

        例1:sum=100+200        时间复杂度为O(1)

        例2:sum=200^2 + 1000^2        这里出现了200^2, 那么它的时间复杂度为 O(n2) 吗?答案为否,因为这里执行次数是恒定的, 不会随着问题规模的变化有任何改变, 复杂度是O(1),与问题规模的大小无关, 执行次数恒定的算法, 我们称之为具有O(1)的时间复杂度

顺序结构:

        例3:

        第一步: a = 100
        第二步: b = 200
        第三步: c = a + b
        顺序结构,时间复杂度按加法进行计算
        第一步时间复杂度:O(1)
        第二步时间复杂度:O(1)
        第三步时间复杂度:O(1)

         因为是顺序结构,所以例3的时间复杂度为O(1)+O(1)+O(1)=3O(1),由于计算时间复杂度时要去掉前面的系数,所以例3的时间复杂度为O(1)     

循环结构:

        例4:

        for i in range(0,n):  print(i)

        这里执行次数随着n的变化而变化,所以时间复杂度是O(n)

        例5:

for j in range(0,2):
    for i in range(0,n):
        print(i)

        例子中执行次数是2n次,所以时间复杂度是O(2n)吗?答案为否,因为我们在判断一个算法的效率时,只关注操作数量的最高次项,其他次要项和常数项都可以忽略,所以例5中的时间复杂度为:O(n)

分支结构:

        例5:

        if 分支的时间复杂度为O(n)

        else 分支的时间复杂度为O(n^2)

        由于时间复杂度取最大值,所以上述代码的时间复杂度为O(n^2)

        例6:

        第一步: 时间复杂度:O(n)
        第二步: 时间复杂度:O(n)
        第三步: 时间复杂度:O(n2)
        第四步: 时间复杂度:O(n3)

        顺序结构相加:

        例6的时间复杂度为:T(n) = n + n + n^2 + n^3 = 2n + n^2 + n^3 = O(n^3)

所以计算时间复杂度的方法为:

        ①基本操作
        时间复杂度为O(1)
        ②顺序结构
        时间复杂度按加法进行计算
        ③循环结构
        时间复杂度按乘法进行计算

        ④分支结构
        时间复杂度取最大值
        ⑤判断一个算法的效率时,往往只需要关注操作数量的最高次项,其它次要项和常数项可以忽略
        ⑥在没有特殊说明时,我们所分析的算法的时间复杂度都是指最坏时间复杂度

五.最优最坏时间复杂度

分析算法时,存在几种可能的考虑:
算法完成工作最少需要多少基本操作,即最优时间复杂度
算法完成工作最多需要多少基本操作,即最坏时间复杂度
算法完成工作平均需要多少基本操作,即平均时间复杂度

最优最坏时间复杂度:

假如我们有一个列表 , 我们要通过一个算法从这个列表中找到10
最坏时间复杂度
my_list = [ 1 , 5 , 6 , 4 , 3 , 2 , 7 , 8 , 9 , 10 ]
最优时间复杂度
my_list = [ 10 , 5 , 6 , 4 , 3 , 2 , 7 , 8 , 9 , 1 ]

最优时间复杂度: 反映的只是最乐观最理想的情况,没有参考价值。
最坏时间复杂度: 算法的一种保证,表明算法在此种程度的基本操作中一定能完成工作

因此,我们主要关注算法的最坏情况,亦即最坏时间复杂度

六.常见时间复杂度

常见的时间复杂度有:

执行次数函数举例非正式术语
42O(1)   常数阶
2n+3O(n)线性阶
3n^2+2n+1O(n^2)   平方阶
5log{​{_2}}n+20O(logn)对数阶
6n^3+2n^2+3n+4O(n^3)立方阶

需要注意的是,O(logn)最典型的例子是二分查找算法,

这是因为二分查找是一种基于分治思想的算法,它将待查找的有序数组逐步分成两半,然后判断目标值位于数组的哪一部分,并继续在相应的部分中进行查找。通过每次查找都将问题规模缩小一半,它的时间复杂度可以表达为对数函数。

具体而言,执行每一轮二分查找操作时,都会将问题规模减半,因此可以将待查找的元素个数n表示为2的幂,即n=2^k。而每次查找操作都将问题规模减半,所以经过k次查找最终可以找到目标元素。

k 表示二分查找的迭代次数,可以通过求解2^k=n得到。这意味着 k = log2(n)。因此,二分查找的时间复杂度为 O(log n)。

需要注意的是,在二分查找算法的应用场景中,通常假设输入是一个有序数组。如果输入不是有序的,那么首先需要先对数组排序,这会增加额外的时间复杂度。

下一个博客,我们来聊一聊目前主流的算法有哪些。

        

        

        

        

        

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
社会演员多的头像社会演员多普通用户
上一篇 2023年12月19日
下一篇 2023年12月19日

相关推荐