算法分析描述
根据数据规模n,分析运行时间(时间复杂度分析)和内存使用情况(空间复杂度分析)
定义
- n: 数据规模
- O(n): 增长函数,又称为增长率。算法的运行时间或内存使用随着数据规模的变化而变化。用于表示时间复杂度和空间复杂度分析。
例如: 时间复杂度:O(1000) , 算法的运行时间为1000s,具体看运行平台,这里只是假设。
情况分析
对于n一样,不同的数据会造成不同的运行时间,分为三种情况,一般我们只分析它的最坏情况。下面以数据结构查找二叉树为例:
二叉树例子
-
最好情况: 左子树和右子树高度相等。查找元素时间复杂度
O(logn)
-
最坏情况:元素往一边倾倒,退化成链表。查找元素时间复杂度
O(n)
-
平均情况:O( l o g n log_n logn)
增长率
下面以运行时间来解释,增长率表达 时间复杂度 和 空间复杂度是一样的方式表达。由快到慢排序
- O(1): 常数级别。使用的时间是固定,于n无关。
O(8) = 10s, O(1000) = 10s
- O(logn): 对数级别。每次处理数据将数量减少一半。
O(8) = log2^8 = 3s
- O(n): 线性级别。数据有多少,就处理多少次。
O(8) -> 8s
- O(nlogn): 线性对数级别。
O(8) = 8*3 = 24
- O(
n
2
n^2
n2):平方级别。
O(8) = 64
- O(
2
n
2^n
2n): 指数级别。
O(8) = 256
- O(n!): 阶乘级别。
O(8) = 40320
例子
下面我们已具体的算法例子求增长率
O(1)
数组的访问
array[0]
解释:数组的访问,不管有多少数据量,访问永远是1。因为数组在内存中的位置是连续的,我们知道数组的开始位置,比如是1,那么访问某个元素array[1000] = 1*1000 = 1000那个位置
Leetcode数据介绍
O(logn)
查找二叉树的查找操作
解释:查找过程,比当前元素小,去左边继续查找,大于去右边继续查找。从上图就可以轻易算出,一共比较了三次。O(log2^8) = 3
O(n)
// 查找元素
for (let i=0; i<array.length; i++) {
if (array[i] == 10) {
break;
}
}
解释:如果目标元素在最后一个位置,那么就要遍历到最后的位置。O(1000) = 1000
O(
n
2
n^2
n2)
let count = 1000;
let target = 506;
let result = 0;
for(let i=1; i<=count; i++) {
for (let j=1; j<=count; j++) {
if (i+j===target) {
result++;
}
}
}
解释:双重循环
问题描述:在1000个数内,找出3个相加和为506有多少个
// js
let count = 1000;
let target = 506;
let result = 0;
for(let i=1; i<=count; i++) {
for (let j=1; j<=count; j++) {
for (let k=1; k<=count; k++) {
if (i+j+k===target) {
result++;
}
}
}
}
分析具体运行时间
运行时间:T(n) = a * 增长函数
a: 常数。具体看运行机器,可以理解为执行每条语句的耗时
增长函数:语句运行的次数。通常为循环语句、函数调用的次数
- 求出增长函数:
O(n^3)
。因为这里有3层循环,其他语句可以忽略不计 - 求出运行时间,这里需要实际运行一下
// js
function testRunTime(fn) {
let start = new Date();
let end = null;
fn();
end = new Date();
console.log(`运行时间: ${(end - start) / 1000}秒`); // 1.718 秒
}
testRunTime(() => {
let count = 1000;
let target = 506;
let result = 0;
for (let i = 1; i <= count; i++) {
for (let j = 1; j <= count; j++) {
for (let k = 1; k <= count; k++) {
if (i + j + k === target) {
result++;
}
}
}
}
});
- 求出a:
1.718 / 1000^3 = 0.000000001718
- 得到时间计算模型:
T(n) = 0.000000001718 * n^3
(秒)
注意事项:
- 如果算法中除了循环或函数调用,有较大的语句运行,也要计算进去。如
O(n) = n + 100
- 增长函数并非只是统计循环、函数调用。
分析内存使用情况
算法运行时所创建的数据类型的内存占用大小总和
- 找出算法运行时创建的数据类型:
5个数字类型变量
。因为for循环中定义的变量是局部变量,每一次循环结束都会销毁,故算一次 - 找出数据类型占用的内存大小,将全部相加
假设每个数据类型占用内存为4字节,则5*4=20
字节