00 写在前面
本文旨在利用一个简单的物理模型类比介绍时间复杂度(Time Complexity)的概念 并向读者讲解 如何使用大O标记法(O Notation)对算法的时间复杂度进行简单估计, 并完成文末的相似概念推导与练习
注:
1. 阅读本文需要您有一定的 计算机语言基础 并 了解一些典型的数学函数模型
2. 本文的代码都由Java写成
3. 驾车行驶的物理模型将贯穿全文, 需要您有一些物理基础
01 目录
目录
02 概念
2.1 时间的推导
想象你即将开车前往某个地方度假, 你如何预估行程所耗的时间?
我们马上就能联想到 物理公式 时间T = 路程S / 速度V
这里的速度V可以理解为 汽车行驶每公里所需要的时间(km/h)
路程S可以理解为 总公里数
那么该公式可以变式为: 时间T = 总公里数N * 行驶每公里的时间t
让我们分析一下上述公式的三个变量:
①行驶每公里的时间t:
受很多因素影响, 如: 汽车的性能, 路况等, 但综合起来可以看作一个常熟
② 时间T: 因变量, 随N的改变而改变
③ 总公里数N: 自变量
那程序运行的时间呢?
最简单的, 我们同样可以使用上面的公式作为推导 时间T = 语句条数N * 每条语句的运行时间t
同样的, 我们也来分析一下上述公式的三个变量:
① 每条语句的运行时间t:
受众多因素影响, 如: 是否同时运行多个程序, CPU的处理速度等, 综合起来也能看作一个常数
②时间T: 因变量, 随N的改变而改变
③ 语句条数 / 语句执行次数N: 自变量
2.2 问题规模(the Size of Input)
程序运行时间T = 语句条数N * 每条语句的运行时间t
其中, 语句条数 / 语句执行次数N, 可能受输入数据的影响
1. N不受输入数据影响的情况
void func(int input){
System.out.println(1);
}
无论input为多少, 该函数中的语句始终只有一条, 执行时间是固定的
2. N受输入数据影响的情况
void func(int input){
for(int i=0;i<input;i++){
System.out.print(1);
}
}
我们发现, 此时当input值变化, 语句执行次数也进行变化, 于是, 我们将输入值的大小称作 问题规模(the Size of Input)
2.3 时间复杂度(Time Complexity)
在驾车出游的例子中, 我们可以把问题规模类比成 目的地n
我们发现: 当目的地n发生变化时, 总路程N发生变化, 行程所需时间T也会发生变化
同样的, 在程序运行的过程中, 问题规模n发生变化, 语句的执行次数N发生变化, 运行时间T也会发生变化
时间复杂度, 是一个用于描述 问题规模n 与 运行时间T 间关系的数学模型
03 时间复杂度的计算
3.1 大O标记法(O Notation)
3.1.1 使用步骤
1. 写出语句条数表达式T (用n指代问题规模)
int func(int input){
for(int i=0;i<input;i++){
System.out.println(0);
System.out.print(1);
}
return 0;
}
T = n + n + 1= 2n + 1;
2. 取"变化最快"的项
如何判断"变化最快的项"?
① 比较n趋近于正无穷时的变化趋势: 可以画函数图进行比较
②常见的比较 (从左到右随n的变化 由慢到快)
常数 < logn < n < nlogn < n^2 < n^3 < 2^n < n! < n^n
void func(int input){
for(int i=0;i<input;i++){
System.out.println(0);
System.out.print(1);
}
}
T = 2n + 1 -> 2n
3. 去掉该项的系数
int func(int input){
for(int i=0;i<input;i++){
System.out.println(0);
System.out.print(1);
}
return 0;
}
T = 2n + 1 -> 2n -> n -> O(n)
上述实例代码的时间复杂度用大O标记法表示为O(n)
3.1.2 复杂情况下时间复杂度的计算
什么是复杂情况?
出现函数, 多个循环时
如何解决?
按块写出时间复杂度并相加, 选取最慢的时间复杂度
常用时间复杂度所耗费的时间从小到大:
O(1) < O(longn) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)注: 常数阶在该方法中统一用 1 表示
例:
int func(int input){
int count = 0;
for(int i=0;i<input;i++){
count++;
}
for(int m=0;m<input;m++){
for(int n=0;n<input;n++){
System.out.print(1);
count++;
}
}
return 0;
}
1. T = 1 + n*1 + n*n*2 + 1
2. 化简为大O标记法:
T = O(1) + n*O(1) + n*n*O(1) + O(1)
= O(1) + O(n) + O(n^2) + O(1)
3. 根据上表选出变化最快的
T -> O(n^2)
上例代码的时间复杂度为 O(n^2)
3.2 我们为什么要这样计算
如果你想驾车去一个很近的地方, 只需要实际去走一圈就可以得到确切时间, 但如果去一个非常遥远的地方, 尝试的成本是很高的
程序也是一样, 这样的计算能够帮助我们对算法的效率有一个初步的估计,进而也能够成为我们优化算法的一条思路
04 反思
问问自己以下问题:
1. 本文主要介绍了哪些概念? 它们之间有什么联系?
2. 本文使用了什么例子介绍的这些概念? 如何用这个例子串起所有概念?3.
05 推导与练习
空间复杂度定义(摘自百度): 一个算法在运行过程中临时占用存储空间大小的量度
请你使用本文的思路 尝试推导 空间复杂度的相关概念, 并推导下列代码的空间复杂度 (答案在文末哦, 请先尝试独立完成吧)
void bubbleSort(int[] array) {
for (int end = array.length; end > 0; end--) {
boolean sorted = true;
for (int i = 1; i < end; i++) {
if (array[i - 1] > array[i]) {
Swap(array, i - 1, i);
sorted = false;
}
}
if (sorted == true) {
break;
}
}
}
int[] fibonacci(int n) {
long[] fibArray = new long[n + 1];
fibArray[0] = 0;
fibArray[1] = 1;
for (int i = 2; i <= n ; i++) {
fibArray[i] = fibArray[i - 1] + fibArray [i - 2];
}
return fibArray;
}
long factorial(int N) {
return N < 2 ? N : factorial(N-1)*N;
}
继续向下拉是答案哦
答案
1. O(1)
2. O(1)
3. O(N)