算法补充知识
发表于 2017-12-21 16:17:46 | 分类于 算法 |
递归
递归,是一种解决问题的方法。通过解决问题的各个小部分,直到解决最初的大问题。通常涉及函数调用自身。
JavaScirpt 调用栈大小的限制
- 如果如果加上用以停止函数递归调用的边界条件,递归不会无限地执行下去。浏览器会抛出错误,也就是栈溢出错误(stack overflowerror)
- ES 6 有尾调用优化(tail call optimization)
- 如果函数内最后一个操作是调用函数,
- 会通过 ”跳转指令“(jump)而不是 ”子程序调用“ (subroutine call )来控制
- 也就是说,ES 6 中,这里的代码可以一直执行下去
斐波那契数列
定义
- 1 和 2 的斐波那契数是 1
- n ( n > 2 ) 的斐波那契数是 ( n - 1 ) 的斐波那契数加上 ( n - 2 ) 的斐波那契数
// 递归实现
function fibonacci( num ) {
if ( num === 1 || num === 2 ) {
return 1;
}
return fibonacci( num - 1 ) + fibonacci( num - 2 );
}
// 非递归的方式
function fib ( num ) {
var n1 = 1,
n2 = 2,
n = 1;
for ( var i = 3; i < num; i++ ) {
n = n1 + n2;
n1 = n2;
n2 = n;
}
return n;
}
- 递归并不比普通版本更快,反倒更慢
- ES 6 中,因为尾调用优化的缘故,递归不会更慢
- 但在其他语言中,递归通常更慢
- 递归更容易理解,代码量更少
动态规划(Dynamic Programming, DP )
- 是一种将复杂问题分解成更小的子问题来解决的优化技术
- 用动态规划解决问题的例子
- 深度优先搜索
- 斐波那契问题
- 动态规划和分而治之(归并排序和快速排序算法中用到的那种),是不同的方法
- 分而治之,把问题分解成相互独立的子问题,然后组合它们的答案
- 动态规划,是将问题分解成相互依赖的子问题
- 用动态归化解决问题时,遵循三个重要步骤:
- 定义子问题
- 实现要反复执行而解决子问题的部分
- 识别并求解出边界条件
- 能用动态规划解决的一些著名的问题
- 背包问题
- 给出一组项目,各自有值和容量
- 目标是找出总值最大的项目的集合
- 问题的限制是,纵容狼必须小于等于“背包”的容量。
- 最长公共子序列
- 找出一组序列的最长公共子序列(可由另一序列删除元素但不改变余下元素的顺序而得到)
- 矩阵链相乘
- 给出一系列矩阵,
- 目标是找到这些矩阵相乘的最高效办法(计算次数尽可能少)
- 相乘操作不会进行,解决方案是找到这些矩阵各自相乘的顺序
- 硬币找零
- 给出面额为 d1...dn 的一定数量的硬币和要找零的钱数,
- 找出有多少种找零的方法
- 图的全源最短路径
- 对所有顶点对(u, v),
- 找出从顶点 u 到顶点 v 的最短路径
- 背包问题
例子:最少硬币找零问题
function MinCoinChange(coins) {
var coins = coins;
var cache = {};
this.makeChange = function (amount) {
var me = this;
if ( !cache[amount] ) {
return [];
}
if ( cache[amount] ) {
return cache[amount];
}
var min = [],
newMin,
newAmount;
for (var i = ; i < coins.length; i++) {
var coin = coins[i];
newAmount = amount - coin;
if (newAmount >= 0) {
newMin = me.makeChange(newAmount);
}
if (
newAmount >= 0 &&
(newMin.length < min.length - 1 || !min.length) &&
(newMin.length || !newAmount)
) {
min = [coin].concat(newMin);
console.log("new Min" + min + " for " + amount);
}
}
};
}
贪心算法
- 遵循一种近似解决问题的技术,期盼通过每个阶段的局部最优选择(当前最好的解),从而达到全局的最优(全局最优解)。不像动态规划那样计算更大的格局
- 比起动态规划,贪心算法更简单、更快
- 贪心算法并不是总能得到最优解
- 最少硬币找零问题
// 思路
// 从最大面额的硬币开始,那尽可能多的这种硬币找零
// 当无法再拿更多这种价值的硬币时,开始拿第二大价值的硬币,依次继续
// 对每个面额(从大到小),把它的值和 total 相加后,
// total 需要小于 amount
// 将当前面额 coin 添加到结果中,也会将它和 total 相加
function MinCoinChange ( coins ) {
var coin = conins;
this.makeChange = function ( amount ) {
var change = [],
total = 0;
for ( var i = coins.length; i >= 0; i--) {
var coin = coins[i];
while ( total + coin <= amount ) {
change.push(coin);
total += coin;
}
}
return change;
};
}
大 O 表示法
大 O 表示法 用于描述算法的性能和复杂程度
符号 | 名称 |
---|---|
O(1) | 常数的 |
O( log(n) ) | 对数的 |
O( (log(n))c ) | 对数多项式的 |
O(n) | 线性的 |
O(n<sup>2</sup>) | 二次的 |
O(n<sup>c</sup>) | 多项式的 |
O(c<sup>n</sup>) | 指数的 |