查看原文
其他

算法分析神器—时间复杂度

2018-01-14 涛声依旧 趣谈编程

窗外风雪再大

也有我陪伴着你


时间复杂度是学习算法的基石,今天我们来聊聊为什么要引入时间复杂度,什么是时间复杂度以及如何去算一个算法的时间复杂度


刻画算法的运行时间


某日,克叫来了慧子打算给他补习补习一下基础知识,只见克写了一段非常简单的代码

你说一下这段代码会运行多长时间

这个...,得在计算机上跑一下才可以知道吧

慧子

恩恩,对的,那如果我改变n的大小为10000,你能够预测它的运行时间吗?

这个...,要不再测一次

慧子

克面无表情

我们要善于发现事物的内在规律

克看了一下慧子

这个程序的内在规律是什么呢?

慧子

慧子看老师有点生气,开始虚心请教了

你要预测当n=10000的时候,这个程序会运行多长时间,你首先要知道程序的运行时间都花在哪了?

当电脑运行这段代码的时候,执行任何一条语句都需要花费时间,这是时间花费的主要地方

为了方便讨论,这里我们把每一条语句的执行时间都看做是一样的,记为一个时间单元

你看一下刚才的程序都有哪些语句

你看,这个程序有这么几个地方消耗了时间

① 蓝色框的两条语句,花费两个时间单元

 ②黑色框的一条语句,花费n+1个时间单元

③红色框的两条语句,花费2*n个时间单元

那么一共花费了3n+3个时间单元,可以看出,程序消耗的时间和你的n成线性关系

这不是数学吗,慧子心里想到

现在我用T(n)表示这个程序运行了多长时间,那么这个程序运行的时间就可以写成T(n)=3n+3

其中的n被我们称为问题的规模,其实就是你处理问题的大小

克顺手画了这个函数的图

本文主要讨论问题规模和运行时间的关系,假定不同输入和运行时间基本无关

哦,我懂了,要预测程序的时间,我可以把运行时间函数求出来

慧子

恩恩,对的

这个函数表达式看起来挺复杂的

慧子

我们常常会对这个函数进行简化,使得它既简单又不失函数的主要特性

哦?怎么简化

慧子


时间复杂度


我们一般只关心随着问题规模n趋于无穷时函数中对函数结果影响最大的项,也就是最高次项

比如说:T(n)=3n+3,当n非常大的时候常数3和n的系数3对函数结果的影响就很小了    

所以一般我们会保留最高次项并忽略该项的系数

   比如:                                                          T(n)=n+1 忽略常数项 T(n)~n                   T(n)=n+n^2 忽略低阶项 T(n)~n^2         

T(n)=3n 忽略最高阶的系数 T(n)~n       

这个忽略低阶项是什么意思

慧子

所谓低阶项,简单地说就是当n非常大时,这个项相对于另外一个项很小,可以忽略,比如n相对于n^2,n就是低阶项

哦,我怎么判断哪个是高阶,哪个是低阶呢?

慧子

具体要用数学知识,对你而言,你只需记住下面的大小关系就行了,到时按照这个进行忽略(相对较小的忽略)

还好不用掌握那头疼的数学,慧子心中想到

简化完了之后呢?

慧子

慧子把话题又拉了回来

化简完后的函数可以近似地代表原来函数的总体趋势

简化后的式子被称为这个程序算法的时间复杂度,记做O(f(n)),f(n)就是简化后的式子,比如说刚开始讨论的T(n)=3n+3,简化后T(n)~f(n)=n,那我们记为O(n)

更准确地说O代表了运行时间函数的一个渐进上界,即T(n)在数量级上小于等于f(n)

哦,原来时间复杂度可以表示某个算法的运行时间的趋势,大致地度量算法效率的好坏, 那我该如何算这个时间复杂度呢?

慧子


时间复杂度的计算


从前面可以总结出计算算法复杂度大O的方法

一、得出运行时间的函数                              二、对函数进行简化                                    

①用常数1来取代运行时间中所有加法常数  

②修改后的函数中,只保留最高阶项            ③如果最高阶项存在且不是1,则忽略这个项的系数                                                 

举个例子吧,先来看这样一段代码

显然,T(n)=3,对这个函数进行简化,用常数1取代常数3,然后取代后的函数没有最高阶项,那么这个算法的时间复杂度就是O(1).

O(1)也被称为常数阶

每次都要把时间函数算出来,好复杂,有没有简便一点的方法

慧子

这个还真可以耍耍小聪明,一般来说,最内层执行次数最多的语句就决定了整个算法的趋势

你只要看看最内层的语句执行次数的规律就行了,这个内层打印语句随着问题规模n的增加会呈线性增加,直接就可以判定复杂度为O(n)

这个方法真棒,那么按照这个方法就很容易得出下面这个嵌套的两层for循环的复杂度为O(n^2)了

慧子

慧子随手写了一段嵌套循环的代码

恩恩,对的,最内层的语句随n的增加会呈二次函数的规律执行,代表了这个算法执行时间的一个趋势

我听说有一个很神奇的函数叫对数函数,它随着自变量的增大,因变量增长的很慢,这个应该很受欢迎吧

慧子

嗯嗯,对的,对数函数的趋势显然比线性函数好(对数函数随着自变量的增大因变量增长的很慢)

接着,克又写了一段时间复杂度为对数的代码

你看,这段代码的复杂度就为对数级别的,O(logn)

这个怎么分析

慧子

一向数学不太好的谦子此时有点懵

和之前的分析方法一样,我们着重看执行次数最多的内层代码语句

每循环一次,sum就给自身乘以2,乘了多少次就跳出循环了呢(大于等于n)?不知道,就设为x吧,那么2^x=n,解出x=logn,这说明随着n的增大,最消耗时间的内层语句是呈指数变化的

哦,我懂了,原来如此

慧子

复杂度的内容很多,你只要理解它的含义以及会分析简单的算法就行了,以后遇到复杂的,为师会给你传授的

好的

慧子

慧子与老师道了别

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存