大O表示法
它用于描述算法的性能和复杂程度。大O表示法将算法按照消耗的时间进行分类,依据随输入增大所需要的空间/内存。 分析算法时,时常遇到以下几类函数。
| 符号 | 名称 |
|---|---|
| O(1) | 常数的 |
| O(log(n)) | 对数的 |
| O(log(n)c) | 对数多项的 |
| O(n) | 线性的 |
| O(n²) | 二次的 |
| O(n^c) | 多项的 |
| O(nⁿ) | 指数的 |
理解大O表示法
如何衡量算法的效率?通常是用资源,例如CPU(时间)占用,内存占用,硬盘占用和网络占用。当讨论大O表示法时,一般考虑的是CPU(时间)占用。 让我们试着用一些例子来理解大O表示法的规则。
- O(1)
function increment(num) {
return ++num;
}
假设运行increment(1)函数,执行时间等于X,如果再用不同的参数(例如2)运行一次increment函数,执行时间依然是X。和参数无关,increment函数的性能都一样。因此,我们说上述函数的复杂度是O(1)(常数)。
- O(n)
function sequentialSearch(array, value, equalsFn = defaultEquals) {
for(let i = 0; i < array.length; i++) { // {1}
return i;
}
}
return -1;
如果将含10个元素的数组([1, ..., 10])传递给该函数,假如搜索1这个元素,那么,第一次判断时就能找到想要搜索的元素。在这里我们假设每执行一次行{1},开销1。 现在,假如要搜索元素11。行{1}会执行10次(迭代数组中所有的值,并且找不到要搜索的元素,因而结果返回-1)。如果行{1}的开销是1,那么它执行10次的开销就是10,10倍于第一种假设。 现在假如该数组有1000个元素([1, ..., 1000])。搜索1001的结果是行{1}执行了1000次(然后返回-1)。 注意,sequentialSearch函数执行的总开销取决于数组元素的个数(数组大小),而且也和搜索的值有关。如果是查找数组中存在的值,行{1}会执行几次呢?如果查找的是数组中不存在的值,那么行{1}就会执行和数组大小一样多次,这就是通常所说的最坏情况。 最坏情况下,如果数组大小是10,开销就是10;如果数组大小是1000,开销就是1000。可以得出sequentialSearch函数的时间复杂度是O(n),n是输入数组的大小。
- O(n²) 用冒泡排序做O(n²)的例子
function bubbleSort(array, compareFn = defaultCompare) {
const { length } = array;
for (let i = 0; i < length; i++) { // {1}
for(let j = 0; j < length - 1; j++) { // {2}
if(compareFn(array[j], array[j + 1]) === Compare.BIGGER_THAN) {
swap(array, j, j + 1);
}
}
}
return array;
}
假设行{1}和行{2}的开销分别是1。修改算法的实现使之计算开销。
function bubbleSort(array, compareFn = defaultCompare) {
const { length } = array;
let cost = 0;
for (let i = 0; i < length; i++) { // {1}
cost++;
for(let j = 0; j < length - 1; j++) { // {2}
cost++;
if(compareFn(array[j], array[j + 1]) === Compare.BIGGER_THAN) {
swap(array, j, j + 1);
}
}
}
console.log(`cost for bubbleSort with input size ${length} is ${cost}`);
return array;
}
如果用大小为10的数组执行bubbleSort,开销是100(10²)。如果用大小为100的数组执行bubbleSort,开销就是10000(100²)。需要注意,我们每次增加输入的大小,执行都会越来越久。
时间复杂度O(n)的代码只有一层循环,而O(n²)的代码有双层嵌套循环。如果算法有三层迭代数组的嵌套循环,它的时间复杂度很可能就是O(n³)。
时间复杂度比较
我们可以创建一个表格来表示不同的时间复杂度。
| 输入大小(n) | O(1) | O(log(n)) | O(n) | O(nlog(n)) | O(n²) | O(2ⁿ) |
|---|---|---|---|---|---|---|
| 10 | 1 | 1 | 10 | 10 | 100 | 1024 |
| 20 | 1 | 1.3 | 20 | 26.02 | 400 | 1048576 |
| 50 | 1 | 1.69 | 50 | 84.94 | 2500 | 非常大 |
| 100 | 1 | 2 | 100 | 200 | 10000 | 非常大 |
| 500 | 1 | 2.69 | 500 | 1349.48 | 250000 | 非常大 |
| 1000 | 1 | 3 | 1000 | 3000 | 1000000 | 非常大 |
| 10000 | 1 | 4 | 10000 | 40000 | 1000000000 | 非常大 |