JavaScript笔记 #07# 用js写算法
算法盒子初代(为了提高学习算法的热情。。。)
效果图:
所有代码放在单个html中:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <textarea id="data-area" rows="25" cols="80" placeholder="paste your data here..."></textarea> <p>Algorithm: <select id="algorithms"> <option value="Temp" style="color: #FF0000;">*临时测试</option> <option value="EqualElement">相等元素</option> <option value="Permutations">排列问题</option> <option value="BinarySearch">二分查找</option> <option value="Quicksort">快速排序</option> <option value="MergeSort">归并排序</option> <option value="InsertSort">插入排序</option> <option value="BinarySearchTree">二叉搜索树</option> <option value="IntegerSum">整数和</option> <option value="MaxSub">最大子段和</option> <option value="theKnapsackProblem">0-1背包问题</option> <option value="LCS">最长公共子序列</option> <option value="HeapSort" selected>堆排序</option> </select> <button id="run">run code</button> </p> <script type="text/javascript"> let btn = document.querySelector("#run"); btn.addEventListener("click", handleClick); function handleClick() { let dataArea = document.querySelector('#data-area'); let selector = document.querySelector("#algorithms"); let data = dataArea.value; let algorithm = algorithms[selector.value]; // console.time(selector.value); // data = CommonUtil.handleData(data); // for (let i = 0; i != 100; ++i) { algorithm.run(data); //} // console.timeEnd(selector.value); } </script> <script type="text/javascript"> class CommonUtil { static handleData(data) { let result = []; let digit = /-?\d+\b/g; let match; while(match = digit.exec(data)) { result.push(Number(match[0])); } return result; } // 浅拷贝,对象的属性不能是对象 static lightCopy(obj) { let result = {} Object.assign(result, obj); // 复制对象所有的属性 return result; } static deepCopy(obj) { let result = JSON.parse(JSON.stringify(obj)); return result; } static zeros(x, y) { let result = []; for (let i = 0; i != x; ++i) { result[i] = []; for (let j = 0; j != y; ++j) { result[i][j] = 0; } } return result; } } </script> <script type="text/javascript"> class Temp { static testMaxSub(data) { let arr = CommonUtil.handleData(data); console.time("DP"); for (let i = 0; i != 100; ++i) { MaxSub.solve(arr, 0, arr.length); } console.timeEnd("DP"); console.time("brute-force"); for (let i = 0; i != 100; ++i) { MaxSub.solve2(arr, 0, arr.length); } console.timeEnd("brute-force"); console.time("recursive"); for (let i = 0; i != 100; ++i) { MaxSub.solve3(arr, 0, arr.length); } console.timeEnd("recursive"); } static run(data) { Temp.testMaxSub(data); } } </script> <script type="text/javascript"> class EqualElement { static hasEqualElement(digitSet) { for (let i = 0; i != digitSet.length; ++i) { for (let j = i + 1; j != digitSet.length; ++j) { if (digitSet[i] == digitSet[j]) return true; } } return false; } static run(data) { data = CommonUtil.handleData(data); let numOFbatchs = data.shift(); for (let batch = 0; batch != numOFbatchs; ++batch) { let numOFdigits = data.shift(); let digitSet = []; while (numOFdigits > 0) { digitSet.push(data.shift()); numOFdigits--; } if (EqualElement.hasEqualElement(digitSet)) { console.log("yes"); } else { console.log("no"); } } } } </script> <script type="text/javascript"> class Permutations { static getSequences(arr) { if (arr.length == 2) { return [arr, arr.slice().reverse()]; } let result = []; for (let i = 0; i != arr.length; ++i) { let subArr = arr.slice(0, i).concat(arr.slice(i + 1, arr.length)); let subSequences = Permutations.getSequences(subArr); let wrapped = subSequences.map(subSeq => [arr[i]].concat(subSeq)); result = result.concat(wrapped); } return result; } static run(data) { data = CommonUtil.handleData(data); console.log(data); if (EqualElement.hasEqualElement(data)) { console.log("Invalid data."); } else { let result = Permutations.getSequences(data); console.log(result); } } } </script> <script type="text/javascript"> class BinarySearch { static find(x, arr, start, end) { if (start > end) return { success: false}; let mid = Math.floor((start + end) / 2); if (arr[mid] == x) { return { success: true, index: mid}; } else if (arr[mid] < x) { return BinarySearch.find(x, arr, mid + 1, end); } else { return BinarySearch.find(x, arr, start, mid - 1); } } static run(data) { data = CommonUtil.handleData(data); if (data.length == 0) { const DEFAULT_SIZE = 10; const MAX = 100; for (let i = 0; i != DEFAULT_SIZE; ++i) { data.push(Math.floor(Math.random() * MAX)); } } Quicksort.sort(data, 0, data.length - 1); console.log(data); let x = Number(prompt("请输入要查找的数据:")); if (Number.isNaN(x)) { console.log("非法输入!"); } else { let result = BinarySearch.find(x, data, 0, data.length - 1); if (result.success) { console.log(`A[${result.index}]: ${x}`); } else { console.log(`元素 ${x} 不在数组中`); } } } } </script> <script type="text/javascript"> class Quicksort { static sort(arr, start, end) { if (start >= end) return; let left = start; let right = end; let x = arr[left]; while (left < right) { while (left < right && arr[right] > x) right--; if (left != right) { arr[left] = arr[right]; left++; } while (left < right && arr[left] < x) left++; if (left != right) { arr[right] = arr[left]; right--; } } arr[left] = x; Quicksort.sort(arr, start, left - 1); Quicksort.sort(arr, left + 1, end); } static run(data) { data = CommonUtil.handleData(data); Quicksort.sort(data, 0, data.length - 1); console.log(data); } } </script> <script type="text/javascript"> class MergeSort { // merge two sorted sequences: arr[start:mid] and arr[mid:end] static merge(arr, start, mid, end) { let leftArr = arr.slice(start, mid); // arr[mid] is excluded let rightArr = arr.slice(mid, end); // arr[end] is excluded // put on the buttom of each pile a sentinel // card, which contains a special value that // we use to simplify out codes let sentinel = Infinity; leftArr.push(sentinel); rightArr.push(sentinel); let i = 0, j = 0; // index of leftArr and rightArr for (let k = start; k < end; ++k) { arr[k] = leftArr[i] < rightArr[j] ? leftArr[i++] : rightArr[j++]; } } // sort arr[start:end], arr[end] is excluded static sort(arr, start, end) { // calling sort(arr, 0, 1) doesn't make // sense for there's only one element, // so just stop dividing/recursive and merge directly. if (start + 1 < end) { // divide the problem into two subproblems let mid = Math.floor((start + end) / 2); // recursively solve subproblems MergeSort.sort(arr, start, mid); MergeSort.sort(arr, mid, end); // combine the solutions to the subproblems into // the solution for the original problem. MergeSort.merge(arr, start, mid, end); } } static run(data) { // convert a string to a numeric array data = CommonUtil.handleData(data); MergeSort.sort(data, 0, data.length); console.log(data); } } </script> <script type="text/javascript"> class InsertSort { static sort(arr, start, end) { for (let i = start + 1; i != end; ++i) { let insertDate = arr[i]; let k = i - 1; while (k >= 0 && arr[k] > insertDate) { arr[k + 1] = arr[k]; k--; } arr[k + 1] = insertDate; } } static run(data) { // convert a string to a numeric array data = CommonUtil.handleData(data); InsertSort.sort(data, 0, data.length); console.log(data); } } </script> <script type="text/javascript"> class Node { constructor(data) { this.data = data; this.left = null; this.right = null; } } class BinarySearchTree { constructor(firstData) { this.root = new Node(firstData); // force user to provide a root, // avoid checking every time . } insert(data, current=this.root) { if (data <= current.data) { if (current.left == null) { current.left = new Node(data); } else { this.insert(data, current.left); } } else { if (current.right == null) { current.right = new Node(data); } else { this.insert(data, current.right); } } } toList() { this.result = []; this.inorder(this.root); return this.result; } inorder(node) { if (node == null) return; this.inorder(node.left); this.result.push(node.data); this.inorder(node.right); } static run(data) { data = CommonUtil.handleData(data); let bst = new BinarySearchTree(data[0]); for (let x of data.slice(1)) { bst.insert(x); } console.log(bst.toList()); } } </script> <script type="text/javascript"> class IntegerSum { static searchSum(arr, start, end, x) { if (end - start <= 1) return false; let sum = arr[start] + arr[end - 1]; if (sum < x) { return IntegerSum.searchSum(arr, start + 1, end, x); } else if (sum > x) { return IntegerSum.searchSum(arr, start, end - 1, x); } else { console.log(arr[start], arr[end - 1]); return true; } } static run(data) { data = CommonUtil.handleData(data); // cn MergeSort.sort(data, 0, data.length); // cnlgn // let x = Number(prompt("please enter the value of x :")); let x = Math.floor(Math.random() * 40); console.log("data=" + data); console.log("x=" + x); let end = -1; while (data[++end] <= x); // cn let result; let max = data[end - 1] + data[end - 2]; let min = data[0] + data[1]; if (max < x || min > x) { // deal with special situations result = false; } else if (max == x || min == x) { result = true; } else { result = IntegerSum.searchSum(data, 0, end, x); // cn } console.log(result); } } </script> <script type="text/javascript"> class MaxSub { // 解法1:动态规划 -- O(n) static solve(arr, start, end) { let s = []; // s[n]保存arr[start:n+1]的最大子段和 let b = []; // b[n]保存由从arr[n]开始到arr[start]的逆向最大和 s[start] = { sum: arr[start], start: start, end: start + 1 }; // 当数组只有一个元素的时候,最大子段和只能是这个元素 b[start] = CommonUtil.lightCopy(s[start]); for (let i = start + 1; i != end; ++i) { // b[i] = max(b[i-1]+arr[i], arr[i]) if (b[i - 1].sum + arr[i] > arr[i]) { b[i] = CommonUtil.lightCopy(b[i - 1]); b[i].sum += arr[i]; } else { b[i] = {sum: arr[i], start: i}; } b[i].end = i + 1; // s[i] = max(s[i-1], b[i]) s[i] = CommonUtil.lightCopy( s[i - 1].sum >= b[i].sum ? s[i - 1] : b[i] ); } // 证明:假设s[n]是截至arr[n]的最大子段和(包括arr[n]) // 当考察s[n+1]时,相当于往原序列添加一个新元素arr[n+1] // 结果是生成了arr[0..n+1],arr[1..n+1]等n+1个新序列 // 我们选取这些新序列中最大的,即从arr[n+1]开始的"逆向最大和", // 与旧序列的最大子段和s[n]比较,s[n+1]取两者中更大的一个 // (其实就是在新和旧之间决策),因为考虑了所有可能的情况, // 并且是大中选大,所以s[n+1]是截至arr[n+1]的最大子段和。 // 思路:先考虑一个元素的情况,显然最大子段和只能是那个元素 // 然后考虑2个元素的情况(在原基础上增加1个),要么仍是第 // 一个元素,要么就是逆向最大和,继续增加元素考虑,发现都是 // 反复在做同样的决策.... return s[end - 1]; } // 解法2:暴力求解 -- O(n^2) static solve2(arr, start, end) { let result = { sum: -Infinity, start: start, end: end }; for (let i = start; i != end; ++i) { let temp = { sum: 0, start: i }; for (let j = i; j != end; ++j) { temp.sum += arr[j]; temp.end = j + 1; if (temp.sum > result.sum) { result = CommonUtil.lightCopy(temp); } } } return result; } // 解法3:递归(分治) -- O(nlgn),来自算法导论 static solve3(arr, start, end) { function findMaxCrossingSubarray(arr, start, mid, end) { let leftSum = -Infinity; let sum = 0; let maxLeft; // 开始下标 for (let i = mid - 1; i >= start; --i) { sum = sum + arr[i]; if (sum > leftSum) { leftSum = sum; maxLeft = i; } } let rightSum = -Infinity; sum = 0; let maxRight; // 结束下标 for (let j = mid; j != end; ++j) { sum = sum + arr[j]; if (sum > rightSum) { rightSum = sum; maxRight = j; } } return { sum: leftSum + rightSum, start: maxLeft, end: maxRight + 1 }; } if (start + 1 == end) { // base case return { sum: arr[start], start: start, end: end }; } else { let mid = Math.floor((start + end) / 2); let leftResult = MaxSub.solve3(arr, start, mid); let rightResult = MaxSub.solve3(arr, mid, end); let crossResult = findMaxCrossingSubarray(arr, start, mid, end); let finalResult = { sum: -Infinity, }; if (leftResult.sum > finalResult.sum) finalResult = CommonUtil.lightCopy(leftResult); if (rightResult.sum > finalResult.sum) finalResult = CommonUtil.lightCopy(rightResult); if (crossResult.sum > finalResult.sum) finalResult = CommonUtil.lightCopy(crossResult); return finalResult; } } static run(data) { let arr = CommonUtil.handleData(data); let result = MaxSub.solve(arr, 0, arr.length); // console.log(result); console.log(arr.slice(result.start, result.end)); console.log(`max_sum = ${result.sum}`); } } </script> <script type="text/javascript"> class theKnapsackProblem { static solve(wt, val, knapsackMaxWeight) { let numOfItems = wt.length; // 物品总数 let maxValue = []; for (let i = 0; i <= numOfItems; ++i) { maxValue[i] = []; for (let j = 0; j <= knapsackMaxWeight; ++j) maxValue[i][j] = { val: 0, items: []}; } // 初始化边界,当可选物品为前0个,背包容量为0时显然最大价值只能是0 for (let i = 1; i <= numOfItems; ++i) { let current = i - 1; // wt,val的序号是从0开始的 for (let j = 1; j <= knapsackMaxWeight; ++j) { let notPut = maxValue[i - 1][j]; if (wt[current] <= j) { let put = { val: maxValue[i - 1][j - wt[current]].val + val[current], items: maxValue[i - 1][j - wt[current]].items.concat(current) }; maxValue[i][j] = put.val > notPut.val ? put : notPut; } else { maxValue[i][j] = notPut; } } } return maxValue[numOfItems][knapsackMaxWeight]; } static run(data) { data = CommonUtil.handleData(data); let batchs = data.shift(); for (let i = 0; i != batchs; ++i) { let numOfItems = data.shift(); let knapsackMaxWeight = data.shift(); let wt = [], val = []; for (let j = 0; j != numOfItems; ++j) { wt.push(data.shift()); } for (let j = 0; j != numOfItems; ++j) { val.push(data.shift()); } let result = theKnapsackProblem.solve(wt, val, knapsackMaxWeight); // 格式化输出结果↓ let items = []; console.log(result.items); for (let i = 0; i != numOfItems; ++i) { if (result.items.indexOf(i) != -1) { items.push(1); } else { items.push(0); } } console.log(result.val); console.log(items + ""); } } // 证明:假设maxValue[i][j]是在仅可以选择前i个物品, // 且背包的最大容量为j时,能够组合得到的最大总价值。 // 当考察maxValue[i+1][j]时,实际上就是考察放不放第 // i+1个物品的问题,该决策取决于能不能得到最大价值, // 放第i+1个物品产生的最大价值为maxValue[i][j-w]+v // 不放的最大价值仍为maxValue[i][j],放不放就是所有 // 情况,我们在所有情况里决策一个最大的就可以了。所以 // 这个迭代可以保持循环不变式。 } </script> <script type="text/javascript"> class LCS { static solve(a, b) { // 初始化表 a.unshift(undefined); // 便于处理,从a[1]开始存东西 b.unshift(undefined); let lcs = CommonUtil.zeros(a.length, b.length); // 填表 for (let i = 1; i != a.length; ++i) { for (let j = 1; j != b.length; ++j) { if (a[i] == b[j]) { lcs[i][j] = lcs[i - 1][j - 1] + 1; } else { lcs[i][j] = Math.max(lcs[i - 1][j], lcs[i][j - 1]); } } } // 返回结果 return lcs[a.length - 1][b.length - 1]; } static run(data) { data = data.split("\n"); let a = data[0].split(""); let b = data[1].split(""); console.log(LCS.solve(a, b)); /** * test: * aebfc * abc * output=3 */ } } </script> <script type="text/javascript"> class HeapSort { static left(i) { return 2 * i; } static right(i) { return 2 * i + 1; } static parent(i) { return Math.floor(i / 2); } // 递归版本 static maxHeapify(arr, i) { let l = HeapSort.left(i); let r = HeapSort.right(i); let largest = i; if (l < arr.length && arr[l] > arr[largest]) { largest = l; } if (r < arr.length && arr[r] > arr[largest]) { largest = r; } if (largest != i) { let temp = arr[i]; arr[i] = arr[largest]; arr[largest] = temp; // 此时arr[largest]已经被置换成更小的那个 HeapSort.maxHeapify(arr, largest); } } // 非递归修正版本,heapSize表示改动范围仅限于arr[1:heapSize+1] static maxHeapify2(arr, i, heapSize) { let flag = true; while (flag) { let l = HeapSort.left(i); let r = HeapSort.right(i); let largest = i; if (l <= heapSize && arr[l] > arr[largest]) { largest = l; } if (r <= heapSize && arr[r] > arr[largest]) { largest = r; } if (largest != i) { let temp = arr[i]; arr[i] = arr[largest]; arr[largest] = temp; // 此时arr[largest]已经被置换成更小的那个 i = largest; } else { flag = false; } } } static buildMaxHeap(arr, heapSize) { for (let i = Math.floor(heapSize / 2); i >= 1; --i) { HeapSort.maxHeapify2(arr, i, heapSize); } } static sort(arr) { let heapSize = arr.length - 1; HeapSort.buildMaxHeap(arr, heapSize); for (let i = arr.length - 1; i >= 2; --i) { let temp = arr[i]; arr[i] = arr[1]; arr[1] = temp; HeapSort.maxHeapify2(arr, 1, --heapSize); } } static run(data) { let test1 = [undefined, 16, 4, 10, 14, 7, 9, 3, 2, 8, 1]; HeapSort.sort(test1); console.log(test1); } } </script> <script type="text/javascript"> const algorithms = { "Temp": Temp, "EqualElement": EqualElement, "Permutations": Permutations, "BinarySearch": BinarySearch, "Quicksort": Quicksort, "MergeSort": MergeSort, "InsertSort": InsertSort, "BinarySearchTree": BinarySearchTree, "IntegerSum": IntegerSum, "MaxSub": MaxSub, "theKnapsackProblem": theKnapsackProblem, "LCS": LCS, "HeapSort": HeapSort }; </script> </body> </html>
稍微模块化:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <textarea id="data-area" rows="25" cols="80" placeholder="paste your data here..."></textarea> <p>Algorithm: <select id="algorithms"> <option value="EqualElement">相等元素</option> <option value="Permutations">排列问题</option> <option value="BinarySearch">二分查找</option> <option value="Quicksort">快速排序</option> <option value="MergeSort">归并排序</option> <option value="InsertSort">插入排序</option> <option value="BinarySearchTree">二叉搜索树</option> <option value="IntegerSum">整数和</option> <option value="MaxSub">最大子段和</option> <option value="theKnapsackProblem">0-1背包问题</option> <option value="LCS">最长公共子序列</option> <option value="HeapSort">堆排序</option> <option value="OptimalLoading" selected="">最优装载问题</option> </select> <button id="run">run code</button> </p> <script type="text/javascript"> let btn = document.querySelector("#run"); btn.addEventListener("click", handleClick); function handleClick() { let dataArea = document.querySelector('#data-area'); let selector = document.querySelector("#algorithms"); let data = dataArea.value; let algorithm = algorithms[selector.value]; algorithm.run(data); } </script> <script src="js/CommonUtil.js"></script> <script src="js/EqualElement.js"></script> <script src="js/Permutations.js"></script> <script src="js/BinarySearch.js"></script> <script src="js/Quicksort.js"></script> <script src="js/MergeSort.js"></script> <script src="js/InsertSort.js"></script> <script src="js/BinarySearchTree.js"></script> <script src="js/IntegerSum.js"></script> <script src="js/MaxSub.js"></script> <script src="js/theKnapsackProblem.js"></script> <script src="js/LCS.js"></script> <script src="js/HeapSort.js"></script> <script src="js/OptimalLoading.js"></script> <script type="text/javascript"> const algorithms = { "EqualElement": EqualElement, "Permutations": Permutations, "BinarySearch": BinarySearch, "Quicksort": Quicksort, "MergeSort": MergeSort, "InsertSort": InsertSort, "BinarySearchTree": BinarySearchTree, "IntegerSum": IntegerSum, "MaxSub": MaxSub, "theKnapsackProblem": theKnapsackProblem, "LCS": LCS, "HeapSort": HeapSort, "OptimalLoading": OptimalLoading }; </script> </body> </html>