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

04 | 复杂度分析(下):浅析最好、最坏、平均、均摊时间复杂度

  • 最好情况时间复杂度(best case time complexity)
  • 最坏情况时间复杂度(worst case time complexity)
  • 平均情况时间复杂度(average case time complexity)
  • 均摊时间复杂度(amortized time complexity)

最好、最坏情况时间复杂度

// 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;
  }
  return pos;
}

以上代码,是在一个无序数组 array 中,查找变量 x 出现的位置,找不到则返回 -1。
在数组中查找一个数据,并不需要每次都把整个数组遍历一遍,因此这段代码可优化为:

// 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) 么?

  • 当数组中第一个元素刚好是要查找的变量 x ,那此时的时间复杂度就是 O(1)
  • 当数组中不存在变量 x ,那么整个数组都需要遍历一遍,时间复杂度是 O(n)
    为了表示代码在不同情况下的不同时间复杂度,引入三个概念:最好情况时间复杂度最坏情况时间复杂度平均情况时间复杂度

平均情况时间复杂度

从上面的例子看,想要查找变量 x 在数组中的位置,有 n + 1 种情况:在数组的 0 ~ n - 1 位置不在数组中。每种情况下,查找所需遍历元素的个数累加 1 + 2 + 3 + ... + (n - 2) + (n - 1) + n + n ,除以 n + 1,就能得到需要遍历元素个数的平均值,即 n(n+3)/2(n+1),时间复杂度的大O表示法中,忽略系数、低阶、常量,所以这个公式简化后得到的平均情况时间复杂度为O(n)

但是,上述计算过程没有将概率考虑进去。🤔🤔🤔考虑概率怎么计算呢......

设变量 x 在数组中的概率是 p ,则不在数组中的概率为 1-p,那么,在数组中各个位置的概率应均为 p/n
那么,平均时间复杂度的计算过程应该是这样的:
1 * p/n + 2 * p/n + ... + (n-1) * p/n + n * p/n + n * (1-p)
经计算,可简化为:n(2-p)/2 + p/2,这就是概率论中的加权平均值,也叫作期望值,所以平均时间复杂度的全称应该是加权平均时间复杂度或者期望时间复杂度

⚠️注意:这里和王争老师讲的不一样啦,王争老师的计算结果是(3n+1)/4,他的计算过程可理解为:为了计算方便,假设 p=1/2,将其代入我的公式也可得出相同结果。✌️

引入概率之后,用大O表示法表示,去掉公式n(2-p)/2 + p/2中的系数 (2-p)/2 和常量 p/2 ,上例的加权平均时间复杂度仍然是 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 时,遍历数组求各项和,将 sum 放到数组第一个位置,然后将新的数据插入。如果数组有空闲空间,则直接将数据插入数组。

  • 最好情况时间复杂度:数组中有空闲空间,只需将数据插入到数组下标为 count 的位置,此时时间复杂度 O(1)
  • 最坏情况时间复杂度:数组中没有空闲空间,需要遍历数组求各项和,再将数据插入,此时时间复杂度为 O(n)
  • 平均情况时间复杂度:数组长度是 n ,根据数据插入位置的不同,有n 种可能性,这些情况的时间复杂度均为 O(1) ;还有一种数组没有空闲空间的情况,这种情况的时间复杂度是 O(n) 。这 n+1 种情况发生的概率均为 1/(n+1)。所以,根据加权平均的计算方法,求得平均时间复杂度是:
    1 * 1/(n+1) + 1 * 1/(n+1) + ... + 1 * 1/(n+1) + n * 1/(n+1),即 O(1)

对比以上 find()insert() 两个函数:

  1. find() 函数最好情况下时间复杂度才为 O(1) ,其平均情况时间复杂度为 O(n) ;而 insert() 函数的平均情况时间复杂度就是 O(1) ,只有个别情况下复杂度较高,为 O(n)
  2. 对于 insert() 来说, O(1) 时间复杂度的插入和 O(n) 时间复杂度的插入,出现的频率是有规律可循的,并且有一定的先后时序关系。一般是 O(n) 插入之后,紧跟着 n - 1O(1) 的插入操作,循环往复。

所以,针对 insert() 函数这种特殊场景的复杂度分析,引入了更加简单的分析方法:摊还分析法,通过摊还分析得到的时间复杂度叫作均摊时间复杂度

均摊时间复杂度就是一种特殊的平均时间复杂度。

还看 insert() 函数这个例子,每一次 O(n) 插入之后,都会跟着 n - 1O(1) 的插入操作,所以把耗时多的那次操作均摊到接下来的 n - 1 次耗时少的操作上,均摊下来,这一组连续操作的均摊时间复杂度就是 O(1) 。这就是均摊分析的大致思路。

均摊时间复杂度和摊还分析应用场景比较特殊:

  1. 对一个数据结构进行一组连续操作中,大部分情况下时间复杂度都很低,只有个别情况下时间复杂度比较高
  2. 这些操作之间存在前后连贯的时序关系

符合上面两条的场景下,可以将这一组操作一块儿分析,看是否能将时间复杂度较高的那次操作的耗时分摊到其他时间复杂度较低的操作上。

在能够应用均摊时间复杂度分析的场合,一般均摊时间复杂度就等于最好情况时间复杂度。

课后思考

用今天学到的来分析一下下面代码中 add() 函数的时间复杂度

// 全局变量,大小为 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;
}

思考思考吧~🤔

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,992评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,212评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,535评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,197评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,310评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,383评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,409评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,191评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,621评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,910评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,084评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,763评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,403评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,083评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,318评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,946评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,967评论 2 351

推荐阅读更多精彩内容