数据结构与算法-day1-复杂度分析(2)

个人理解:

  • 最好状况时间复杂度

    最好的那种状况出现时,好比买彩票一次就中算法

  • 最坏状况时间复杂度

    最坏的状况出现时,好比把这一期的彩票全买完才中数组

  • 平均时间复杂度

    最好和最坏按照加权算起来,五五开!!数据结构

  • 均摊时间复杂度

    特殊的平均!!好比我好的少,坏的多,就把好的均摊到坏的上,获得的就是坏的!!函数


// n 表示数组 array 的长度
int find(int[] array, int n, int x) {
  int i = 0;
  int pos = -1;
  for (; i < n; ++i) {
    if (array[i] == x) {
       pos = i;
       break;
    }
  }
  return pos;
}
复制代码

这个时候,问题就来了。这段代码的时间复杂度是 O(n) 吗?很显然,我们上一节讲的分析方法,解决不了这个问题。ui

由于,要查找的变量 x 可能出如今数组的任意位置。spa

  • 最好时间复杂度 :若是数组中第一个元素正好是要查找的变量 x,那就不须要继续遍历剩下的 n-1 个数据了,那时间复杂度就是 O(1)。code

  • 最坏时间复杂度:但若是数组中不存在变量x,那咱们就须要把整个数组都遍历一遍,时间复杂度就成了 O(n)。cdn

  • 平均时间复杂度 下面证blog

咱们知道,要查找的变量x,要么在数组里,要么就不在数组里。这两种状况对应的几率统计起来很麻烦,为了方便你理解,咱们假设在数组中与不在数组中的几率都为 1/2。另外,要查找的数据出如今 0~n-1 这 n 个位置的几率也是同样的,为 1/n。因此,根据几率乘法法则,要查找的数据出如今 0~n-1 中任意位置的几率就是 1/(2n)。it

这个值就是几率论中的加权平均值,也叫做指望值,因此平均时间复杂度的全称应该叫加权平均时间复杂度或者指望时间复杂度。

咱们知道,时间复杂度的大O标记法中,能够省略掉系数、低阶、常量,

引入几率以后,前面那段代码的加权平均值为 (3n+1)/4。用大 O 表示法来表示,去掉系数和常量,这段代码的加权平均时间复杂度仍然是 O(n)


均摊时间复杂度

到此为止,你应该已经掌握了算法复杂度分析的大部份内容了。下面我要给你讲一个更加高级的概念,均摊时间复杂度,以及它对应的分析方法,摊还分析(或者叫平摊分析)。

均摊时间复杂度,听起来跟平均时间复杂度有点儿像。对于初学者来讲,这两个概念确实很是容易弄混。我前面说了,大部分状况下,咱们并不须要区分最好、最坏、平均三种复杂度。平均复杂度只在某些特殊状况下才会用到,而均摊时间复杂度应用的场景比它更加特殊、更加有限

// 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() 的例子和前面那个 find() 的例子,你就会发现这二者有很大差异。

首先 ,find() 函数在极端状况下,复杂度才为 O(1)。但 insert() 在大部分状况下,时间复杂度都为 O(1)。只有个别状况下,复杂度才比较高,为 O(n)。这是 insert()第一个区别于 find() 的地方。

咱们再来看第二个不一样的地方。对于 insert() 函数来讲,O(1) 时间复杂度的插入和 O(n) 时间复杂度的插入,出现的频率是很是有规律的,并且有必定的先后时序关系,通常都是一个 O(n) 插入以后,紧跟着 n-1 个 O(1) 的插入操做,循环往复。

因此,针对这样一种特殊场景的复杂度分析,咱们并不须要像以前讲平均复杂度分析方法那样,找出全部的输入状况及相应的发生几率,而后再计算加权平均值。

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

那究竟如何使用摊还分析法来分析算法的均摊时间复杂度呢?

咱们仍是继续看在数组中插入数据的这个例子。每一次 O(n) 的插入操做,都会跟着 n-1 次 O(1) 的插入操做,因此把耗时多的那次操做均摊到接下来的 n-1 次耗时少的操做上,均摊下来,这一组连续的操做的均摊时间复杂度就是 O(1)。这就是均摊分析的大体思路。

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

相关文章
相关标签/搜索