时间复杂度
一个数据规模为n的程序,执行多少次能得到结果。这两者之间有一种函数映射关系:O(f(n))。分析程序时间复杂度的核心就是得到这个函数表达式。
不过时间复杂度并没有像数学中函数这么较真,当n很大的时候,表达式中的低阶项和常数项就会省略,就比如表达式:n^3 + n^2 + 1000,他的时间复杂度就是O(n^3), 因为当n很大的时候,n^2与1000比起n^3就如九牛一毛,所以可以省略。
概念没有实际的支撑就会显得十分空洞,通过分析几个简单排序定能有所感觉。
排序的时间复杂度分析
function bubbleSort (arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) swap(arr, j, j + 1)
}
}
}
对于冒泡排序,我们分析程序执行次数与n的关系:
- 第一次循环执行 n - 1
- 第二次循环执行 n - 2
- 第三次循环执行 n - 3
- ....
- 1
所以执行次数为:(n-1) + (n-2) + (n-3) + ... + 2 + 1 = 2/n^2(等差数列求和), 抛去常数与低阶项,得到冒泡排序的时间复杂度为 O(n^2)
递归中的时间复杂度
一下是求一个数组中最大值的迭代写法与递归写法,对比一下两则的时间复杂度。
function getMax(arr) {
let max = Number.MIN_SAFE_INTEGER
for (let i = 0; i < arr.length; i++) {
max = Math.max(max, arr[i])
}
return max
}
对于迭代版本,无非就是一个遍历。时间复杂度就是O(n)
function getMax(arr, l = 0, r = arr.length - 1) {
if (l === r) return arr[l]
// 取中间数
let mid = l + ((r - l) >> 1)
return Math.max(getMax(arr, l, mid), getMax(arr, mid+1, r))
}
对于递归版本我们需要使用master公式: 一个数组每次分成两份,比较左边的最大值和右边的最大值,即为整个数组的最大值。
master公式
T(N) = a * T(n/b) + O(n^d),简单解释一下这个公式:
- T(N):代表母问题
- a * 代表子问题在一次递归中执行了多少次。对于上诉方法思路,求左边的最大值和求右边的最大值,所以a等于2
- T(n/b) 子问题的规模。每次递归都是去中间值,一分为2,所以b为2
- O(n^d) 除了递归部分,剩下程序的时间复杂大。对于上诉程序,除去递归,就是一些判断,求中间值,比较左边与右边哪个值大,所以为:O(1)
所以此问题的master公式为: T(N) = 2*T(n/2) + O(1) ===> a=2 b=2 d=0。 调用一下结论:
log(b,a) = log(2,2) = 1。 因为1>0 所以时间复杂度为: O(n^log(b,a)) = n ^ 1 = O(n)。后续可以用此方法分析归并排序。
关于O(logN)算法的底数
对于O(logN)复杂度的算法,比较让人疑惑的点有:
- logN的底数到底是多少
- logN的底数能够省略
通过二分查找发来看看底数问题
分析二分查找法的执行次数
对于数组arr=[1,2,3,4...100],这个数组,我们找一个数,到底要找多少次?举一个最坏的情况,我们找的书就是arr[0], 划分范围如下:
- 0 ~ 49
- 0 ~ 24
- 0 ~ 12
- 0 ~ 6
- 0 ~ 3
- 0 ~ 1
- 0 ~ 0
每次划分范围都是上一次的基础上除以2,可以换个思路就是100能够除以多少次2,也就是log(2,100)。所以二分查找的底数为2。
总结来说就是对于二分查找,我们最多只需要log(2,n)次就能找到我们要的数
for (let i = 0; i < 1000; i *= 10) {
console.log(i)
}
对于这个程序,它每个乘以10,意思就是乘以多少次10到1000。次数就是log(10, 100)。也就是log(10,n)
为什么底数可以省略
之所以 底数可以省略,是因为数学公式:
log(a,n) = log (a, b) * log (b, n) ==> log(a,n) / log (b, n) = log(a,b)
其中log(a,b)为一个常数,相当于 log(a,n)与log(b,n)是一个倍数关系,在时间复杂度中,常数项的倍数可以被省略。
这就是log(N)算法没有底数的原因。