时间复杂度——大O记

时间复杂度是一个古老的话题,但是大家都知道它设计出来的目的是什么吗?可能心里嘀咕的并不是那么精准吧。有回答可能是衡量算法性能好坏的,这种粗糙的回答,用一句话“什么是好坏?”就能怼到娘胎;进一步答曰衡量算法执行快慢的,算是答到点上了,不过可以专业化一点,衡量执行时间的变化趋势(变化率),当输入规模n趋于大规模时,执行时间的变化趋势。例如O(n^2),O(n^3)在n趋于大规模时(一定能找到一个n0,当n>n0时),O(n^2)的算法在执行时间上快(少)于O(n^3)的算法。

工作上的比较怎么比较,我想拿上相同的比较环境,例如相同的输入规模,相同计算机,单线程等等,跑起来得到运行时间即可见高低优劣。这种教科书上的时间复杂度的记法,无论是大O,Ω,Θ,o,ω这些记号只有在论文、业界规范文章、教科书中出现。多个算法的大O或者其他时间复杂度表示完全可以相同,但是具体精确表示肯定是有优劣之见。

首先明确的是,大O记法的时间复杂度是基于RAM的概念,用于比较对同一问题的不同解决算法之间的时间消耗。如果算法要在多线程或多CPU上并行跑,那么要考虑并行参数的影响。

其次明确的是,算法之间都是有共同的基本运算。如果没有共同的基本运算,你照本宣科地算出来比较毫无意义。

其实我发现大O,小o或者其他就是从数学中无穷(大或小)量的比较引入而来,大O对应着有界量的定义,而小o(或小ω)定义中蕴含着无穷小量或者说无穷大量的阶的比较,其他也只是换汤不换药。具体数学中的定义可以见数学分析相关书籍,书写嫌麻烦,在此略过,你可以理解完数学中的定义之后再来看看算法导论中的定义,可以说换汤换药。其实目的只有一个就是把衡量算法时间的关于输入规模的函数用另一个关于输入规模的函数集来表示,在工作中有时候这样的表示并不适用,真的是多此一举。

搬来算导的一些定义:O(g(n))={f(n):∃c(>0),n0(>0),使得∀n>=n0有0<=f(n)<=cg(n)},可以写作f(n)= O(g(n)),称g(n)的某个常数倍是f(n)的渐进上界。看了英文版也是这个意思,所以免去了对误译的怀疑。

Ω(g(n))={f(n):∃c(>0),n0(>0),使得∀n>=n0有0<= cg(n)<= f(n)},可以写作f(n)= Ω(g(n)),称g(n)的某个常数倍是f(n)的渐进下界。

Θ(g(n))={f(n):∃c1(>0),c2(>0),n0(>0),使得∀n>=n0有0<= c1g(n)<= f(n) <= c2g(n) },可以写作f(n)= Θ(g(n)),称g(n)的某个常数倍是f(n)的渐进确界。

图释如下:

我们来看一个算法复杂度分析,如下插入算法:

void insertion_sort(int arr[], int len){
        int i,j,temp;
        for (i=1;i<len;i++){
                temp = arr[i];
                for (j=i;j>0 && arr[j-1]>temp;j--)
                        arr[j] = arr[j-1];
                arr[j] = temp;
        }
}

我们要铭记,算法的每种输入实例的运行时间f(n)都可以找到一些界来限界它,无论是渐进上界(蕴含在O记中),渐进下界(蕴含在Ω记中)还是渐进确界(蕴含在Θ记中)。为了便于刻化理解蕴含的含义,我们以下就称“渐进(上、下,确)界是(O、Ω、Θ)记号”例如插入排序最坏情况的运行时间f(n)=1+2+3+...+n-2+n-1=n(n-1)/2=n^2/2-n/2的渐进上界为O(n²),分析可知O(n²)也是算法每种输入实例的运行时间的(蕴含着)渐进上界。然而,插入排序最坏情况的运行时间的渐进确界Θ(n²),却并没有说明每种输入实例的运行时间的渐进确界也是Θ(n²),譬如,已排好序的输入实例的运行时间的渐进确界是Θ(n)。我们这里说到了最坏情况的运行时间的渐进确界Θ(n²),但是还是没有论证它到底对不对,即符不符合Θ定义,换个角度论证,根据Θ的定义,我们已知渐进上界为O(n²),如果我们论证了最坏情况的运行时间的渐进下界Ω(n²),那么也就论证了渐进确界Θ(n²),好,下面我们论证渐进下界Ω(n²)。

          找到c和即可,则有不等式,解得,而在n>0的区间里是一个增函数,故,取,c=1/4可使得∀n>=2有,故,最坏情况的运行时间有渐进下界Ω(n²),因有最坏情况的运行时间的渐进确界Θ(n²)。

从数学定义上看,称插入排序的运行时间为O(n²)有点不合适,因为每种输入实例的运行时间的f(n)的数量级(阶)是变化的,所以当我们说“时间复杂度为O(n²)时”,也就是说最坏情况下时间复杂度为O(n²)。

同理自推,最理想情况的运行时间的渐进下界Ω(n)也是算法每种输入实例的运行时间的渐进下界。所以,我觉着最理想情况下的时间复杂度用Ω(n)来表示最为合理。而在实际中上界比下界更有意义。

但是,插入排序的渐进确界就要根据输入实例来说明了。

O,Ω,Θ,o,ω这些都是在输入规模趋于无穷大时用来比较增长快慢的符号,即输入规模很大时这些符号记法是受欢迎的。实际上在输入规模有限定的情况下比较快慢,还是得比较运行时间f(n),例如在一个实际情况下输入规模n<=10000,O(n^2),O(n^3)2个算法如何选取?这就要看各自实际的运行时间f(n)了,如果是100000000 n^2n^3,我想我要选O(n^3)这个算法。

另外,有一些常见的增长快慢的函数:

指数量级:

多项式量级:

对数多项式量级:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值