如何从最坏、平均、最好的情况分析复杂度?
前言
你好,我是彤哥,一个每天爬二十六层楼还不忘读源码的硬核男人。
上一节,我们从事后统计法过渡到渐近分析法,详细讲解了如何进行算法的复杂度分析。
但是,如果遵循严格的渐近分析法,需要掌握大量数学知识,这无疑给我们评估算法的优劣带来了很大的挑战。
那么,有没有更好地评估算法的方法呢?
答案是必然的,本节,我们就从最坏、平均、最好三种情况来分析分析复杂度。
案例
为了便于讲解,我写了一个小例子:
public class LinearSearch {
public static void main(String[] args) {
int[] array = new int[]{1, 8, 9, 3, 5, 6, 10, 13};
int index = search(array, 10);
System.out.println("index=" + index);
}
private static int search(int[] array, int value) {
for (int i = 0; i < array.length; i++) {
if (array[i] == value) {
return i;
}
}
return -1;
}
}
这个例子使用线性搜索从一个数组中查找一个元素,这个元素有可能存在,也有可能不存在于数组中。
最坏情况
在最坏情况下,要查找的元素不存在于数组中,此时,它的时间复杂度是多少呢?
很简单,必然需要遍历完所有元素才会发现要查找的元素不存在于数组中。
所以,最坏情况下,使用线性查找的时间复杂度为O(n)。
平均情况
在平均情况下,我们要照顾到每一个元素,此时,它的时间复杂度如何计算呢?
在上一节,我们已经讲过计算方式了,不过,这里考虑到有元素不存在于数组中,所以,是(n+1)种可能:
1*1/(n+1) + 2*/(n+1) + ... + n*1/(n+1) + (n+1)/(n+1) = 1/(n+1) * (n+1)(n+2)/2 = (n+2)/2
所以,在平均情况下,忽略掉常数项,使用线性查找的时间复杂度也是O(n)。
为什么要忽略掉常数项? 当n趋向于无穷大的时候,常数项的意义不是很大,所以,可以忽略,比如(n+2)/2=n/2 + 1,n本身已经趋向于无穷大,加不加1有什么意义呢,n的倍数是2还是1/2并不会有明显的差别。 同样地,低阶项一般也会抹掉,比如2n^2 + 3n + 1,当n趋向于无穷大的时候,n^2的值是远远大于3n的,所以,不需要保留3n。 所以,计算复杂度时通常都会把常数项和低阶项抹掉,只保留高阶项。
最好情况
最好情况是什么呢?
如果我们要查找的元素正好是数组的第一个元素,查找一次就找到了,这无疑是最好的情况。
所以,在最好情况下,使用线性查找的时间复杂度是O(1)。
小结
通过上面的分析,可以看到,最坏情况和最好情况是比较好评估的,而平均情况则比较难以计算。
但是,最好情况又不能代表大多数样本,且平均情况与最坏情况在省略常数项的情况下往往是比较接近的。
所以,通常,我们使用最坏情况来评估算法的时间复杂度,这也是比较简单的一种评估方法,且往往也是比较准确的。
后记
本节,我们从最坏、平均、最好三种情况分析了线性查找的时间复杂度,经过详细地分析,我们得出结论,通常使用最坏情况来评估算法的时间复杂度。
请注意,我们这里使用了“通常”,说明有些情况是不能使用最坏情况来评估算法的时间复杂度的。
那么,你知道什么情况下不能使用最坏情况来评估算法的时间复杂度吗?
下一节,我们接着聊。
- 引入Fragment原来是这么回事
- Fragment显示和隐藏、绑定和解绑
- 一网打进Linux下那些查找命令
- 高颜值可定制在线绘图工具-第三版
- network3D 交互式网络生成
- 如何用六点教会老婆写 Python ?
- 连高晓松都想学的区块链江湖切口,「HODL」是什么意思?
- Spring Data REST 与 Spring RestTemplate 实战详解
- 程序员炒股,如何计算股票投资组合的风险和收益
- Docker 容器化部署运维 OpenStack 和 Ceph
- 关于设计模式的思考
- Spring 框架之 AOP 原理剖析
- Java 平台反应式编程(Reactive Programming)入门
- 从原理到实例,他用区块链技术做一了个COIN 客户端
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- DFS&BFS - 37. Sudoku Solver
- Array - 36. Valid Sudoku
- Array - 57. Insert Interval
- Binary Search - 378. Kth Smallest Element in a Sorted Matrix
- Array - 59. Spiral Matrix II
- Array - 54. Spiral Matrix
- String - 8. String to Integer (atoi)
- Array - 16. 3Sum Closest
- Array - 15. 3Sum
- Design - 146. LRU Cache
- LinkedList - 142. Linked List Cycle II
- LinkedList - 2. Add Two Numbers
- Array - 56. Merge Intervals
- golang 内存分析/内存泄漏
- golang gctrace分析gc过程