美文网首页
数据结构与算法之美笔记——复杂度分析(下)

数据结构与算法之美笔记——复杂度分析(下)

作者: Cloneable | 来源:发表于2019-03-25 00:14 被阅读0次

摘要:

时间复杂度还可分为四种,分别是「最好时间复杂度」、「最坏时间复杂度」、「平均时间复杂度」和「均摊时间复杂度」。

为什么会引入这几种时间复杂度,是由于一段代码会出现多种情况,而在这些不同的执行条件下代码的执行效率会出现变化,所以需要引入这四种时间复杂度,可以更加全面地表达一段代码的执行效率。

最好、最坏时间复杂度

我们先来分析下列代码的时间复杂度。

int findTarget(int[] intArray, int n, int target) {
  int index = -1;
  for(int i = 0; i < n; i++) {
    if(intArray[i] == target) {
      index = i;
    }
  }

  return index;
}

这段代码中 n 表示数组的长度,可以分析出时间复杂度是 O(n),但是这段代码在查找到相应的数据下标后就可以中断循环,所以还可以优化一下。

int findTarget(int[] intArray, int n, int target) {
  int index = -1;
  for(int i = 0; i < n; i++) {
    if(intArray[i] == target) {
      index = i;
      break;
    }
  }

  return index;
}

此时再来分析时间复杂度就会遇到特殊情况,因为第 6 行代码的 break 使代码有几率可以不遍历完整个数组就结束循环,每次的循环次数就可能出现变化,那这种情况下时间复杂度又需要如何分析?

这段代码的循环会有极端情况和一般情况,先来分析极端情况,当 target 对应的数值出现在数组第一个或者最后一个(包括在数组中找不到对应数值情况),这两种情况的时间复杂度分别为 O(1)O(n),这两种情况分别对应了最好情况和最坏情况,所以对应的这两种情况下分析得到的时间复杂度就是最好时间复杂度和最坏时间复杂度。

平均时间复杂度

分析完极端情况我们需要分析一般情况,一般情况下,target 对应数组中的数值有 n 种可能(在数组的第 1 位至第 n 位之间的任意一位)或数组中没有与target 对应的数值,所以总共有 n+1 种可能,那每种可能下需要循环的次数分别为 1, 2, 3, ......, n-1, n, n 次,一般情况下需要循环的次数也就是 \frac{1 + 2 + 3 + ...... + n -1 + n + n}{(n + 1)},可根据数据公式作出如下推导。

\frac{1 + 2 + 3 + ...... + n -1 + n + n}{n + 1} = \frac{\frac{n\times(n + 1)}{2} + n}{n + 1}=\frac{n\times(n + 1) + 2n}{2\times(n + 1)}=\frac{n}{2} + \frac{n}{n + 1} = \frac{n}{2} + 1 - \frac{1}{(n + 1)}

忽略低阶,常量和系数后时间复杂度为 O(n)

其实这段分析有些问题,因为忽略了与 target 对应的值出现在数组中和不出现在数组中的概率。先假设这两种情况下的概率相同,都是二分之一,由于目标数据出现在数组中每个位置的几率都为 \frac{1}{n}, 所以 target 对应数组中每个位置数值的几率为 \frac{1}{2n},所以一般情况下循环的总次数为 1 \times \frac{1}{2n} + 2 \times \frac{1}{2n} + 3 \times \frac{1}{2n} + ..... + n \times \frac{1}{2n} + \frac{n}{2},最后的 \frac{n}{2}target 对应值不在数组内的循环次数。

1 \times\frac{1}{2n} + 2 \times\frac{1}{2n} + 3 \times\frac{1}{2n} + ..... + n \times\frac{1}{2n} + \frac{n}{2}=\frac{\frac{n(n+1)}{2} + n^2}{2n} = \frac{n(3n+1)}{4n}=\frac{3n+1}{4}

时间复杂度为 O(n)

均摊时间复杂度

从名字上看与平均时间复杂度比较相似,但两者有很大的不同,先分析以下代码。

int[] intArray = new int[n];
int count = 0;

