ES6的JavaScript算法思想实现之分而治之,动态规划,贪心算法和回溯算法

目的:ES6标准下的JS算法的一些实现代码。(作为记录和启发)

内容:分而治之,动态规划,贪心算法,回溯算法及其著名算法问题。(未完成,待继续)

所有源码在我的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));
binarySearch

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));
knapSack

 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 }
LCS

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));
matrixChainOrder

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));
minCoinChange

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
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
minCoinChangeGreedy

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));
knapSackGreedy

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));
ratInAMaze

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));
sudokuSolver
原文地址:https://www.cnblogs.com/xinkuiwu/p/11848821.html