《数据结构与算法》三:最好、最坏、平均、均摊复杂度

585 阅读2分钟

本系列文章是以笔记的方式进行记录,主要内容均来源于王争老师的《数据结构与算法之美》

最好、最坏时间复杂度

什么是最好时间复杂度

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

什么是最坏时间复杂度

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

看例子:

    /**
     * 在数组中找到指定数字的未知
     * @param array 数组
     * @param x 需要在数组中查找的数字
     * @return 位置
     */
    int find(int[] array, int x) {
        int i = 0;
        int pos = -1;
        int n = array.length;
        for (; i < n; ++i) {
            if (array[i] == x) {
                pos = i;
                break;
            }
        }
        return pos;
    }

如果数组中第一个元素正好是要查找的变量 x,那就不需要继续遍历剩下的 n-1 个数据了,那时间复杂度就是 O(1)。
但如果数组中不存在变量 x,那我们就需要把整个数组都遍历一遍,时间复杂度就成了 O(n)。

所以例子中的最好时间复杂度是O(1),最坏时间复杂度是O(n)。

平均时间复杂度

什么是平均时间复杂度

平均时间复杂度就是,在所有情况取平均值下,执行这段代码的时间复杂度。

看上面的例子,要查找的变量 x 在数组中的位置,有 n+1 种情况:在数组的 0~n-1 位置中不在数组中。我们把每种情况下,查找需要遍历的元素个数累加起来,然后再除以 n+1,就可以得到需要遍历的元素个数的平均值,即:

所以省略掉不不要的数后,平均复杂度为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;
 }

从代码中看出大部分情况下,时间复杂度都为 O(1)。只有个别情况下,复杂度才比较高,为 O(n)。
O(1) 时间复杂度的插入和 O(n) 时间复杂度的插入,出现的频率是非常有规律的,而且有一定的前后时序关系,一般都是一个 O(n) 插入之后,紧跟着 n-1 个 O(1) 的插入操作,循环往复。

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

均摊时间复杂度就是一种特殊的平均时间复杂度,我们没必要花太多精力去区分它们。

本系列其他文章:
《数据结构与算法》目录