-
二分查找
二分查找高效的前提是数据结构是有序的
思路:1.排序
2.将数组折半,分成左右两个数组。
3.判断要查找的数和中间位置数值的大小,来判断要查找的数实在哪一半。
4.之后继续折半查找,直至找到这个数。图解:
首先是找出各个指针;
此时target>arr[mid],所以left=mid+1,然后更新arr[mid];
此时target<arr[mid],所以right=mid_1,然后更新arr[mid];
最后发现arr[mid]=target,结束function binarySearch(arr, target){ let start = 0; let end = arr.length - 1; if(!end){ return arr[0] === target ? 0 : -1; } if(end == 1){ return arr[0] === target ? 0 : arr[1] === target ? 1 : -1; } let middle; while(start <= end){ middle = start + ((end - start) >> 1) | 0; if(arr[middle] === target){ return middle }else if(target > arr[middle]){ start = middle + 1 }else{ end = middle - 1 } } return -1 }
-
Master公式计算递归的时间复杂度
-
有些算法在处理一个较大规模的问题时,往往会把问题拆分成几个子问题,对其中的一个或多个问题递归地处理,并在分治之前或之后进行一些预处理、汇总处理。这时候我们可以得到关于这个算法复杂度的一个递推方程,求解此方程便能得到算法的复杂度。其中很常见的一种递推方程就是这样的:
设常数 a >= 1,b > 1,f(n) 为函数,T(n) 为非负整数,T(n) = a T(n / b) + f(n),则有:
- 若 f(n)=O(nlogba−ε),ε>0,那么 T(n)=Θ(nlogba)。
- 若 f(n)=Θ(nlogba),那么 T(n)=Θ(nlogbalogn)。
- 若 f(n)=Ω(nlogba+ε),ε>0,并且对于某个常数 c < 1 和充分大的 n 有 af(n/b)≤cf(n),那么 T(n)=Θ(f(n))。
比如常见的二分查找算法,时间复杂度的递推方程为 T(n) = T(n / 2) + θ(1),显然有 nlogba=n0=Θ(1),满足 Master 定理第二条,可以得到其时间复杂度为 T(n) = θ(log n)。
再看一个例子,T(n) = 9 T(n / 3) + n,可知 nlogba=n2,令ε取 1,显然满足 Master 定理第一条,可以得到 T(n) = θ(n ^ 2)。
来一个稍微复杂一点儿例子,T(n) = 3 T(n / 4) + n log n。nlogba=O(n0.793),取ε = 0.2,显然当 c = 3 / 4 时,对于充分大的 n 可以满足 a * f(n / b) = 3 * (n / 4) * log(n / 4) <= (3 / 4) * n * log n = c * f(n),符合 Master 定理第三条,因此求得 T(n) = θ(n log n)。
运用 Master 定理的时候,有一点一定要特别注意,就是第一条和第三条中的ε必须大于零。如果无法找到大于零的ε,就不能使用这两条规则。
参考: https://blog.gocalf.com/algorithm-complexity-and-master-theorem.html
-
斐波那契数列
问题描述:Fibonacci 数(Fibonacci Number)的定义是:F(n) = F(n - 1) + F(n - 2),并且 F(0) = 0,F(1) = 1。对于任意指定的整数 n(n ≥ 0),计算 F(n) 的精确值,并分析算法的时间、空间复杂度。// 斐波那契数列 递归 function fibonacci(n){ if (n==0) { return 0; } if (n==1) { return 1; } return fibonacci(n-1) + fibonacci(n-2); }
一个看起来很直观、用起来很恐怖的算法就是递归法。根据 Fibonacci 的递推公式,对于输入的 n,直接递归地调用相同的函数分别求出 F(n - 1) 和 F(n - 2),二者相加就是结果。递归的终止点就是递推方程的初值,即 n 取 0 或 1 的时候。
这个算法的时间复杂度有着跟 Fibonacci 类似的递推方程:T(n) = T(n - 1) + T(n - 2) + O(1),很容易得到 T(n) = O(1.618 ^ n)(1.618 就是黄金分割,(1+5)/2)。空间复杂度取决于递归的深度,显然是 O(n)。
// 斐波那契数列 递推法 function fibonacci(n){ if (n == 0) { return 0; } if (n == 1) { return 1; } let a = [0, 1]; let n_i = 1; while(n_i < n) { n_i++; // 交换位置 let next = a[0] + a[1]; a[0] = a[1]; a[1] = next; } return a[1]; }
虽然只是一字之差,但递推法的复杂度要小的多。这个方法就是按照递推方程,从 n = 0 和 n = 1 开始,逐个求出所有小于 n 的 Fibonacci 数,最后就可以算出 F(n)。由于每次计算值需要用到前两个 Fibonacci 数,更小的数就可以丢弃了,可以将空间复杂度降到最低。显然时间复杂度是 O(n),空间复杂度是 O(1)。
比较一下递归法和递推法,二者都用了分治的思想——把目标问题拆为若干个小问题,利用小问题的解得到目标问题的解。二者的区别实际上就是普通分治算法和动态规划的区别。
矩阵法:~
-
归并排序
// 归并排序 function process(arr, L, R) { if (L === R) { return; } let mid = L + ((R - L) >> 1); process(arr,L,mid); process(arr,mid+1,R); merge(arr,L,mid,R); } function merge(arr,L,mid,R) { let help = []; let i = 0; let p1 = L; let p2 = mid +1; while (p1 <= mid && p2 <=R) { help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++]; } while(p1 <= M) { help[i++] = arr[p1++] } while(p2 <= R) { help[i++] = arr[p2++] } for(let i = 0;i<help.length;i++) { arr[L + i] = help[i] } }