3L-最好、最坏、平均、均摊时间复杂度
关注公众号 MageByte,设置星标点「在看」是我们创造好文的动力。后台回复 “加群” 进入技术交流群获更多技术成长。 本文来自 MageByte-青叶编写
上次我们说过 时间复杂度与空间复度,列举了一些分析技巧以及一些常见的复杂度分析比如 O(1)、O(logn)、O(n)、O(nlogn),今天会继续细化时间复杂度。
1. 最好情况时间复杂度(best case time complexity)
2.最坏情况时间复杂度(worst case time complexity)
3. 平均情况时间复杂度(average case time complexity)
4.均摊时间复杂度(amortized time complexity)
复杂度分析
public int findGirl(int[] girlArray, int number) {
int i = 0;
int pos = -1;
int n = girlArray.lentgh();
for (; i < n; ++i) {
if (girlArray[i] == number) {
pos = i;
break;
}
}
return pos;
}
代码逻辑你应该很容易看出来,在无序数组 中查找 number 出现的位置,如果没找到就返回 -1。《唐伯虎点秋香》主角星爷通过这个方法遍历数组找到秋香,因为此刻我们还没有学会各种风骚的算法,只能从头到尾查验是不是秋香,所以只能遍历数组。girlArray 数组保存着秋香、冬香、春香……的编码,现在唐伯虎通过 选择 number 这个编码比对是否是秋香。
这段代码在不同的情况下,时间复杂度是不一样的,所以为了描述代码在不同情况下的不同时间复杂度,我们引入了最好、最坏、平均时间复杂度。n = girlArray 数组的长度。
- 当如秋香在第一个,那 代码的时间复杂度就是 O(1)。
- 当秋香在队伍的最后一个,那代码的时间复杂度就是 O(n)。
- 当 秋香在队伍中但是不在队伍第一个,也不再最后一个,那么就不确定。
- 假如华府使诈,队伍里也根本不存在秋香,唐波徐也需要把队伍一个个查验完毕才知道,时间复杂度就成了 O(n)
最好情况时间复杂度
在最理想的情况下,执行这段代码的时间,也就是「唐伯虎」最快点中秋香。假如 这一排姑娘就代表 girlArray 数组,number 变量就是秋香的编码。假如第一个姑娘就是「秋香」那时间复杂度就是 O(1)。
最坏情况时间复杂度
在最糟糕的情况下,执行这段代码的时间复杂度。也就是要一个个查验真个数组的长度 O(n)。
平均情况时间复杂度
其实最好与最坏情况是极端情况,发生的概率并不大。所以为了更准确的表示平均情况下的时间复杂度,引入另一个改变:平均情况时间复杂度。
还是上面的「找秋香」代码,判断 number 编码在循环中出现的位置,有 n + 1种情况:
在数组 0~n-1 中和不在这个数组中。在数组中共有 n 种情况,加上不在数组中则就是 n + 1 种了。 每种情况要遍历的姑娘人数都不同。我们把每种情况需要查找姑娘的数量累加,然后再除以 所有情况数量 (n + 1),就得到需要遍历次数的平均值。敲黑板了:公式就是平均情况复杂度 = 累加每种遍历的元素个数 / 所有的情况数量
平均情况复杂度为:
$$\frac {((1+2+3… +n) + n)} {(n+1)} = \frac {n(n+3)} {2(n+1)}$$
推导过程:
$$\because 1+2+3 …+ n = n + (n-1) + (n-2)… + 1$$
$$\therefore (1 +2 +3… + n) = \frac {n(1+n)} {2}$$
$$\therefore (1+2+3+…+n) + n = \frac {n(3+n)} {2}$$
根据我们之前学的 时间复杂度与空间复度 大 O 表示法,省略系数、地接、常量,所以平均情况时间复杂度是 O(n)。
期望时间复杂度
上面的平均情况时间复杂度推导没有考虑每种情况的发生概率,这里的 n+1 种情况,每种情况发生的概率是不一样的,所以还要引入各自发生的概率再具体分析。
秋香的编号 number 要么在 0 ~ n-1 中,要么不在 0~n-1 中,所以他们的概率是 $\frac {1} {2}$。
同时 number 在 0~n-1 各个位置的概率是一样的为 1/n。根据概率乘法法则,number 在 0~n-1 中任意位置的概率是 $$\frac {1} {2n}$$。
所以在前面推导的基础上,我们再把每种情况发生的概率考虑进去,那么平均情况时间复杂度的计算过程就是:
考虑概率的平均情况复杂度:
$$(1 \frac {1} {2n} + 2 \frac {1} {2n}+ 3 \frac {1} {2n}…+n\frac {n} {2n} ) + n \frac {1} {2} = \frac {3n+1} {4}$$
这就是概率论中的加权平均值,也叫做期望值,所以平均时间复杂度全称叫:加权平均时间复杂度或者期望时间复杂度。
引入概率之后,平均复杂度变为 O($$\frac {3n+1} {4}$$),忽略系数以及常量,最后得到的加权平均时间复杂度为 O(n)。终于分析推导完了,同学们可以松一口气。
注意:
多数情况下,我们不需要区分最好、最坏、平均情况时间复杂度。只有同一块代码在不同情况下时间复杂度有量级差距,我们才会区分 3 种情况,为的是更有效的描述代码的时间复杂度。
均摊情况时间复杂度
最后一个硬骨头来了,了解了上面加上概率的期望时间复杂度再看这个就容易多了。均摊时间复杂度,听起来跟平均时间复杂度有点儿像。
均摊复杂度是一个更加高级的概念,它是一种特殊的情况,应用的场景也更加特殊和有限。
对应的分析方式称为:摊还分析或平摊分析。
// array 表示一个长度为 n 的数组
// 代码中的 array.length 就等于 n
int[] array = new int[n];
int count = 0;
public 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.lenth,遍历数组求和,将求和之后的 sum 值放到数组的第一个位置,然后再将新的数据插入。但如果数组一开始就有空闲空间,则直接将数据插入数组。这里的数据满:对于可反复读写的存储空间,使用者认为它是空的它就是空的。如果你定义清空是全部重写为 0 或者某个值,那也可以!使用者只关心要存的新值!
分析上述的时间复杂度:
- 最理想情况,有空闲空间则直接插入到数组下标 count 的位置即可。所以是 O(1)。
- 最坏的情况,数组没有空闲空间,需要先做一次循环遍历求和,然后再插入。时间复杂度 O(n)。
平均时间复杂度
数组长度为 n,因为可以插入不同位置,所以有 n 种情况,每种复杂度为 O(1)。
还有一种特殊情况,没有空闲空间插入的时候,复杂度是 O(n),一共就是 n+1 种情况,且每种情况的概率都是 $$\frac{1} {n+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} = \frac {2n} {n+1}$$
当省略系数及常量后,平均时间复杂度为 O(1)。
其实我们不需要这么复杂,对比 findGirl 跟 insert 方法。
- findGirl 在极端情况下复杂度 O(1),而 insert 基本情况是 O(1)。只有当数组满了才是 O(n)。
- 对于 insert() 函数来说,O(1) 时间复杂度的插入和 O(n) 时间复杂度的插入,出现的频率是非常有规律的,而且有一定的前后时序关系,一般都是一个 O(n) 插入之后,紧跟着 n-1 个 O(1) 的插入操作,循环往复。
摊还分析法
分析上述示例的平均复杂度分析并不需要如此复杂,无需引入概率论的知识。
因为通过分析可以看出,上述示例代码复杂度大多数为 O(1),极端情况下复杂度才较高为 O(n)。同时复杂度遵循一定的规律,一般为 1 个 O(n),和 n 个 O(1)。针对这样一种特殊场景使用更简单的分析方法:摊还分析法。
通过摊还分析法得到的时间复杂度为均摊时间复杂度。
大致思路:每一次 O(n)都会跟着 n 次 O(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;
}
总体的含义就是向数组添加一个元素,当空间不够的时候重新生情一个原来两倍空间的数组并把原来的数组数据依次复制到新数组中。
其实同学们这里还可以拓展到 HashMap 的拓容,当元素大刀负载因子 0.75 的容量,HashMap 需要拓容为原来的两倍然后再重新 把元素放到新数组中。那么时间复杂度又是多少呢?
关注公众号 MageByte 后台回复 「add」获取本题目答案,也可以回复「加群」加入技术群跟我们一起分享你的看法,我们第一是时间反馈。
参考文献:《数据结构与算法之美》
3L-最好、最坏、平均、均摊时间复杂度的更多相关文章
- Chapter4 复杂度分析(下):浅析最好,最坏,平均,均摊时间复杂度
四个复杂度分析: 1:最好情况时间复杂度(best case time complexity) 2:最坏情况时间复杂度(worst case time complexity) 3:平均情况时间复杂度( ...
- 《数据结构与算法之美》 <02>复杂度分析(下):浅析最好、最坏、平均、均摊时间复杂度?
上一节,我们讲了复杂度的大 O 表示法和几个分析技巧,还举了一些常见复杂度分析的例子,比如 O(1).O(logn).O(n).O(nlogn) 复杂度分析.掌握了这些内容,对于复杂度分析这个知识点, ...
- [BZOJ3277/BZOJ3473] 串 - 后缀数组,二分,双指针,ST表,均摊分析
[BZOJ3277] 串 Description 现在给定你n个字符串,询问每个字符串有多少子串(不包括空串)是所有n个字符串中至少k个字符串的子串(注意包括本身). Solution 首先将所有串连 ...
- 【loj6029】「雅礼集训 2017 Day1」市场 线段树+均摊分析
题目描述 给出一个长度为 $n$ 的序列,支持 $m$ 次操作,操作有四种:区间加.区间下取整除.区间求最小值.区间求和. $n\le 100000$ ,每次加的数在 $[-10^4,10^4]$ 之 ...
- Mr. Kitayuta's Colorful Graph CodeForces - 506D(均摊复杂度)
Mr. Kitayuta has just bought an undirected graph with n vertices and m edges. The vertices of the gr ...
- 【uoj#228】基础数据结构练习题 线段树+均摊分析
题目描述 给出一个长度为 $n$ 的序列,支持 $m$ 次操作,操作有三种:区间加.区间开根.区间求和. $n,m,a_i\le 100000$ . 题解 线段树+均摊分析 对于原来的两个数 $a$ ...
- HDU - 6098:Inversion(暴力均摊)
Give an array A, the index starts from 1. Now we want to know B i =max i∤j A j Bi=maxi∤jAj , i≥2 i≥ ...
- 5.15 牛客挑战赛40 B 小V的序列 关于随机均摊分析 二进制
LINK:小V的序列 考试的时候 没想到正解 于是自闭. 题意很简单 就是 给出一个序列a 每次询问一个x 问序列中是否存在y 使得x^y的二进制位位1的个数<=3. 容易想到 暴力枚举. 第一 ...
- 洛谷 P6783 - [Ynoi2008] rrusq(KDT+势能均摊+根号平衡)
洛谷题面传送门 首先显然原问题严格强于区间数颜色,因此考虑将询问离线下来然后用某些根号级别复杂度的数据结构.按照数颜色题目的套路,我们肯定要对于每种颜色维护一个前驱 \(pre\),那么答案可写作 \ ...
随机推荐
- Torque:轻应用背后的大蓄势 微软语音技术厚积薄发
Torque,为中国用户而来 此前,"微软车库"公开发布了一系列小而美的产品,它们全部由具有"极客创新"精神的微软员工在工作之外的时间构思和开发.Torque就 ...
- node.js 和 npm/cnpm/nrm 的安装
node.js 和 npm/cnpm/nrm 的安装 安装 node.js.去 官网 下载,下载 LTS 版本的.安装时一路点确定,不要改动任何设置. 在 git-bash 或是 cmd 下,输入 n ...
- 未释放资源的教训,开发MongoDB连接一定要关闭连接
废不少工夫将数据存储,全部迁移至mongodb,未作大量改动则是主因. 但遇到奇怪的现象. 程序跑起不久后,mongodb即假死,另起客户端想登陆mongodb都不成. 要重启mongodb服务器才好 ...
- 使用内网映射工具Holer将本地的Web应用映射到公网上访问
Holer exposes local servers behind NATs and firewalls to the public internet over secure tunnels. Su ...
- 4.2英寸的iPhone SE2就要来了!但你还会买单吗?
SE2就要来了!但你还会买单吗?" title="4.2英寸的iPhone SE2就要来了!但你还会买单吗?"> 与其他手机厂商不同,苹果在手机市场的产品策略很&q ...
- TCP大文件发送案例以及UDP介绍
基于TCP的大文件发送 #server服务端 import struct import json import os import socket server = socket.socket() # ...
- HEALER
项目介绍: 专注是一款时间管理应用,可以帮你管理时间,制定计划,让你保持专注,从快节奏的当下抽离,进入另一个平和安静的时空,以获得更好的工作和学习效率. 主模块(专注):设置分类.专注时长.简介,点击 ...
- /lib64/libc.so.6: version `GLIBC_2.18' not found报错解决
今日安装一区块链服务时报错:/lib64/libc.so.6: version `GLIBC_2.18' not found,检查后现有的glibc版本是2.17,然后参考https://www.ji ...
- docker-compose的安装和设定
docker的1.12版本中,swarm已经合体,docker-engine/swarm/docker-compose的三件套装已经变成两件.后续会不会将docker-compose进一步合体呢,想做 ...
- 实现子数组和绝对值差最小 - Objective-C
类似于背包问题,前提条件是数组全是正整数和0,先求和Sum,再从子数组中找出接近Sum/2的子数组 @interface TempState : NSObject @property (nonatom ...