了解算法复杂度
算法复杂度是用来作为算法好坏的衡量标准,复杂度分为两个维度:时间和空间:
时间复杂度,代表算法执行时间效率;
空间复杂度,代表算法占用的内存空间。
对于某些算法来说,相同数据规模的不同数据依然会造成算法的运行时间/空间的不同,因此我们通常使用算法的最坏时间复杂度,记为T(n)。
通常用大O渐近时间复杂度来表式算法的复杂度,它代表着代码执行时间增长的变化趋势。
算法复杂度种类及优先级
1. 常数复杂度:O(1)
2. 对数复杂度:O(logn)
3. 一次方复杂度:O(n)
4. 一次方乘对数复杂度:O(nlogn)
5. 乘方复杂度:O(n^2),O(n^3)
6. 指数复杂度:O(2^n)
7. 阶乘复杂度:O(n!)
8. 无限大指数复杂度:O(n^n)
n | O(1) | O(log n) | O(n) | O(n log n) | O(n2) | O(2n) | O(n!) |
---|---|---|---|---|---|---|---|
1 | < 1 秒 | < 1 秒 | < 1 秒 | < 1 秒 | < 1 秒 | < 1 秒 | < 1 秒 |
10 | < 1 秒 | < 1 秒 | < 1 秒 | < 1 秒 | < 1 秒 | < 1 秒 | 4 秒 |
100 | < 1 秒 | < 1 秒 | < 1 秒 | < 1 秒 | < 1 秒 | 40170万亿年 | >百年历史 |
1,000 | < 1 秒 | < 1 秒 | < 1 秒 | < 1 秒 | < 1 秒 | >百年历史 | >百年 |
10,000 | < 1 秒 | < 1 秒 | < 1 秒 | < 1 秒 | 2分钟 | >百年 | >百年 |
100,000 | < 1 秒 | < 1 秒 | < 1 秒 | 1秒 | 3小时 | >百年 | >百年 |
1,000,000 | < 1 秒 | < 1 秒 | 1秒 | 20秒 | 12天 | >百年 | >百年 |
算法复杂度类型判断
分析方法
下面主要分析前几种常见的时间复杂度:
- 比较容易判断的几种:恒定O(1)、线性O(n)、多次方O(n^2)
- 比较不好判断的:对数O(log n)和nO(log n)、指数O(2 ^ n)、n^n
(其中暂时不详情分析指数O(2 ^ n)、n^n)
在 大O符号表示法中,时间复杂度的公式是: T(n) = O( f(n) ),其中f(n) 表示每行代码执行次数之和,而 O 表示正比例关系
O(1)-恒定时间
有恒定的增长率,算法计算花费的时间不会随着输入的值增大而增长:
1、加、减、乘、除操作。
O(n)-线性时间
增长率为n,随着输入的增大,算法计算花费的时间更长:
1、获取数组中的最大值/最小值;
2、在集合中找到给定的元素;
3、打印列表中的所有值。
O(n ^ 2)-二次时间
增长率为n ^ 2,二次时间复杂度为n的for循环:
1、检查集合中是否有重复的值;
2、使用冒泡排序,插入排序或选择排序对集合中的项目进行排序;
3、查找数组中所有可能的有序对。
O(n ^ c)-多项式时间
同上类推,增长率为n ^ c:
1、三层嵌套循环
求方程式 3x + 9y + 8z = 79; x,y,z小于n。
function findXYZ(n) {
const solutions = [];
for(let x = 0; x < n; x++) {
for(let y = 0; y < n; y++) {
for(let z = 0; z < n; z++) {
if( 3*x + 9*y + 8*z === 79 ) {
solutions.push({x, y, z});
}
}
}
}
return solutions;
}
console.log(findXYZ(10));
O(log n)-对数时间
如何计算是对数时间复杂度?
输入的值作为结果变量n可以和执行次数x表式成指数函数:
n=2^x
就可以转化成
x = log2^n
此时算法复杂度就为O(log n)
简单例子解释
var i = 1;
while(i<n)
{
i = i * 2;
}
n越大循环计算的次数越多,最大执行x次刚好等于n,于是退出while循环,可以表式:2^x = n。
复杂例子举例
对数时间复杂度通常适用于每次将问题分成两半的算法:
1、二分查找。
二分查找:
function indexOf(array, element, offset = 0) {
// split array in half
const half = parseInt(array.length / 2);
const current = array[half];
if(current === element) {
return offset + half;
} else if(element > current) {
const right = array.slice(half);
return indexOf(right, element, offset + half);
} else {
const left = array.slice(0, half)
return indexOf(left, element, offset);
}
}
// Usage example with a list of names in ascending order:
const directory = ["Adrian", "Bella", "Charlotte", "Daniel", "Emma", "Hanna", "Isabella", "Jayden", "Kaylee", "Luke", "Mia", "Nora", "Olivia", "Paisley", "Riley", "Thomas", "Wyatt", "Xander", "Zoe"];
console.log(indexOf(directory, 'Hanna')); // => 5
console.log(indexOf(directory, 'Adrian')); // => 0
console.log(indexOf(directory, 'Zoe')); // => 18
O(n log n)-线性运算
同上,基于O(log n)算法复杂度执行了n次。
1、高效的排序算法,例如合并排序,快速排序等。
/**
* Sort array in asc order using merge-sort
* @example
* sort([3, 2, 1]) => [1, 2, 3]
* sort([3]) => [3]
* sort([3, 2]) => [2, 3]
* @param {array} array
*/
function sort(array = []) {
const size = array.length;
// base case
if (size < 2) {
return array;
}
if (size === 2) {
return array[0] > array[1] ? [array[1], array[0]] : array;
}
// slit and merge
const mid = parseInt(size / 2, 10);
return merge(sort(array.slice(0, mid)), sort(array.slice(mid)));
}
/**
* Merge two arrays in asc order
* @example
* merge([2,5,9], [1,6,7]) => [1, 2, 5, 6, 7, 9]
* @param {array} array1
* @param {array} array2
* @returns {array} merged arrays in asc order
*/
function merge(array1 = [], array2 = []) {
const merged = [];
let array1Index = 0;
let array2Index = 0;
// merge elements on a and b in asc order. Run-time O(a + b)
while (array1Index < array1.length || array2Index < array2.length) {
if (array1Index >= array1.length || array1[array1Index] > array2[array2Index]) {
merged.push(array2[array2Index]);
array2Index += 1;
} else {
merged.push(array1[array1Index]);
array1Index += 1;
}
}
return merged;
}
O(2 ^ n)-指数时间
指数(以2为底)的运行时间意味着,随着输入的增加,算法每次执行的计算都会加倍。
1、功率集:集合上的所有子集;
2、斐波那契;
3、使用动态编程的旅行商问题。
持续优化本文...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。