《算法与数据结构》学习笔记2---时间复杂度与空间复杂度(下)

前言

上一篇介绍了时间复杂度和空间复杂度的概念,这一篇关注最好情况时间复杂度、最坏情况时间复杂度、平均情况时间复杂度、均摊时间复杂度。

正文

最好情况时间复杂度:在最理想的情况下,执行代码的时间复杂度。
最坏情况时间复杂度:在最糟糕的情况下,执行代码的时间复杂度。
例:

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

    以上面的代码为例。可以看出代码的目的是为了查找变量x在数组中的位置。如果数组中第一个位置正好是要查找的那个变量x,那么就不再需要遍历剩下的n-1个元素了,此时的时间复杂度为O(1),此时则为最好情况时间复杂度。如果数组中不存在查找的变量,那么就需要把整个数组遍历一次,时间复杂度则为O(n),此时为最差情况时间复杂度。所以,不同情况下的这段代码的时间复杂度不一样。
    最好情况时间复杂度和最坏情况时间复杂度对应的都是极端情况下的代码复杂度,发生的概率其实不是很大。平均情况时间复杂度能够更好的表示平均情况下的复杂度。
平均情况时间复杂度
    仍以上述的代码为例。在数组中查找变量x存在两种情况:情况1变量在数组中,情况2变量不在数组中。对于情况2而言,它出现的概率是1/2;对于情况1要查找的变量x在数组中,出现在数组n个位置之一的概率是1/n。根据概率乘法法则,要查找的数据出现在0~n-1中任意位置的概率是1/(2n)。把两种情况综合考虑,平均时间复杂度为:

                 1 ∗ 1 2 n + 2 ∗ 1 2 n + 3 ∗ 1 2 n + . . . + n ∗ 1 2 n + n ∗ 1 2 = 3 n + 1 4 1*\frac{1}{2n}+2*\frac{1}{2n}+3*\frac{1}{2n}+...+n*\frac{1}{2n}+n*\frac{1}{2}=\frac{3n+1}{4}\qquad 12n1+22n1+32n1+...+n2n1+n21=43n+1

    这个值是概率论中的加权平均值,也叫作期望值,所以平均时间复杂度的全称就叫加权平均时间复杂度或期望时间复杂度。所以这段代码的加权平均时间复杂度为(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]
    }
    //当数组满后count==array.length时,用for 循环遍历数组求和,并清空数组,将求和后的值放到第一个位置,然后再将新的数据插入。
    array[0] = sum;
    count = 1
    //再次调用的时候就不会再走该循环了,
    //除非count再次累加到和数组长度相等
  }
  //若数组一开始有空闲空间,则直接将数据插入数组
  array[count] = val;
  ++count;
}

    最理想的情况下,数组中有空闲空间,只需要将数据插入到数组下标为count的位置就可以了,所以最好情况时间复杂度为O(1)。最坏情况时,数组中没有空闲空间,需要先做一次数组遍历求和,然后再把数据插入,所以最坏情况时间复杂度为O(n)。
    假设数组长度为n,根据数据插入的位置不同,可以分为n种情况,每种情况的时间复杂度为O(1)。此外,还是一种情况,若数组没有空闲空间的情况,此时的时间复杂度为O(n)。这n+1种情况发生的概率都为1/(1+n)。所以平均时间复杂度为:

             1 ∗ 1 n + 1 + 1 ∗ 1 n + 1 + 1 ∗ 1 n + 1 + . . . + 1 ∗ 1 n + 1 + n ∗ 1 n + 1 = O ( 1 ) 1*\frac{1}{n+1}+1*\frac{1}{n+1}+1*\frac{1}{n+1}+...+1*\frac{1}{n+1}+n*\frac{1}{n+1}=O(1)\qquad 1n+11+1n+11+1n+11+...+1n+11+nn+11=O(1)

    针对insert()函数,O(1)时间复杂度的插入和O(n)时间复杂度的插入,出现的频率很有规律。一般都是一个O(n)插入之后,紧跟着一个O(1)插入操作,并且insert()函数在大部分情况下的时间复杂度为O(1)。
    所以,针对这种特殊的场景,引入一种更加简单的分析方法:摊还分析法。通过摊分得到的时间复杂度,叫均摊时间复杂度。
    因为每一次O(n)后都会跟着几个O(1),所以把耗时多的操作均摊到接下来的耗时少的操作上,均摊下来,这一组连续的操作的均摊时间复杂度就是O(1)。
    均摊时间复杂度和摊还分析法的应用场景比较特殊,所以并不会经常用到。它的应用场景总结如下:
    对一个数据结构进行一组连续的操作中,大部分情况下时间复杂度都很低,只有个别情况下时间复杂度比较高,而且这些操作之间存在前后连贯的时序关系,这时,我们可以将这一组操作放在一块分析,看是否能将较高时间复杂度那次操作的时间平摊到其他那些时间复杂度低的操作上。而且在能够应用均摊时间复杂度分析 的场合,一般均摊时间复杂度就等于最好情况时间复杂度。均摊时间复杂度可当成一种特殊的平均时间复杂度。
PS:本文主要内容出自极客时间课程数据结构与算法之美课程。

你可能感兴趣的:(数据结构与算法)