ES6的JavaScript算法思想实现之分而治之,动态规划,贪心算法和回溯算法
内容:分而治之,动态规划,贪心算法,回溯算法及其著名算法问题。(未完成,待继续)
所有源码在我的Github上(如果觉得不错记得给星鼓励我哦):ES6的JavaScript算法思想实现之分而治之,动态规划,贪心算法和回溯算法(分别在divide and rule、dynamic programming、greedy、backtracking目录下)
一、基础算法
1、分而治之
概念:分而治之算法可以分为三个部分。1、分解原问题为多个子问题(原问题的多个小实例);2、解决子问题,用返回解决子问题的方式的递归算法。递归算法的基本情形可以用来解决子问题;3、组合这些子问题的解决方式,得到原问题的解。
分而治之的二分搜索算法如下:
1 const Compare = { 2 LESS_THAN: -1, 3 BIGGER_THAN: 1, 4 EQUALS: 0 5 }; 6 7 const DOES_NOT_EXIST = -1; 8 9 function defaultCompare(a, b) { 10 if (a === b) { 11 return Compare.EQUALS; 12 } 13 return a < b ? Compare.LESS_THAN : Compare.BIGGER_THAN; 14 } 15 16 function swap(array, a, b) { 17 /* const temp = array[a]; 18 array[a] = array[b]; 19 array[b] = temp; */ 20 [array[a], array[b]] = [array[b], array[a]]; 21 } 22 function partition(array, left, right, compareFn) { 23 const pivot = array[Math.floor((right + left) / 2)]; 24 let i = left; 25 let j = right; 26 27 while (i <= j) { 28 while (compareFn(array[i], pivot) === Compare.LESS_THAN) { 29 i++; 30 } 31 while (compareFn(array[j], pivot) === Compare.BIGGER_THAN) { 32 j--; 33 } 34 if (i <= j) { 35 swap(array, i, j); 36 i++; 37 j--; 38 } 39 } 40 return i; 41 } 42 function quick(array, left, right, compareFn) { 43 let index; 44 if (array.length > 1) { 45 index = partition(array, left, right, compareFn); 46 if (left < index - 1) { 47 quick(array, left, index - 1, compareFn); 48 } 49 if (index < right) { 50 quick(array, index, right, compareFn); 51 } 52 } 53 return array; 54 } 55 function quickSort(array, compareFn = defaultCompare) { 56 return quick(array, 0, array.length - 1, compareFn); 57 } 58 59 function binarySearchRecursive(array, value, low, high, compareFn = defaultCompare) { 60 if (low <= high) { 61 const mid = Math.floor((low + high) / 2); 62 const element = array[mid]; 63 if (compareFn(element, value) === Compare.BIGGER_THAN) { 64 return binarySearchRecursive(array, value, low, mid -1, compareFn); 65 } 66 if (compareFn(element, value) === Compare.LESS_THAN) { 67 return binarySearchRecursive(array, value, mid + 1, high, compareFn); 68 } 69 return mid; 70 } 71 return DOES_NOT_EXIST; 72 } 73 74 function binarySearch(array, value, compareFn = defaultCompare){ 75 const sortedArray = quickSort(array); 76 const low = 0; 77 const high = sortedArray.length - 1; 78 return binarySearchRecursive(array, value, low, high, compareFn); 79 } 80 81 82 const array = [8,7,6,5,4,3,2,1]; 83 console.log(array); 84 console.log(binarySearch(array,2)); 85 console.log(binarySearch(array,16));
2、动态规划
概念:动态规划(dynamic programming,DP)是一种将复杂问题分解成更小的子问题来解决的优化技术(分而治之方法是把问题分解成相互独立的子问题,然后组合它们的答案;而动态规划是将问题分解成相互依赖的子问题)。用动态规划解决问题时,要遵循三个重要步骤:1、定义子问题;2、实现要反复执行来解决子问题的部分(考虑递归);3、识别并求解出基线条件。
动态规划能解决一些著名算法问题:
2.1 背包问题
描述:给出一组项,各自有值和容量,目标是找出总值最大的项的集合。这个问题的限制是,总容量必须小于等于“背包”的容量。
1 function knapSack(capacity, weights, values, n) { 2 const kS = []; 3 for (let i = 0; i <= n; i++) { 4 kS[i] = []; 5 } 6 for (let i = 0; i <= n; i++) { 7 for (let w = 0; w <= capacity; w++) { 8 if ( i === 0 || w === 0) { 9 kS[i][w] = 0; 10 } else if (weights[i - 1] <= w) { 11 const a = values[i - 1] + kS[i - 1][w - weights[i - 1]]; 12 const b = kS[i - 1][w]; 13 kS[i][w] = a > b ? a : b; 14 } else { 15 kS[i][w] = kS[i - 1][w]; 16 } 17 } 18 } 19 findValues(n, capacity, kS); 20 return kS[n][capacity]; 21 } 22 23 function findValues(n, capacity, kS) { 24 let i = n; 25 let k = capacity; 26 console.log('Items that are part of the solution:'); 27 while (i > 0 && k > 0) { 28 if (kS[i][k] !== kS[i - 1][k]) { 29 console.log( 30 'item ' + i + ' can be part of solution w,v: ' + weights[i - 1] + ',' + values[i - 1] 31 ); 32 i--; 33 k -= kS[i][k]; 34 } else { 35 i--; 36 } 37 } 38 } 39 40 41 const values = [3,4,5]; 42 const weights = [2,3,4]; 43 const capacity = 5; 44 const n = values.length; 45 46 console.log(knapSack(capacity, weights, values, n));
2.2 最长公共子序列
描述:找出一组序列的最长公共子序列(可由另一序列删除元素但不改变余下元素的顺序而得到)。最长子序列是指,在两个字符串序列中以相同顺序出现,但不要求连续(非字符串子串)的字符串序列。
1 function printSolution(solution, wordX, m, n) { 2 let a = m; 3 let b = n; 4 let x = solution[a][b]; 5 let answer = ''; 6 while (x !== '0') { 7 if (solution[a][b] === 'diagonal') { 8 answer = wordX[a - 1] + answer; 9 a--; 10 b--; 11 } else if (solution[a][b] === 'left') { 12 b--; 13 } else if (solution[a][b] === 'top') { 14 a--; 15 } 16 x = solution[a][b]; 17 } 18 return answer; 19 } 20 export function lcs(wordX, wordY) { 21 const m = wordX.length; 22 const n = wordY.length; 23 const l = []; 24 const solution = []; 25 for (let i = 0; i <= m; i++) { 26 l[i] = []; 27 solution[i] = []; 28 for (let j = 0; j <= n; j++) { 29 l[i][j] = 0; 30 solution[i][j] = '0'; 31 } 32 } 33 for (let i = 0; i <= m; i++) { 34 for (let j = 0; j <= n; j++) { 35 if (i === 0 || j === 0) { 36 l[i][j] = 0; 37 } else if (wordX[i - 1] === wordY[j - 1]) { 38 l[i][j] = l[i - 1][j - 1] + 1; 39 solution[i][j] = 'diagonal'; 40 } else { 41 const a = l[i - 1][j]; 42 const b = l[i][j - 1]; 43 l[i][j] = a > b ? a : b; // max(a,b) 44 solution[i][j] = l[i][j] === l[i - 1][j] ? 'top' : 'left'; 45 } 46 } 47 // console.log(l[i].join()); 48 // console.log(solution[i].join()); 49 } 50 return printSolution(solution, wordX, m, n); 51 }
2.3 矩阵链相乘
描述:给出一系列矩阵,目标是找到这些矩阵相乘的最高效办法(计算次数尽可能少)。相乘运算不会进行,解决方案是找到这些矩阵各自相乘的顺序(由于矩阵乘法结合律的原因)。
1 function matrixChainOrder(p) { 2 const n = p.length; 3 const m = []; 4 const s = []; 5 for (let i = 1; i <= n; i++) { 6 m[i] = []; 7 m[i][i] = 0; 8 } 9 for (let i = 0; i <= n; i++) { 10 s[i] = []; 11 for (let j = 0; j <= n; j++) { 12 s[i][j] = 0; 13 } 14 } 15 for(let l = 2; l < n; l++) { 16 for (let i = 1; i <= (n - l) + 1; i++) { 17 const j = (i + l) - 1; 18 m[i][j] = Number.MAX_SAFE_INTEGER; 19 for (let k = i; k <= j - 1; k++) { 20 const q = m[i][k] + m[k + 1][j] +((p[i - 1] * p [k]) * p[j]); 21 if (q < m[i][j]) { 22 m[i][j] = q; 23 s[i][j] = k; 24 } 25 } 26 } 27 } 28 printOptimalParenthesis(s, 1, n - 1); 29 return m[1][n - 1]; 30 } 31 function printOptimalParenthesis(s, i, j) { 32 if (i === j) { 33 console.log('A[' + i + ']'); 34 } else { 35 console.log('('); 36 printOptimalParenthesis(s, i, s[i][j]); 37 printOptimalParenthesis(s, s[i][j] + 1, j); 38 console.log(')'); 39 } 40 } 41 const p = [10, 100, 5, 50, 1]; 42 console.log(matrixChainOrder(p));
2.4 硬币找零
描述:给出面额为d1,...,dn的一定数量的硬币和要找零的钱数,找出有多少种找零的方法。
我们来研究一下最少硬币找零问题。(找出最少硬币个数的方案)
1 function minCoinChange(coins, amount) { 2 const cache = []; 3 4 const makeChange = (value) => { 5 if(!value) { 6 return []; 7 } 8 if (cache[value]) { 9 return cache[value]; 10 } 11 let min = []; 12 let newMin; 13 let newAmount; 14 for (let i = 0; i < coins.length; i++) { 15 const coin = coins[i]; 16 newAmount = value - coin; 17 // console.log(coin); 18 if (newAmount >= 0) { 19 newMin = makeChange(newAmount); 20 } 21 if ( 22 newAmount >= 0 23 && (newMin.length < min.length - 1 ||!min.length) 24 && (newMin.length || !newAmount) 25 ) { 26 min = [coin].concat(newMin); 27 console.log('new Min ' + min + ' for ' + amount); 28 } 29 } 30 return (cache[value] = min); 31 }; 32 return makeChange(amount); 33 } 34 35 console.log(minCoinChange([1, 3, 4], 6));
2.5 图的全源最短路径
描述:对所有顶点对(u,v),找出从顶点u到顶点v的最短路径。
用Floyd-Warshall算法:
1 const floydWarshall = graph => { 2 const dist = []; 3 const {length} = graph; 4 for (let i = 0; i < length; i++) { 5 dist[i] = []; 6 for (let j = 0; j < length; j++) { 7 if (i === j) { 8 dist[i][j] = 0; 9 } else if (!isFinite(graph[i][j])) { 10 dist[i][j] = Infinity; 11 } else { 12 dist[i][j] = graph[i][j]; 13 } 14 } 15 } 16 for (let k = 0; k < length; k++) { 17 for(let i = 0; i < length; i++) { 18 for (let j = 0; j < length; j++) { 19 if (dist[i][k] + dist[k][j] < dist[i][j]) { 20 dist[i][j] = dist[i][k] + dist[k][j]; 21 } 22 } 23 } 24 } 25 return dist; 26 }; 27 28 const INF = Infinity; 29 const graph = [ 30 [INF, 2, 4, INF, INF, INF], 31 [INF, INF, 2, 4, 2, INF], 32 [INF, INF, INF, INF, 3, INF], 33 [INF, INF, INF, INF, INF, 2], 34 [INF, INF, INF, 3, INF, 2], 35 [INF, INF, INF, INF, INF, INF] 36 ]; 37 38 dist = floydWarshall(graph); 39 let s = ''; 40 for (let i = 0; i < dist.length; i++) { 41 s = ''; 42 for (let j = 0; j < dist.length; j++) { 43 if (dist[i][j] === INF) { 44 s += 'INF '; 45 } else { 46 s += dist[i][j] + ' '; 47 } 48 49 } 50 console.log(s); 51 } 52 53 floydWarshall
3、贪心算法
概念:贪心算法遵循一种近似解决问题的技术,期盼通过每个阶段的局部最优选择(当前最好的解),从而达到全局的最优(全局最优解)。它不像动态规划算法那那样计算更大的格局。
3.1最少硬币找零问题
1 function minCoinChangeGreedy (coins, amount) { 2 const change = []; 3 let total = 0; 4 for (let i = coins.length; i >= 0; i--) { 5 const coin = coins[i]; 6 while (total + coin <= amount) { 7 change.push(coin); 8 total += coin; 9 } 10 } 11 return change; 12 } 13 14 15 16 17 console.log(minCoinChangeGreedy([1, 5, 10], 15)); // [5, 10] 18 console.log(minCoinChangeGreedy([1, 3, 4], 6)); //not the best
3.2 分数背包问题
概念:在分数背包问题中,可以装入分数的物品。
1 function knapSackGreedy(capacity, weights, values) { 2 const n = values.length; 3 let load = 0; 4 let val = 0; 5 for (let i = 0; i < n && load < capacity; i++) { 6 if (weights[i] <= capacity - load) { 7 val += values[i]; 8 load += weights[i]; 9 } else { 10 const r = (capacity - load) / weights[i]; 11 val += r*values[i]; 12 load += weights[i]; 13 } 14 } 15 return val; 16 } 17 18 19 20 const values = [3,4,5]; 21 const weights = [2,3,4]; 22 const capacity = 5; 23 24 console.log(knapSackGreedy(capacity, weights, values));
4、回溯算法
概念:回溯是一种渐进式寻找并构建问题解决方式的策略。从一个可能的动作开始并试着用这个动作解决问题。如果不能解决,就回溯并选择另一个动作直到问题解决为止。根据这种行为,回溯算法会尝试所有可能的动作(如果更快找到了解决办法就尝试较少的次数)来解决问题。可用回溯解决的著名问题有:骑士巡逻问题,N皇后问题,迷宫老鼠问题,数独解问题。
4.1 迷宫老鼠问题
1 function ratInAMaze(maze) { 2 const solution = []; 3 for (let i = 0; i < maze.length; i++) { 4 solution[i] = []; 5 for (let j = 0; j < maze[i].length; j++) { 6 solution[i][j] = 0; 7 } 8 } 9 if (findPath(maze, 0, 0, solution) === true) { 10 return solution; 11 } 12 return 'NO PATH FOUND'; 13 } 14 15 function findPath(maze, x, y, solution) { 16 const n = maze.length; 17 if (x === n - 1 && y === n -1) { 18 solution[x][y] = 1; 19 return true; 20 } 21 if (isSafe(maze, x, y) === true) { 22 solution[x][y] = 1; 23 if (findPath(maze, x + 1, y, solution)) { 24 return true; 25 } 26 if (findPath(maze, x, y + 1, solution)) { 27 return true; 28 } 29 solution[x][y] = 0; 30 return false; 31 } 32 return false; 33 } 34 35 function isSafe(maze, x, y) { 36 const n = maze.length; 37 if (x >= 0 && y >= 0 && x < n && y < n && maze[x][y] !== 0) { 38 return true; 39 } 40 return false; 41 } 42 43 const maze = [ 44 [1, 0, 0, 0], 45 [1, 1, 1, 1], 46 [0, 0, 1, 0], 47 [0, 1, 1, 1] 48 ]; 49 50 console.log(ratInAMaze(maze));
4.2 数独解问题
1 function sudokuSolver(matrix) { 2 if (solveSudoku(matrix) === true) { 3 return matrix; 4 } 5 return 'NO SOLUTION'; 6 } 7 const UNASSIGNED = 0; 8 9 function solveSudoku(matrix) { 10 let row = 0; 11 let col = 0; 12 let checkBlankSpaces = false; 13 for (row = 0; row < matrix.length; row++) { 14 for (col = 0; col < matrix[row].length; col++) { 15 if (matrix[row][col] === UNASSIGNED) { 16 checkBlankSpaces = true; 17 break; 18 } 19 } 20 if (checkBlankSpaces === true) { 21 break; 22 } 23 } 24 if (checkBlankSpaces === false) { 25 return true; 26 } 27 for (let num = 1; num <= 9; num++) { 28 if (isSafe(matrix, row, col, num)) { 29 matrix[row][col] = num; 30 if (solveSudoku(matrix)) { 31 return true; 32 } 33 matrix[row][col] = UNASSIGNED; 34 } 35 } 36 return false; 37 } 38 39 function isSafe(matrix, row, col, num) { 40 return ( 41 !usedInRow(matrix, row, num) 42 && !usedInCol(matrix, col, num) 43 && !usedInBox(matrix, row - (row % 3), col - (col % 3), num) 44 ); 45 } 46 function usedInRow(matrix, row, num) { 47 for (let col = 0; col < matrix.length; col++) { 48 if (matrix[row][col] === num) { 49 return true; 50 } 51 } 52 return false; 53 } 54 55 function usedInCol(matrix, col, num) { 56 for (let row = 0; row < matrix.length; row++) { 57 if (matrix[row][col] === num) { 58 return true; 59 } 60 } 61 return false; 62 } 63 64 function usedInBox(matrix, boxStartRow, boxStartCol, num) { 65 for (let row = 0; row < 3; row++) { 66 for (let col = 0; col < 3; col++) { 67 if (matrix[row + boxStartRow][col + boxStartCol] === num) { 68 return true; 69 } 70 } 71 } 72 return false; 73 } 74 const sudokuGrid = [ 75 [5, 3, 0, 0, 7, 0, 0, 0, 0], 76 [6, 0, 0, 1, 9, 5, 0, 0, 0], 77 [0, 9, 8, 0, 0, 0, 0, 6, 0], 78 [8, 0, 0, 0, 6, 0, 0, 0, 3], 79 [4, 0, 0, 8, 0, 3, 0, 0, 1], 80 [7, 0, 0, 0, 2, 0, 0, 0, 6], 81 [0, 6, 0, 0, 0, 0, 2, 8, 0], 82 [0, 0, 0, 4, 1, 9, 0, 0, 5], 83 [0, 0, 0, 0, 8, 0, 0, 7, 9] 84 ]; 85 86 console.log(sudokuSolver(sudokuGrid));