2.复杂度分析进阶

平均情况复杂度

在数组查询的案例中,最好情况时间复杂度是 O(1),最坏情况时间复杂度是 O(n)。但其实最好与最坏情况是极端情况,更多的时候是不在数组两端的中间某位置。所以为了更准确的表示平均情况下的时间复杂度,引入另一个改变:平均情况时间复杂度

 int cal(int n) {
   int sum = 0;
   int i = 1;
   for (; i <= n; ++i) {
     sum = sum + i;
   }
   return sum;
 }

在数组遍历某元素的过程中,有 n+1 中情况(在数组中的n种情况和不在数组的情况)。该元素在数组中可能存放在任意位置,即1,2,3,…,n。所以:

平均复杂度的公式 = (1+2+3+…+n) / (n+1)

因为 (1+2+3+…+n) = n * (n + 1) / 2,所以平均复杂度公式化简后为:

平均复杂度的公式 = n * (n + 3) / 2 * (n + 1)

这个公式简化之后,得到的平均时间复杂度就是 O(n)

平均时间复杂度

上面的结论虽然是正确,但是计算过程有一个问题,那就是没有考虑到概率问题。
我们刚讲的这 n+1 种情况,出现的概率并不是一样的。首先,要查找变量 x,要么在数组里,要么就不在数组里。在数组中与不在数组中的概率都为 1/2。另外,要查找的数据出现在 0~n-1 这 n 个下标的概率也是一样的,为 1/n。所以,根据概率乘法法则,变量 x 出现在 0~n-1 中任意位置的概率就是 1/(2n)。因此,在前面的推导过程中把每种情况发生的概率也考虑进去,那平均时间复杂度的计算公式就变成了这样:

1*(1/2n) + 2*(1/2n) + 3*(1/2n) + … + n*(1/2n) + n/2 = (3n + 1) / 4

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

均摊时间复杂度

下面讲一个更加高级的概念——均摊时间复杂度。以及它对应的分析方法——摊还分析(或者叫平摊分析)。

/**
 * 代码实现了往数组中不断插入数据。当数组满了,就将所有元素之和赋值给首位。如此周而复始。
 **/
 // array表示一个长度为n的数组
 // 代码中的array.length就等于n
 int[] array = new int[n];
 int count = 0;
 
 // 会有程序不断调用 insert() 方法
 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 的位置就可以了,所以最好情况时间复杂度为 O(1)。最坏的情况下,数组中没有空闲空间了,我们需要先做一次数组的遍历求和,然后再将数据插入,所以最坏情况时间复杂度为 O(n)。那平均时间复杂度是多少呢?
假设数组的长度是 n,根据数据插入的位置的不同,我们可以分为 n 种情况,每种情况的时间复杂度是 O(1)。除此之外,还有一种“额外”的情况,就是在数组没有空闲空间时插入一个数据,这个时候的时间复杂度是 O(n)。而且,这 n+1 种情况发生的概率一样,都是 1/(n+1)。所以,根据加权平均的计算方法,我们求得的平均时间复杂度就是:
去掉系数,结果就是O(1)

插入操作和遍历操作的区别

首先,遍历操作在极端情况下,复杂度才为 O(1)。但插入操作在大部分情况下,时间复杂度都为 O(1)。只有个别情况下,复杂度才比较高,为 O(n)。这是插入操作第一个区别于遍历操作的地方。

第二,对于插入操作来说,O(1) 时间复杂度的插入和 O(n) 时间复杂度的插入,出现的频率是非常有规律的,而且有一定的前后时序关系,一般都是一个 O(n) 插入之后,紧跟着 n-1 个 O(1) 的插入操作,循环往复。

所以,针对这样一种特殊场景的复杂度分析,我们并不需要像之前讲平均复杂度分析方法那样,找出所有的输入情况及相应的发生概率,然后再计算加权平均值。

针对这种特殊的场景,我们引入了一种更加简单的分析方法:摊还分析法。通过摊还分析得到的时间复杂度我们起了一个名字——均摊时间复杂度

那究竟如何使用摊还分析法来分析算法的均摊时间复杂度呢?
我们还是继续看在数组中插入数据的这个例子。每一次 O(n) 的插入操作,都会跟着 n-1 次 O(1) 的插入操作,所以把耗时多的那次操作均摊到接下来的 n-1 次耗时少的操作上,均摊下来,这一组连续的操作的均摊时间复杂度就是 O(1)。这就是均摊分析的大致思路。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tzzt01

您的支持是我坚持下去的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值