void insert(int val) {
  if(count == intArray.length) {
    int sum = 0;
    for(int i = 0; i < intArray.length; i++) {
      sum += intArray[i];
    }
    intArray[0] = sum;
    count = 1;
  }

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

利用之前的方式分析一下这段代码的时间复杂度,当数组没有被填充满时有 n 种可能,加上填充满的情况总共是 n+1 种情况,当填充满数组时会循环执行 n 次代码,所以代码的平均时间复杂度为

1 \times\frac{1}{n + 1} + 1 \times\frac{1}{n + 1} + .... + 1 \times\frac{1}{n + 1} + \frac{n}{n+1} = \frac{2n}{n+1} = \frac{2(n + 1) - 2}{n + 1} = 2 - \frac{2}{n + 1} = O(1)

其实分析这段代码可以发现,循环 n 次的情况是规律出现的,当数组被填充 n - 1 次后就会出现循环 n 次,而 n - 1 次的操作时间复杂度是 O(1),循环的时间复杂度为 O(n)。这种特殊场景下的时间复杂度分析可以使用摊还分析法,也就是说每次 O(n) 操作后都会出现 n - 1 次 O(1) 的操作,可以把耗时多的操作分摊到 n - 1 次时间复杂度低的情况上,所以时间复杂度为

\frac{n}{n-1} = \frac{(n - 1) + 1}{n - 1} = 1 + \frac{1}{n - 1} = O(1)

均摊能够运用的场景比较特殊,而且能够应用均摊时间复杂度分析的场合,一般均摊时间复杂度就等于最好时间复杂度。

课后题目

用以上讲的几种时间复杂度分析下面这段代码。

// 全局变量,大小为 10 的数组 array,长度 len,下标 i。
int array[] = new int[10]; 
int len = 10;
int i = 0;
 
// 往数组中添加一个元素
void add(int element) {
   if (i >= len) { // 数组空间不够了
     // 重新申请一个 2 倍大小的数组空间
     int new_array[] = new int[len*2];
     // 把原来 array 数组中的数据依次 copy 到 new_array
     for (int j = 0; j < len; ++j) {
       new_array[j] = array[j];
     }
     // new_array 复制给 array,array 现在大小就是 2 倍 len 了
     array = new_array;
     len = 2 * len;
   }
   // 将 element 放到下标为 i 的位置,下标 i 加一
   array[i] = element;
   ++i;
}

分析一下这段代码,功能就是向数组的末尾插入元素,当数组满后就把数组扩容两倍,而扩容时需要将之前数组的数据搬移至新数组中,此时就需要循环 n 次。

分析完代码,那最好时间复杂度就是数组未满时的插入为 O(1),最坏时间复杂度就是数组已满时搬移数组的情况为 O(n)

因为每次扩容后数组就有 len 个空置可插入元素,再加上数组填充满后的 1 种可能,总共有 len + 1 种可能,前 len 种可能执行代码一次,最后一种可能会循环执行代码 len 次,所以

1\times\frac{1}{len + 1} + 1\times\frac{1}{len + 1} + ... + 1\times\frac{1}{len + 1} + len \times\frac{1}{len + 1} = \frac{len}{len + 1} + \frac{len}{len + 1} = \frac{2\times len}{len + 1} = 2 - \frac{2}{len + 1} = O(1)

平均时间复杂度就为 O(1)

依照上面分析,前 len 次时间复杂度都是 O(1),每隔 len 次就会有一次时间复杂度为 O(len),将 O(len) 分摊到 len 次上,均摊时间复杂度就为 O(1)


文章中如有问题欢迎留言指正
数据结构与算法之美笔记系列将会做为我对王争老师此专栏的学习笔记,如想了解更多王争老师专栏的详情请到极客时间自行搜索。

相关文章

  • 数据结构与算法

    数据结构与算法之美 数据结构与算法之美1--如何学数据结构与算法之美2--复杂度分析(上)数据结构与算法之美3--...

  • 数据结构与算法之美(一):复杂度分析

    本章内容源于笔者对极客时间《数据结构与算法之美》以下章节的学习笔记: 复杂度分析(上):如何分析、统计算法的执行效...

  • 《数据结构与算法之美--复杂度分析》

    《数据结构与算法之美--复杂度分析》 keyword: ​ 概念,大O表示法,常见的时间复杂度,最好、最坏、平...

  • 算法的复杂度分析

    本文是对极客时间《数据结构与算法之美》03-04 节课关于算法复杂度分析的小结。 前面:为什么要学习数据结构与算法...

  • 复杂度分析(上)

    +文本内容是对王争《数据结构与算法之美》课程的笔记, 如果有任何侵权行为, 请联系博主删除 为什么需要复杂度分析?...

  • 重温:数据结构与算法 - 02复杂度分析(二)

    数据结构与算法之美-学习大纲 上一节,学习了什么是大O复杂度分析、有哪些复杂度分析技巧、什么是时间复杂度、什么是空...

  • 数据结构与算法-复杂度分析

    时间、空间复杂度:衡量算法执行小路的指标,数据结构与算法离不开时间、空间复杂度分析,复杂度分析是算法的精髓。 为什...

  • 数据结构与算法之时间复杂度分析

    复杂度分析,是所有数据结构与算法的重中之重,复杂度分析是整个算法学习的精髓,只要掌握了它,可以说数据结构和算法的内...

  • 《如何计算一个程序的时间和空间复杂度》

    本文为自己的学习笔记,仅供自我学习,学习资料极客时间的王争的《算法与数据结构之美》。 一.基础概念 时间复杂度表示...

  • 数据结构学习大纲

    第一章 绪论 数据结构基本概念数据结构基本概念算法的基本概念算法的时间复杂度与空间复杂度分析基础时间复杂度分析空间...

网友评论

      本文标题:数据结构与算法之美笔记——复杂度分析(下)

      本文链接:https://www.haomeiwen.com/subject/alwpvqtx.html