复杂度分析(下)

Posted tczw

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了复杂度分析(下)相关的知识,希望对你有一定的参考价值。

复杂度分析(下)

继续上篇,这篇将介绍四个复杂度分析方面的知识点:最好情况时间复杂度、最坏情况时间复杂度、平均情况时间复杂度、均摊时间复杂度。

 

1.最好、最坏情况时间复杂度


 

我们以数组查找举例,遍历数组找指定元素,找到则立即返回该元素所在数组的下标位置,没找到则返回-1。代码比较简单我就不写出来了。

我们来分析一下,

最好情况时间复杂度:最好情况当然是数组的第一个元素就是查找的元素,为O(1)

最坏情况时间复杂度:最坏情况就是数组的最后一个元素为查找的元素,需要遍历完整个数组才能找到该元素,或者数组中不存在该元素,那么也需要遍历完数组,为O(n)

总结:

最好情况时间复杂度,就是在最理想的情况下,执行这段代码的时间复杂度。

最坏情况时间复杂度,就是在最糟糕的情况下,执行这段代码的时间复杂度。

 

2.平均情况时间复杂度


 

最好和最坏情况时间复杂度都是对应极端情况下的代码复杂度,发生的概率并不大。为了更好地表示平均情况下的复杂度,我们需要引入另一个概念:平均情况时间复杂度。

还是刚刚那个例子,看看怎么分析平均情况时间复杂度。

要查找的变量x在数组中的位置,有n+1种情况:在数组的0~n-1的位置上,和不在数组中。另外我们假设x在或不在数组中的概率各为1/2,且x出现在数组各个位置的概率也是一样的,为1/n。

根据概率乘法法则,要查找的元素出现在0~n-1的任意位置的概率就是1/2n。

那平均时间复杂度的计算过程如下:

技术图片

 

这个值就是概率论中的加权平均值,也叫作期望值,所以平均时间复杂度的全称应该叫加权平均时间复杂度或者期望时间复杂度

用大O表示法来表示,去掉系数和常量,这段代码的加权平均时间复杂度是O(n)。

 

3.均摊时间复杂度


 

均摊时间复杂度只在某些特殊情况下才会使用。

 // array表示一个长度为n的数组
 // 代码中的array.length就等于n
 int[] array = new int[n];
 int count = 0;
 
 void insert(int val) {
    if (count == array.length) {
       int sum = 0;
       for (int i = 0; i < array.length; ++i) {
          sum = sum + array[i];
       }
       array[0] = sum;
       count = 1;
    }

    array[count] = val;
    ++count;
 }

这段代码实现了一个往数组中插入数据的功能。当数组满了之后,也就是代码中的 count == array.length 时,我们用 for 循环遍历数组求和,并清空数组,将求和之后的 sum 值放到数组的第一个位置,然后再将新的数据插入。但如果数组一开始就有空闲空间,则直接将数据插入数组。

我们先用上面介绍了三种方法分析一下:

最理想的情况下,数组中有空闲空间,我们只需要将数据插入到数组下标为 count 的位置就可以了,所以最好情况时间复杂度为 O(1)。

最坏的情况下,数组中没有空闲空间了,我们需要先做一次数组的遍历求和,然后再将数据插入,所以最坏情况时间复杂度为 O(n)。

那平均时间复杂度是多少呢?答案是 O(1)。我们还是可以通过前面讲的概率论的方法来分析。假设数组的长度是 n,根据数据插入的位置的不同,我们可以分为 n 种情况,每种情况的时间复杂度是 O(1)。除此之外,还有一种“额外”的情况,就是在数组没有空闲空间时插入一个数据,这个时候的时间复杂度是 O(n)。而且,这 n+1 种情况发生的概率一样,都是 1/(n+1)。所以,根据加权平均的计算方法,我们求得的平均时间复杂度就是:技术图片

 但其实这里平均复杂度没必要这么复杂。我们来分析一下代码。

insert()方法在大部分情况下,时间复杂度都为O(1),只有在个别情况下,复杂度才有O(n)。且O(1)和O(n)的出现是有规律的,一个O(n)插入之后,紧跟着n-1个O(1)插入,循环往复。

所以,针对这种特殊场景的复杂度分析,我们并不需要向之前那样通过求概率的方法计算加权平均值。

引入一种更加简单的分析方法叫做摊还分析法,通过摊还分析得到的时间复杂度叫做均摊时间复杂度。

每一次 O(n) 的插入操作,都会跟着 n-1 次 O(1) 的插入操作,所以把耗时多的那次操作均摊到接下来的 n-1 次耗时少的操作上,均摊下来,这一组连续的操作的均摊时间复杂度就是 O(1)。这就是均摊分析的大致思路。

对一个数据结构进行一组连续操作中,大部分情况下时间复杂度都很低,只有个别情况下时间复杂度比较高,而且这些操作之间存在前后连贯的时序关系,这个时候,我们就可以将这一组操作放在一块儿分析,看是否能将较高时间复杂度那次操作的耗时,平摊到其他那些时间复杂度比较低的操作上。而且,在能够应用均摊时间复杂度分析的场合,一般均摊时间复杂度就等于最好情况时间复杂度。

 均摊时间复杂度就是一种特殊的平均时间复杂度

 总结


这篇文章接着上一篇介绍了几个新的复杂度分析相关的概念,分别有:最好情况时间复杂度,最坏情况时间复杂度,平均情况时间复杂度,均摊时间复杂度。之所以引入这几个复杂度概念,是因为同一段代码,在不同输入的情况下,复杂度的量级有可能是不一样的。

以上是关于复杂度分析(下)的主要内容,如果未能解决你的问题,请参考以下文章

以下代码片段的时间复杂度是多少?

代码片段使用复杂的 JavaScript 在 UIWebView 中插入 HTML?

CPNtools协议建模安全分析---实例变迁标记

基于时间复杂度的这些片段真的很困惑

复杂度分析(下)

如何使用 Firebase 分析跟踪 android 片段