贪心算法和动态规划

贪心算法

当遇到一个求解全局最优解问题时,如果可以将全局问题切分为小的局部问题,并寻求局部最优解,同时可以证明局部最优解累计的结果就是全局最优解,则可以使用贪心算法

找零问题:

示例:假设你有一间小店,需要找给客户 46 分钱的硬币,你的货柜里只有面额为 25 分、10 分、5 分、1 分的硬币,如何找零才能保证数额正确并且硬币数最小

JavaScript 代码:

/**
 * 得到一个找零的结果: [25, 10, 10, 1]
 * @param {*} total 要找零的总额
 * @param {*} denos 拥有的面额
 */
function exchange(total, denos) {
 if (total <= 0) {
  return [] //不用找零
 }
 // 寻找最大的面额,同时要保证面额小于等于total
 var max = 0
 for (var i = 0; i < denos.length; i++) {
  var deno = denos[i]
  if (deno > max && deno <= total) {
   max = deno
  }
 }
 // max记录这一次的解(局部最优解)
 var result = [max]
 var next = exchange(total - max, denos) //得到后续的局部最优解
 result = result.concat(next) //拼接之后,就是整体最优解
 return result
}

// 要找的总金额
var total = 51
// 拥有的面值
var denos = [30, 25, 10, 5, 1]
var result = exchange(total, denos)
console.log(result) // [25, 10, 10, 1]

动态规划

分治法有一个问题,就是容易重复计算已经计算过的值,使用动态规划,可以将每一次分治时算出的值记录下来,防止重复计算,从而提高效率。

青蛙跳台阶问题:

有 N 级台阶,一只青蛙每次可以跳 1 级或两级,一共有多少种跳法可以跳完台阶?

Js 代码:

var num1 = 0
// 不使用动态规划
function count1(total) {
 num1++
 if (total === 0) return 0
 if (total === 1) return 1
 if (total === 2) return 2
 return count1(total - 1) + count1(total - 2)
}

var num2 = 0
//使用动态规划优化效率
function count2(total) {
 var cache = {} //缓存已经计算过的结果
 function _count(total) {
  if (cache[total] !== undefined) {
   return cache[total] //直接使用缓存结果
  }
  num2++
  var result
  if (total === 0) result = 0
  else if (total === 1) result = 1
  else if (total === 2) result = 2
  else {
   result = _count(total - 1) + _count(total - 2)
  }
  cache[total] = result
  return result
 }
 return _count(total)
}

console.time('没有动态规划')
var result1 = count1(25)
console.log(result1, '计算了' + num1 + '次')
console.timeEnd('没有动态规划')
console.time('动态规划')
var result2 = count2(25)
console.log(result2, '计算了' + num2 + '次')
console.timeEnd('动态规划')

// 121393 "计算了150049次"
// 没有动态规划: 5.377197265625ms
// 121393 "计算了25次"
// 动态规划: 0.339111328125ms

由上述代码运行的结果可知,动态规划可以大大的提高程序的效率