├── .babelrc ├── .gitignore ├── README.md ├── assets └── big-o-graph.png ├── jest.config.js ├── package.json ├── src ├── algorithms │ ├── cryptography │ │ ├── caesar-cipher │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── caesarCipher.test.js │ │ │ └── caesarCipher.js │ │ ├── hill-cipher │ │ │ ├── README.md │ │ │ ├── _test_ │ │ │ │ └── hillCipher.test.js │ │ │ └── hillCipher.js │ │ ├── polynomial-hash │ │ │ ├── PolynomialHash.js │ │ │ ├── README.md │ │ │ ├── SimplePolynomialHash.js │ │ │ └── __test__ │ │ │ │ ├── PolynomialHash.test.js │ │ │ │ └── SimplePolynomialHash.test.js │ │ └── rail-fence-cipher │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ └── railFenceCipher.test.js │ │ │ └── railFenceCipher.js │ ├── graph │ │ ├── breadth-first-search │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── breadthFirstSearch.test.js │ │ │ └── breadthFirstSearch.js │ │ ├── depth-first-search │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── depthFirstSearch.test.js │ │ │ └── depthFirstSearch.js │ │ └── kruskal │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ └── kruskal.test.js │ │ │ └── kruskal.js │ ├── linked-list │ │ ├── reserve-traversal │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── reverseTraversal.test.js │ │ │ └── reverseTraversal.js │ │ └── traversal │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ └── traversal.test.js │ │ │ └── traversal.js │ ├── math │ │ ├── binary-floating-point │ │ │ ├── README.md │ │ │ ├── __tests__ │ │ │ │ ├── bitsToFloat.test.js │ │ │ │ └── floatAsBinaryString.test.js │ │ │ ├── bitsToFloat.js │ │ │ ├── floatAsBinaryString.js │ │ │ ├── images │ │ │ │ ├── 02-half-precision-floating-point-number-explained.png │ │ │ │ └── 03-scientific-notation.png │ │ │ └── testCases.js │ │ ├── bits │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ ├── bitLength.test.js │ │ │ │ ├── bitsDiff.test.js │ │ │ │ ├── clearBit.test.js │ │ │ │ ├── countSetBits.test.js │ │ │ │ ├── divideByTwo.test.js │ │ │ │ ├── fullAdder.test.js │ │ │ │ ├── getBit.test.js │ │ │ │ ├── isEven.test.js │ │ │ │ ├── isPositive.test.js │ │ │ │ ├── isPowerOfTwo.test.js │ │ │ │ ├── multiply.test.js │ │ │ │ ├── multiplyByTwo.test.js │ │ │ │ ├── multiplyUnsigned.test.js │ │ │ │ ├── setBit.test.js │ │ │ │ ├── switchSign.test.js │ │ │ │ └── updateBit.test.js │ │ │ ├── bitLength.js │ │ │ ├── bitsDiff.js │ │ │ ├── clearBit.js │ │ │ ├── countSetBits.js │ │ │ ├── divideByTwo.js │ │ │ ├── fullAdder.js │ │ │ ├── getBit.js │ │ │ ├── isEven.js │ │ │ ├── isPositive.js │ │ │ ├── isPowerOfTwo.js │ │ │ ├── multiply.js │ │ │ ├── multiplyByTwo.js │ │ │ ├── multiplyUnsigned.js │ │ │ ├── setBit.js │ │ │ ├── switchSign.js │ │ │ └── updateBit.js │ │ ├── complex-number │ │ │ ├── ComplexNumber.js │ │ │ ├── README.md │ │ │ └── __test__ │ │ │ │ └── ComplexNumber.test.js │ │ ├── euclidean-algorithm │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ ├── euclideanAlgorithm.test.js │ │ │ │ └── euclideanAlgorithmIterative.test.js │ │ │ ├── euclideanAlgorithm.js │ │ │ └── euclideanAlgorithmIterative.js │ │ ├── euclidean-distance │ │ │ ├── README.md │ │ │ ├── __tests__ │ │ │ │ └── euclideanDistance.test.js │ │ │ └── euclideanDistance.js │ │ ├── factorial │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ ├── factorial.test.js │ │ │ │ └── factorialRecursive.test.js │ │ │ ├── factorial.js │ │ │ └── factorialRecursive.js │ │ ├── fast-powering │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── fastPowering.test.js │ │ │ └── fastPowering.js │ │ ├── fibonacci │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ ├── fibonacci.test.js │ │ │ │ ├── fibonacciNth.test.js │ │ │ │ └── fibonacciNthClosedForm.test.js │ │ │ ├── fibonacci.js │ │ │ ├── fibonacciNth.js │ │ │ └── fibonacciNthClosedForm.js │ │ ├── horner-method │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ ├── classicPolynome.test.js │ │ │ │ └── hornerMethod.test.js │ │ │ ├── classicPolynome.js │ │ │ └── hornerMethod.js │ │ ├── integer-partition │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── integerPartition.test.js │ │ │ └── integerPartition.js │ │ ├── is-power-of-two │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ ├── isPowerOfTwo.test.js │ │ │ │ └── isPowerOfTwoBitwise.test.js │ │ │ ├── isPowerOfTwo.js │ │ │ └── isPowerOfTwoBitwise.js │ │ ├── least-common-multiple │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── leastCommonMultiple.test.js │ │ │ └── leastCommonMultiple.js │ │ ├── matrix │ │ │ ├── Matrix.js │ │ │ ├── README.md │ │ │ └── __tests__ │ │ │ │ └── Matrix.test.js │ │ ├── pascal-triangle │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ ├── pascalTriangle.test.js │ │ │ │ └── pascalTriangleRecursive.test.js │ │ │ ├── pascalTriangle.js │ │ │ └── pascalTriangleRecursive.js │ │ ├── primality-test │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── trialDivision.test.js │ │ │ └── trialDivision.js │ │ ├── prime-factors │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── primeFactors.test.js │ │ │ └── primeFactors.js │ │ ├── radian │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ ├── degreeToRadian.test.js │ │ │ │ └── radianToDegree.test.js │ │ │ ├── degreeToRadian.js │ │ │ └── radianToDegree.js │ │ ├── sieve-of-eratosthenes │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── sieveOfEratosthenes.test.js │ │ │ └── sieveOfEratosthenes.js │ │ └── square-root │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ └── squareRoot.test.js │ │ │ └── squareRoot.js │ ├── others │ │ ├── best-time-to-buy-sell-stocks │ │ │ ├── README.md │ │ │ ├── __tests__ │ │ │ │ ├── accumulatorBestTimeToBuySellStocks.test.js │ │ │ │ ├── dpBestTimeToBuySellStocks.test.js │ │ │ │ ├── dqBestTimeToBuySellStocks.test.js │ │ │ │ └── peakvalleyBestTimeToBuySellStocks.test.js │ │ │ ├── accumulatorBestTimeToBuySellStocks.js │ │ │ ├── dpBestTimeToBuySellStocks.js │ │ │ ├── dqBestTimeToBuySellStocks.js │ │ │ └── peakvalleyBestTimeToBuySellStocks.js │ │ ├── hanoi-tower │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── hanoiTower.test.js │ │ │ └── hanoiTower.js │ │ ├── jump-game │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ ├── backtrackingJumpGame.test.js │ │ │ │ ├── dpBottomUpJumpGame.test.js │ │ │ │ ├── dpTopDownJumpGame.test.js │ │ │ │ └── greedyJumpGame.test.js │ │ │ ├── backtrackingJumpGame.js │ │ │ ├── dpBottomUpJumpGame.js │ │ │ ├── dpTopDownJumpGame.js │ │ │ └── greedyJumpGame.js │ │ ├── knight-tour │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── knightTour.test.js │ │ │ └── knightTour.js │ │ ├── n-queens │ │ │ ├── QueenPosition.js │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ ├── QueensPosition.test.js │ │ │ │ ├── nQueens.test.js │ │ │ │ └── nQueensBitwise.test.js │ │ │ ├── nQueens.js │ │ │ └── nQueensBitwise.js │ │ ├── rain-terraces │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ ├── bfRainTerraces.test.js │ │ │ │ └── dpRainTerraces.test.js │ │ │ ├── bfRainTerraces.js │ │ │ └── dpRainTerraces.js │ │ ├── recursive-staircase │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ ├── recursiveStaircaseBF.test.js │ │ │ │ ├── recursiveStaircaseDP.test.js │ │ │ │ ├── recursiveStaircaseIT.test.js │ │ │ │ └── recursiveStaircaseMEM.test.js │ │ │ ├── recursiveStaircaseBF.js │ │ │ ├── recursiveStaircaseDP.js │ │ │ ├── recursiveStaircaseIT.js │ │ │ └── recursiveStaircaseMEM.js │ │ ├── square-matrix-rotation │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── squareMatrixRotation.test.js │ │ │ └── squareMatrixRotation.js │ │ └── unique-paths │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ ├── btUniquePaths.test.js │ │ │ ├── dpUniquePaths.test.js │ │ │ └── uniquePaths.test.js │ │ │ ├── btUniquePaths.js │ │ │ ├── dpUniquePaths.js │ │ │ └── uniquePaths.js │ ├── search │ │ ├── binary-search │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── binarySearch.test.js │ │ │ └── binarySearch.js │ │ ├── interpolation-search │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── interpolationSearch.test.js │ │ │ └── interpolationSearch.js │ │ ├── jump-search │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── jumpSearch.test.js │ │ │ └── jumpSearch.js │ │ └── linear-search │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ └── linearSearch.test.js │ │ │ └── linearSearch.js │ ├── sets │ │ ├── cartesian-product │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── cartesianProduct.test.js │ │ │ └── cartesianProduct.js │ │ ├── combinations │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ ├── combineWithRepetitions.test.js │ │ │ │ └── combineWithoutRepetitions.test.js │ │ │ ├── combineWithRepetitions.js │ │ │ └── combineWithoutRepetitions.js │ │ ├── fisher-yates │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── fisherYates.test.js │ │ │ └── fisherYates.js │ │ ├── longest-common-subsequence │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── longestCommonSubsequence.test.js │ │ │ └── longestCommonSubsequence.js │ │ ├── longest-increasing-subsequence │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── dpLongestIncreasingSubsequence.test.js │ │ │ └── dpLongestIncreasingSubsequence.js │ │ ├── permutations │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ ├── permutateWithRepetitions.test.js │ │ │ │ └── permutateWithoutRepetitions.test.js │ │ │ ├── permutateWithRepetitions.js │ │ │ └── permutateWithoutRepetitions.js │ │ ├── power-set │ │ │ ├── README.md │ │ │ ├── btPowerSet.js │ │ │ └── bwPowerSet.js │ │ └── shortest-common-supersequence │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ └── shortestCommonSupersequence.test.js │ │ │ └── shortestCommonSupersequence.js │ ├── sorting │ │ ├── Sort.js │ │ ├── SortTester.js │ │ ├── __test__ │ │ │ └── Sort.test.js │ │ ├── bubble-sort │ │ │ ├── BubbleSort.js │ │ │ ├── README.md │ │ │ └── __test__ │ │ │ │ └── BubbleSort.test.js │ │ ├── counting-sort │ │ │ ├── CountingSort.js │ │ │ ├── README.md │ │ │ └── __test__ │ │ │ │ └── CountingSort.test.js │ │ ├── heap-sort │ │ │ ├── HeapSort.js │ │ │ ├── README.md │ │ │ └── __test__ │ │ │ │ └── HeapSort.test.js │ │ ├── insertion-sort │ │ │ ├── InsertionSort.js │ │ │ ├── README.md │ │ │ └── __test__ │ │ │ │ └── InsertionSort.test.js │ │ ├── merge-sort │ │ │ ├── MergeSort.js │ │ │ ├── README.md │ │ │ └── __test__ │ │ │ │ └── MergeSort.test.js │ │ ├── quick-sort │ │ │ ├── QuickSort.js │ │ │ ├── QuickSortInPlace.js │ │ │ ├── README.md │ │ │ └── __test__ │ │ │ │ ├── QuickSort.test.js │ │ │ │ └── QuickSortInPlace.test.js │ │ ├── radix-sort │ │ │ ├── README.md │ │ │ ├── RadixSort.js │ │ │ └── __test__ │ │ │ │ └── RadixSort.test.js │ │ ├── selection-sort │ │ │ ├── README.md │ │ │ ├── SelectionSort.js │ │ │ └── __test__ │ │ │ │ └── SelectionSort.test.js │ │ └── shell-sort │ │ │ ├── README.md │ │ │ ├── ShellSort.js │ │ │ └── __test__ │ │ │ └── ShellSort.test.js │ ├── string │ │ ├── hamming-distance │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── hammingDistance.test.js │ │ │ └── hammingDistance.js │ │ ├── knuth-morris-pratt │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ │ └── knuthMorrisPratt.test.js │ │ │ └── knuthMorrisPratt.js │ │ └── levenshtein-distance │ │ │ ├── README.md │ │ │ ├── __test__ │ │ │ └── levenshteinDistance.test.js │ │ │ └── levenshteinDistance.js │ └── tree │ │ ├── breadth-first-search │ │ ├── README.md │ │ ├── __test__ │ │ │ └── breadthFirstSearch.test.js │ │ └── breadthFirstSearch.js │ │ └── depth-first-search │ │ ├── README.md │ │ ├── __test__ │ │ └── depthFirstSearch.test.js │ │ └── depthFirstSearch.js ├── data-structures │ ├── bloom-filter │ │ ├── BloomFilter.js │ │ ├── README.md │ │ └── __test__ │ │ │ └── BloomFilter.test.js │ ├── disjoint-set │ │ ├── DisjointSet.js │ │ ├── DisjointSetItem.js │ │ ├── README.md │ │ └── __test__ │ │ │ ├── DisjointSet.test.js │ │ │ └── DisjointSetItem.test.js │ ├── doubly-linked-list │ │ ├── DoublyLinkedList.js │ │ ├── DoublyLinkedListNode.js │ │ ├── README.md │ │ └── __test__ │ │ │ ├── DoublyLinkedList.test.js │ │ │ └── DoublyLinkedListNode.test.js │ ├── graph │ │ ├── Graph.js │ │ ├── GraphEdge.js │ │ ├── GraphVertex.js │ │ ├── README.md │ │ └── __test__ │ │ │ ├── Graph.test.js │ │ │ ├── GraphEdge.test.js │ │ │ └── GraphVertex.test.js │ ├── hash-table │ │ ├── HashTable.js │ │ ├── README.md │ │ └── __test__ │ │ │ └── HashTable.test.js │ ├── heap │ │ ├── Heap.js │ │ ├── MaxHeap.js │ │ ├── MinHeap.js │ │ ├── README.md │ │ └── __test__ │ │ │ ├── Heap.test.js │ │ │ ├── MaxHeap.test.js │ │ │ └── MinHeap.test.js │ ├── linked-list │ │ ├── LinkedList.js │ │ ├── LinkedListNode.js │ │ ├── README.md │ │ └── __test__ │ │ │ ├── LinkedList.test.js │ │ │ └── LinkedListNode.test.js │ ├── priority-queue │ │ ├── PriorityQueue.js │ │ ├── README.md │ │ └── __test__ │ │ │ └── PriorityQueue.test.js │ ├── queue │ │ ├── Queue.js │ │ ├── README.md │ │ └── __test__ │ │ │ └── Queue.test.js │ ├── stack │ │ ├── README.md │ │ ├── Stack.js │ │ └── __test__ │ │ │ └── Stack.test.js │ ├── tree │ │ ├── BinaryTreeNode.js │ │ ├── README.md │ │ ├── __test__ │ │ │ └── BinaryTreeNode.test.js │ │ ├── avl-tree │ │ │ ├── AvlTree.js │ │ │ ├── README.md │ │ │ └── __test__ │ │ │ │ └── AvlTRee.test.js │ │ ├── binary-search-tree │ │ │ ├── BinarySearchTree.js │ │ │ ├── BinarySearchTreeNode.js │ │ │ ├── README.md │ │ │ └── __test__ │ │ │ │ ├── BinarySearchTree.test.js │ │ │ │ └── BinarySearchTreeNode.test.js │ │ ├── fenwick-tree │ │ │ ├── FenwickTree.js │ │ │ ├── README.md │ │ │ └── __test__ │ │ │ │ └── FenwickTree.test.js │ │ ├── red-black-tree │ │ │ ├── README.md │ │ │ ├── RedBlackTree.js │ │ │ └── __test__ │ │ │ │ └── RedBlackTree.test.js │ │ └── segment-tree │ │ │ ├── README.md │ │ │ ├── SegmentTree.js │ │ │ └── __test__ │ │ │ └── SegmentTree.test.js │ └── trie │ │ ├── README.md │ │ ├── Trie.js │ │ ├── TrieNode.js │ │ └── __test__ │ │ ├── Trie.test.js │ │ └── TrieNode.test.js └── utils │ └── Comparator.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ] 5 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | coverage 4 | .vscode 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /assets/big-o-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hahuyhungdev/javascript-algorithms-and-data-structure/158bde4ec2043e6f97ef12186b28b6f12d0f5621/assets/big-o-graph.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // The bail config option can be used here to have Jest stop running tests after 3 | // the first failure. 4 | bail: false, 5 | 6 | // Indicates whether each individual test should be reported during the run. 7 | verbose: false, 8 | 9 | // Indicates whether the coverage information should be collected while executing the test 10 | collectCoverage: false, 11 | 12 | // The directory where Jest should output its coverage files. 13 | coverageDirectory: './coverage/', 14 | 15 | // If the test path matches any of the patterns, it will be skipped. 16 | testPathIgnorePatterns: ['/node_modules/'], 17 | 18 | // If the file path matches any of the patterns, coverage information will be skipped. 19 | coveragePathIgnorePatterns: ['/node_modules/'], 20 | 21 | // The pattern Jest uses to detect test files. 22 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.jsx?$', 23 | 24 | // This option sets the URL for the jsdom environment. 25 | // It is reflected in properties such as location.href. 26 | // @see: https://github.com/facebook/jest/issues/6769 27 | testURL: 'http://localhost/', 28 | 29 | // @see: https://jestjs.io/docs/en/configuration#coveragethreshold-object 30 | coverageThreshold: { 31 | global: { 32 | statements: 100, 33 | branches: 95, 34 | functions: 100, 35 | lines: 100, 36 | }, 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-dsa", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "test": "jest", 8 | "coverage": "npm run test -- --coverage" 9 | }, 10 | "keywords": [ 11 | "computer-science", 12 | "cs", 13 | "algorithms", 14 | "data-structures", 15 | "javascript", 16 | "algorithm", 17 | "javascript-algorithms", 18 | "sorting-algorithms", 19 | "graph", 20 | "tree", 21 | "interview", 22 | "interview-preparation" 23 | ], 24 | "dependencies": {}, 25 | "engines": { 26 | "node": ">=12.0.0", 27 | "npm": ">=6.9.0" 28 | }, 29 | "devDependencies": { 30 | "@babel/preset-env": "^7.15.8", 31 | "jest": "^27.2.5" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/algorithms/cryptography/caesar-cipher/README.md: -------------------------------------------------------------------------------- 1 | # Mật mã Caesar 2 | 3 | Trong mật mã học, **Mật mã Caesar** hay còn được gọi là **mật mã chuyển vị** là một trong những kỹ thuật mã hóa đơn giản và phổ biến nhất. Đây là một dạng mật mã thay thế, trong đó mỗi ký tự trên văn bản thô sẽ được thay bằng một ký tự khác, có vị trí cách nó một khoảng xác định trong bảng chữ cái. Ví dụ với độ dịch chuyển là `3`, `D` sẽ trở thành `A`, `E` sẽ trở thành `B`, v.v. Tên của kỹ thuật mã hóa này được đặt theo tên của Julius Caesar, người đã sử dụng nó trong các thư từ bí mật của mình 4 | 5 | ![Caesar Cipher Algorithm](https://upload.wikimedia.org/wikipedia/commons/4/4a/Caesar_cipher_left_shift_of_3.svg) 6 | 7 | ## Ví dụ 8 | 9 | Các chuyển đổi có thể biểu diễn bằng cách sắp hai bảng chữ cái trên hai hàng song song với nhau; bảng chữ cái mật mã sẽ là bảng chữ cái thô đã được dịch sang trái hoặc sang phải một số vị trí. Ví dụ, dưới đây là một bộ mật mã Caesar được thiết lập bằng phép dịch sang trái 3 vị trí, tương đương với phép dịch sang phải 23 vị trí (con số vị trí dịch này được sử dụng làm khóa mã): 10 | 11 | ```text 12 | Plain: ABCDEFGHIJKLMNOPQRSTUVWXYZ 13 | Cipher: XYZABCDEFGHIJKLMNOPQRSTUVW 14 | ``` 15 | Khi tiến hành mã hóa, người gửi mật mã sẽ tra cứu từng ký tự của tin nhắn gốc trên dòng "plain" và sau đó viết ra ký tự tương ứng lấy từ dòng "cipher". 16 | 17 | ```text 18 | Plaintext: THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG 19 | Ciphertext: QEB NRFZH YOLTK CLU GRJMP LSBO QEB IXWV ALD 20 | ``` 21 | 22 | ## Độ phức tạp 23 | 24 | - Thời gian: `O(|n|)` 25 | - Không gian: `O(|n|)` 26 | 27 | ## Liên kết 28 | 29 | - [Caesar cipher on Wikipedia](https://en.wikipedia.org/wiki/Caesar_cipher) -------------------------------------------------------------------------------- /src/algorithms/cryptography/caesar-cipher/__test__/caesarCipher.test.js: -------------------------------------------------------------------------------- 1 | import { caesarCipherEncrypt, caesarCipherDecrypt } from '../caesarCipher'; 2 | 3 | describe('caesarCipher', () => { 4 | it('should not change a string with zero shift', () => { 5 | expect(caesarCipherEncrypt('abcd', 0)).toBe('abcd'); 6 | expect(caesarCipherDecrypt('abcd', 0)).toBe('abcd'); 7 | }); 8 | 9 | it('should cipher a string with different shifts', () => { 10 | expect(caesarCipherEncrypt('abcde', 3)).toBe('defgh'); 11 | expect(caesarCipherDecrypt('defgh', 3)).toBe('abcde'); 12 | 13 | expect(caesarCipherEncrypt('abcde', 1)).toBe('bcdef'); 14 | expect(caesarCipherDecrypt('bcdef', 1)).toBe('abcde'); 15 | 16 | expect(caesarCipherEncrypt('xyz', 1)).toBe('yza'); 17 | expect(caesarCipherDecrypt('yza', 1)).toBe('xyz'); 18 | }); 19 | 20 | it('should be case insensitive', () => { 21 | expect(caesarCipherEncrypt('ABCDE', 3)).toBe('defgh'); 22 | }); 23 | 24 | it('should correctly handle an empty strings', () => { 25 | expect(caesarCipherEncrypt('', 3)).toBe(''); 26 | }); 27 | 28 | it('should not cipher unknown chars', () => { 29 | expect(caesarCipherEncrypt('ab2cde', 3)).toBe('de2fgh'); 30 | expect(caesarCipherDecrypt('de2fgh', 3)).toBe('ab2cde'); 31 | }); 32 | 33 | it('should encrypt and decrypt full phrases', () => { 34 | expect(caesarCipherEncrypt('THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG', 23)) 35 | .toBe('qeb nrfzh yoltk clu grjmp lsbo qeb ixwv ald'); 36 | 37 | expect(caesarCipherDecrypt('qeb nrfzh yoltk clu grjmp lsbo qeb ixwv ald', 23)) 38 | .toBe('the quick brown fox jumps over the lazy dog'); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/algorithms/cryptography/rail-fence-cipher/__test__/railFenceCipher.test.js: -------------------------------------------------------------------------------- 1 | import { encodeRailFenceCipher, decodeRailFenceCipher } from '../railFenceCipher'; 2 | 3 | describe('railFenceCipher', () => { 4 | it('encodes a string correctly for base=3', () => { 5 | expect(encodeRailFenceCipher('', 3)).toBe(''); 6 | expect(encodeRailFenceCipher('12345', 3)).toBe( 7 | '15243', 8 | ); 9 | expect(encodeRailFenceCipher('WEAREDISCOVEREDFLEEATONCE', 3)).toBe( 10 | 'WECRLTEERDSOEEFEAOCAIVDEN', 11 | ); 12 | expect(encodeRailFenceCipher('Hello, World!', 3)).toBe( 13 | 'Hoo!el,Wrdl l', 14 | ); 15 | }); 16 | 17 | it('decodes a string correctly for base=3', () => { 18 | expect(decodeRailFenceCipher('', 3)).toBe(''); 19 | expect(decodeRailFenceCipher('WECRLTEERDSOEEFEAOCAIVDEN', 3)).toBe( 20 | 'WEAREDISCOVEREDFLEEATONCE', 21 | ); 22 | expect(decodeRailFenceCipher('Hoo!el,Wrdl l', 3)).toBe( 23 | 'Hello, World!', 24 | ); 25 | expect(decodeRailFenceCipher('15243', 3)).toBe( 26 | '12345', 27 | ); 28 | }); 29 | 30 | it('encodes a string correctly for base=4', () => { 31 | expect(encodeRailFenceCipher('', 4)).toBe(''); 32 | expect(encodeRailFenceCipher('THEYAREATTACKINGFROMTHENORTH', 4)).toBe( 33 | 'TEKOOHRACIRMNREATANFTETYTGHH', 34 | ); 35 | }); 36 | 37 | it('decodes a string correctly for base=4', () => { 38 | expect(decodeRailFenceCipher('', 4)).toBe(''); 39 | expect(decodeRailFenceCipher('TEKOOHRACIRMNREATANFTETYTGHH', 4)).toBe( 40 | 'THEYAREATTACKINGFROMTHENORTH', 41 | ); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/algorithms/graph/breadth-first-search/README.md: -------------------------------------------------------------------------------- 1 | # Tìm kiếm theo chiều rộng (BFS) 2 | 3 | Tìm kiếm theo chiều rộng là một thuật toán duyệt hoặc tìm kiếm trên cấu trúc cây hoặc đồ thị.Thuật toán bắt đầu từ đỉnh gốc và lần lượt nhìn các đỉnh kề với đỉnh gốc. Sau đó, với mỗi đỉnh trong số đó, thuật toán lại lần lượt nhìn trước các đỉnh kề với nó mà chưa được quan sát trước đó và lặp lại. 4 | 5 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/5/5d/Breadth-First-Search-Algorithm.gif) 6 | 7 | ## References 8 | 9 | - [Wikipedia](https://en.wikipedia.org/wiki/Breadth-first_search) 10 | - [Tree Traversals (Inorder, Preorder and Postorder)](https://www.geeksforgeeks.org/tree-traversals-inorder-preorder-and-postorder/) 11 | - [BFS vs DFS](https://www.geeksforgeeks.org/bfs-vs-dfs-binary-tree/) 12 | - [BFS Visualization](https://www.cs.usfca.edu/~galles/visualization/BFS.html) 13 | -------------------------------------------------------------------------------- /src/algorithms/graph/depth-first-search/README.md: -------------------------------------------------------------------------------- 1 | # Tìm kiếm theo chiều sâu (DFS) 2 | 3 | Tìm kiếm ưu tiên chiều sâu hay tìm kiếm theo chiều sâu là một thuật toán duyệt hoặc tìm kiếm trên cấu trúc cây hoặc đồ thị. Thuật toán bắt đầu tại gốc (hoặc chọn một đỉnh nào đó coi như gốc) và khám phá xa nhất có thể theo mỗi nhánh. 4 | 5 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/7/7f/Depth-First-Search.gif) 6 | 7 | ## Liên kết 8 | 9 | - [Wikipedia](https://en.wikipedia.org/wiki/Depth-first_search) 10 | - [Tree Traversals (Inorder, Preorder and Postorder)](https://www.geeksforgeeks.org/tree-traversals-inorder-preorder-and-postorder/) 11 | - [BFS vs DFS](https://www.geeksforgeeks.org/bfs-vs-dfs-binary-tree/) 12 | - [DFS Visualization](https://www.cs.usfca.edu/~galles/visualization/DFS.html) 13 | -------------------------------------------------------------------------------- /src/algorithms/linked-list/reserve-traversal/README.md: -------------------------------------------------------------------------------- 1 | # Duyệt ngược danh sách liên kết 2 | 3 | Nhiệm vụ là duyệt danh sách liên kết đã cho theo thứ tự ngược. 4 | 5 | Ví dụ: 6 | ![](https://upload.wikimedia.org/wikipedia/commons/6/6d/Singly-linked-list.svg) 7 | 8 | Thứ tự duyệt ngược là : 9 | 10 | ```text 11 | 37 → 99 → 12 12 | ``` 13 | 14 | Độ phức tạp thời gian là `O(n)` bởi vì chúng ta đi qua tất cả các nút một lần. 15 | 16 | ## Liên kết 17 | 18 | - [Wikipedia](https://en.wikipedia.org/wiki/Linked_list) -------------------------------------------------------------------------------- /src/algorithms/linked-list/reserve-traversal/__test__/reverseTraversal.test.js: -------------------------------------------------------------------------------- 1 | import LinkedList from '../../../../data-structures/linked-list/LinkedList'; 2 | import reverseTraversal from '../reverseTraversal'; 3 | 4 | describe('reverseTraversal', () => { 5 | it('should traverse linked list in reverse order', () => { 6 | const linkedList = new LinkedList(); 7 | 8 | linkedList 9 | .append(1) 10 | .append(2) 11 | .append(3); 12 | 13 | const traversedNodeValues = []; 14 | const traversalCallback = (nodeValue) => { 15 | traversedNodeValues.push(nodeValue); 16 | }; 17 | 18 | reverseTraversal(linkedList, traversalCallback); 19 | 20 | expect(traversedNodeValues).toEqual([3, 2, 1]); 21 | }); 22 | }); 23 | 24 | // it('should reverse traversal the linked list with callback', () => { 25 | // const linkedList = new LinkedList(); 26 | // 27 | // linkedList 28 | // .append(1) 29 | // .append(2) 30 | // .append(3); 31 | // 32 | // expect(linkedList.toString()).toBe('1,2,3'); 33 | // expect(linkedList.reverseTraversal(linkedList.head, value => value * 2)).toEqual([6, 4, 2]); 34 | // expect(() => linkedList.reverseTraversal(linkedList.head)).toThrow(); 35 | // }); 36 | -------------------------------------------------------------------------------- /src/algorithms/linked-list/reserve-traversal/reverseTraversal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Duyệt bằng hàm callback. 3 | * @callback traversalCallback 4 | * @param {*} nodeValue 5 | */ 6 | 7 | /** 8 | * @param {LinkedListNode} node 9 | * @param {traversalCallback} callback 10 | */ 11 | function reverseTraversalRecursive(node, callback) { 12 | if (node) { 13 | reverseTraversalRecursive(node.next, callback); 14 | callback(node.value); 15 | } 16 | } 17 | 18 | /** 19 | * @param {LinkedList} linkedList 20 | * @param {traversalCallback} callback 21 | */ 22 | export default function reverseTraversal(linkedList, callback) { 23 | reverseTraversalRecursive(linkedList.head, callback); 24 | } 25 | -------------------------------------------------------------------------------- /src/algorithms/linked-list/traversal/README.md: -------------------------------------------------------------------------------- 1 | # Duyệt danh sách liên kết 2 | 3 | Nhiệm vụ là duyệt danh sách liên kết đã cho theo đúng thứ tự. 4 | Ví dụ : 5 | 6 | ![Singly linked list](https://upload.wikimedia.org/wikipedia/commons/6/6d/Singly-linked-list.svg) 7 | 8 | Thứ tự duyệt đúng là : 9 | 10 | ```text 11 | 12 → 99 → 37 12 | ``` 13 | 14 | Độ phức tạp thời gian là `O(n)` bởi vì chúng ta đi qua tất cả các nút một lần. 15 | 16 | ## Liên kết 17 | 18 | - [Wikipedia](https://en.wikipedia.org/wiki/Linked_list) 19 | -------------------------------------------------------------------------------- /src/algorithms/linked-list/traversal/__test__/traversal.test.js: -------------------------------------------------------------------------------- 1 | import LinkedList from '../../../../data-structures/linked-list/LinkedList'; 2 | import traversal from '../traversal'; 3 | 4 | describe('traversal', () => { 5 | it('should traverse linked list', () => { 6 | const linkedList = new LinkedList(); 7 | 8 | linkedList 9 | .append(1) 10 | .append(2) 11 | .append(3); 12 | 13 | const traversedNodeValues = []; 14 | const traversalCallback = (nodeValue) => { 15 | traversedNodeValues.push(nodeValue); 16 | }; 17 | 18 | traversal(linkedList, traversalCallback); 19 | 20 | expect(traversedNodeValues).toEqual([1, 2, 3]); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/algorithms/linked-list/traversal/traversal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Duyệt bằng hàm callback. 3 | * @callback traversalCallback 4 | * @param {*} nodeValue 5 | */ 6 | 7 | /** 8 | * @param {LinkedList} linkedList 9 | * @param {traversalCallback} callback 10 | */ 11 | export default function traversal(linkedList, callback) { 12 | let currentNode = linkedList.head; 13 | 14 | while (currentNode) { 15 | callback(currentNode.value); 16 | currentNode = currentNode.next; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/algorithms/math/binary-floating-point/__tests__/bitsToFloat.test.js: -------------------------------------------------------------------------------- 1 | import { testCases16Bits, testCases32Bits, testCases64Bits } from '../testCases'; 2 | import { bitsToFloat16, bitsToFloat32, bitsToFloat64 } from '../bitsToFloat'; 3 | 4 | describe('bitsToFloat16', () => { 5 | it('should convert floating point binary bits to floating point decimal number', () => { 6 | for (let testCaseIndex = 0; testCaseIndex < testCases16Bits.length; testCaseIndex += 1) { 7 | const [decimal, binary] = testCases16Bits[testCaseIndex]; 8 | const bits = binary.split('').map((bitString) => parseInt(bitString, 10)); 9 | expect(bitsToFloat16(bits)).toBeCloseTo(decimal, 4); 10 | } 11 | }); 12 | }); 13 | 14 | describe('bitsToFloat32', () => { 15 | it('should convert floating point binary bits to floating point decimal number', () => { 16 | for (let testCaseIndex = 0; testCaseIndex < testCases32Bits.length; testCaseIndex += 1) { 17 | const [decimal, binary] = testCases32Bits[testCaseIndex]; 18 | const bits = binary.split('').map((bitString) => parseInt(bitString, 10)); 19 | expect(bitsToFloat32(bits)).toBeCloseTo(decimal, 7); 20 | } 21 | }); 22 | }); 23 | 24 | describe('bitsToFloat64', () => { 25 | it('should convert floating point binary bits to floating point decimal number', () => { 26 | for (let testCaseIndex = 0; testCaseIndex < testCases64Bits.length; testCaseIndex += 1) { 27 | const [decimal, binary] = testCases64Bits[testCaseIndex]; 28 | const bits = binary.split('').map((bitString) => parseInt(bitString, 10)); 29 | expect(bitsToFloat64(bits)).toBeCloseTo(decimal, 14); 30 | } 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/algorithms/math/binary-floating-point/__tests__/floatAsBinaryString.test.js: -------------------------------------------------------------------------------- 1 | import { floatAs32BinaryString, floatAs64BinaryString } from '../floatAsBinaryString'; 2 | import { testCases32Bits, testCases64Bits } from '../testCases'; 3 | 4 | describe('floatAs32Binary', () => { 5 | it('should create a binary representation of the floating numbers', () => { 6 | for (let testCaseIndex = 0; testCaseIndex < testCases32Bits.length; testCaseIndex += 1) { 7 | const [decimal, binary] = testCases32Bits[testCaseIndex]; 8 | expect(floatAs32BinaryString(decimal)).toBe(binary); 9 | } 10 | }); 11 | }); 12 | 13 | describe('floatAs64Binary', () => { 14 | it('should create a binary representation of the floating numbers', () => { 15 | for (let testCaseIndex = 0; testCaseIndex < testCases64Bits.length; testCaseIndex += 1) { 16 | const [decimal, binary] = testCases64Bits[testCaseIndex]; 17 | expect(floatAs64BinaryString(decimal)).toBe(binary); 18 | } 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/algorithms/math/binary-floating-point/images/02-half-precision-floating-point-number-explained.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hahuyhungdev/javascript-algorithms-and-data-structure/158bde4ec2043e6f97ef12186b28b6f12d0f5621/src/algorithms/math/binary-floating-point/images/02-half-precision-floating-point-number-explained.png -------------------------------------------------------------------------------- /src/algorithms/math/binary-floating-point/images/03-scientific-notation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hahuyhungdev/javascript-algorithms-and-data-structure/158bde4ec2043e6f97ef12186b28b6f12d0f5621/src/algorithms/math/binary-floating-point/images/03-scientific-notation.png -------------------------------------------------------------------------------- /src/algorithms/math/bits/__test__/bitLength.test.js: -------------------------------------------------------------------------------- 1 | import bitLength from '../bitLength'; 2 | 3 | describe('bitLength', () => { 4 | it('should calculate number of bits that the number is consists of', () => { 5 | expect(bitLength(0b0)).toBe(0); 6 | expect(bitLength(0b1)).toBe(1); 7 | expect(bitLength(0b01)).toBe(1); 8 | expect(bitLength(0b101)).toBe(3); 9 | expect(bitLength(0b0101)).toBe(3); 10 | expect(bitLength(0b10101)).toBe(5); 11 | expect(bitLength(0b11110101)).toBe(8); 12 | expect(bitLength(0b00011110101)).toBe(8); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/__test__/bitsDiff.test.js: -------------------------------------------------------------------------------- 1 | import bitsDiff from '../bitsDiff'; 2 | 3 | describe('bitsDiff', () => { 4 | it('should calculate bits difference between two numbers', () => { 5 | expect(bitsDiff(0, 0)).toBe(0); 6 | expect(bitsDiff(1, 1)).toBe(0); 7 | expect(bitsDiff(124, 124)).toBe(0); 8 | expect(bitsDiff(0, 1)).toBe(1); 9 | expect(bitsDiff(1, 0)).toBe(1); 10 | expect(bitsDiff(1, 2)).toBe(2); 11 | expect(bitsDiff(1, 3)).toBe(1); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/__test__/clearBit.test.js: -------------------------------------------------------------------------------- 1 | import clearBit from '../clearBit'; 2 | 3 | describe('clearBit', () => { 4 | it('should clear bit at specific position', () => { 5 | // 1 = 0b0001 6 | expect(clearBit(1, 0)).toBe(0); 7 | expect(clearBit(1, 1)).toBe(1); 8 | expect(clearBit(1, 2)).toBe(1); 9 | 10 | // 10 = 0b1010 11 | expect(clearBit(10, 0)).toBe(10); 12 | expect(clearBit(10, 1)).toBe(8); 13 | expect(clearBit(10, 3)).toBe(2); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/__test__/countSetBits.test.js: -------------------------------------------------------------------------------- 1 | import countSetBits from '../countSetBits'; 2 | 3 | describe('countSetBits', () => { 4 | it('should return number of set bits', () => { 5 | expect(countSetBits(0)).toBe(0); 6 | expect(countSetBits(1)).toBe(1); 7 | expect(countSetBits(2)).toBe(1); 8 | expect(countSetBits(3)).toBe(2); 9 | expect(countSetBits(4)).toBe(1); 10 | expect(countSetBits(5)).toBe(2); 11 | expect(countSetBits(21)).toBe(3); 12 | expect(countSetBits(255)).toBe(8); 13 | expect(countSetBits(1023)).toBe(10); 14 | expect(countSetBits(-1)).toBe(32); 15 | expect(countSetBits(-21)).toBe(30); 16 | expect(countSetBits(-255)).toBe(25); 17 | expect(countSetBits(-1023)).toBe(23); 18 | expect(countSetBits(-4294967296)).toBe(0); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/__test__/divideByTwo.test.js: -------------------------------------------------------------------------------- 1 | import divideByTwo from '../divideByTwo'; 2 | 3 | describe('divideByTwo', () => { 4 | it('should divide numbers by two using bitwise operations', () => { 5 | expect(divideByTwo(0)).toBe(0); 6 | expect(divideByTwo(1)).toBe(0); 7 | expect(divideByTwo(3)).toBe(1); 8 | expect(divideByTwo(10)).toBe(5); 9 | expect(divideByTwo(17)).toBe(8); 10 | expect(divideByTwo(125)).toBe(62); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/__test__/fullAdder.test.js: -------------------------------------------------------------------------------- 1 | import fullAdder from '../fullAdder'; 2 | 3 | describe('fullAdder', () => { 4 | it('should add up two numbers', () => { 5 | expect(fullAdder(0, 0)).toBe(0); 6 | expect(fullAdder(2, 0)).toBe(2); 7 | expect(fullAdder(0, 2)).toBe(2); 8 | expect(fullAdder(1, 2)).toBe(3); 9 | expect(fullAdder(2, 1)).toBe(3); 10 | expect(fullAdder(6, 6)).toBe(12); 11 | expect(fullAdder(-2, 4)).toBe(2); 12 | expect(fullAdder(4, -2)).toBe(2); 13 | expect(fullAdder(-4, -4)).toBe(-8); 14 | expect(fullAdder(4, -5)).toBe(-1); 15 | expect(fullAdder(2, 121)).toBe(123); 16 | expect(fullAdder(121, 2)).toBe(123); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/__test__/getBit.test.js: -------------------------------------------------------------------------------- 1 | import getBit from '../getBit'; 2 | 3 | describe('getBit', () => { 4 | it('should get bit at specific position', () => { 5 | // 1 = 0b0001 6 | expect(getBit(1, 0)).toBe(1); 7 | expect(getBit(1, 1)).toBe(0); 8 | 9 | // 2 = 0b0010 10 | expect(getBit(2, 0)).toBe(0); 11 | expect(getBit(2, 1)).toBe(1); 12 | 13 | // 3 = 0b0011 14 | expect(getBit(3, 0)).toBe(1); 15 | expect(getBit(3, 1)).toBe(1); 16 | 17 | // 10 = 0b1010 18 | expect(getBit(10, 0)).toBe(0); 19 | expect(getBit(10, 1)).toBe(1); 20 | expect(getBit(10, 2)).toBe(0); 21 | expect(getBit(10, 3)).toBe(1); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/__test__/isEven.test.js: -------------------------------------------------------------------------------- 1 | import isEven from '../isEven'; 2 | 3 | describe('isEven', () => { 4 | it('should detect if a number is even', () => { 5 | expect(isEven(0)).toBe(true); 6 | expect(isEven(2)).toBe(true); 7 | expect(isEven(-2)).toBe(true); 8 | expect(isEven(1)).toBe(false); 9 | expect(isEven(-1)).toBe(false); 10 | expect(isEven(-3)).toBe(false); 11 | expect(isEven(3)).toBe(false); 12 | expect(isEven(8)).toBe(true); 13 | expect(isEven(9)).toBe(false); 14 | expect(isEven(121)).toBe(false); 15 | expect(isEven(122)).toBe(true); 16 | expect(isEven(1201)).toBe(false); 17 | expect(isEven(1202)).toBe(true); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/__test__/isPositive.test.js: -------------------------------------------------------------------------------- 1 | import isPositive from '../isPositive'; 2 | 3 | describe('isPositive', () => { 4 | it('should detect if a number is positive', () => { 5 | expect(isPositive(1)).toBe(true); 6 | expect(isPositive(2)).toBe(true); 7 | expect(isPositive(3)).toBe(true); 8 | expect(isPositive(5665)).toBe(true); 9 | expect(isPositive(56644325)).toBe(true); 10 | 11 | expect(isPositive(0)).toBe(false); 12 | expect(isPositive(-0)).toBe(false); 13 | expect(isPositive(-1)).toBe(false); 14 | expect(isPositive(-2)).toBe(false); 15 | expect(isPositive(-126)).toBe(false); 16 | expect(isPositive(-5665)).toBe(false); 17 | expect(isPositive(-56644325)).toBe(false); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/__test__/isPowerOfTwo.test.js: -------------------------------------------------------------------------------- 1 | import isPowerOfTwo from '../isPowerOfTwo'; 2 | 3 | describe('isPowerOfTwo', () => { 4 | it('should detect if the number is power of two', () => { 5 | expect(isPowerOfTwo(1)).toBe(true); 6 | expect(isPowerOfTwo(2)).toBe(true); 7 | expect(isPowerOfTwo(3)).toBe(false); 8 | expect(isPowerOfTwo(4)).toBe(true); 9 | expect(isPowerOfTwo(5)).toBe(false); 10 | expect(isPowerOfTwo(6)).toBe(false); 11 | expect(isPowerOfTwo(7)).toBe(false); 12 | expect(isPowerOfTwo(8)).toBe(true); 13 | expect(isPowerOfTwo(9)).toBe(false); 14 | expect(isPowerOfTwo(16)).toBe(true); 15 | expect(isPowerOfTwo(23)).toBe(false); 16 | expect(isPowerOfTwo(32)).toBe(true); 17 | expect(isPowerOfTwo(127)).toBe(false); 18 | expect(isPowerOfTwo(128)).toBe(true); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/__test__/multiply.test.js: -------------------------------------------------------------------------------- 1 | import multiply from '../multiply'; 2 | 3 | describe('multiply', () => { 4 | it('should multiply two numbers', () => { 5 | expect(multiply(0, 0)).toBe(0); 6 | expect(multiply(2, 0)).toBe(0); 7 | expect(multiply(0, 2)).toBe(0); 8 | expect(multiply(1, 2)).toBe(2); 9 | expect(multiply(2, 1)).toBe(2); 10 | expect(multiply(6, 6)).toBe(36); 11 | expect(multiply(-2, 4)).toBe(-8); 12 | expect(multiply(4, -2)).toBe(-8); 13 | expect(multiply(-4, -4)).toBe(16); 14 | expect(multiply(4, -5)).toBe(-20); 15 | expect(multiply(2, 121)).toBe(242); 16 | expect(multiply(121, 2)).toBe(242); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/__test__/multiplyByTwo.test.js: -------------------------------------------------------------------------------- 1 | import multiplyByTwo from '../multiplyByTwo'; 2 | 3 | describe('multiplyByTwo', () => { 4 | it('should multiply numbers by two using bitwise operations', () => { 5 | expect(multiplyByTwo(0)).toBe(0); 6 | expect(multiplyByTwo(1)).toBe(2); 7 | expect(multiplyByTwo(3)).toBe(6); 8 | expect(multiplyByTwo(10)).toBe(20); 9 | expect(multiplyByTwo(17)).toBe(34); 10 | expect(multiplyByTwo(125)).toBe(250); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/__test__/multiplyUnsigned.test.js: -------------------------------------------------------------------------------- 1 | import multiplyUnsigned from '../multiplyUnsigned'; 2 | 3 | describe('multiplyUnsigned', () => { 4 | it('should multiply two unsigned numbers', () => { 5 | expect(multiplyUnsigned(0, 2)).toBe(0); 6 | expect(multiplyUnsigned(2, 0)).toBe(0); 7 | expect(multiplyUnsigned(1, 1)).toBe(1); 8 | expect(multiplyUnsigned(1, 2)).toBe(2); 9 | expect(multiplyUnsigned(2, 7)).toBe(14); 10 | expect(multiplyUnsigned(7, 2)).toBe(14); 11 | expect(multiplyUnsigned(30, 2)).toBe(60); 12 | expect(multiplyUnsigned(17, 34)).toBe(578); 13 | expect(multiplyUnsigned(170, 2340)).toBe(397800); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/__test__/setBit.test.js: -------------------------------------------------------------------------------- 1 | import setBit from '../setBit'; 2 | 3 | describe('setBit', () => { 4 | it('should set bit at specific position', () => { 5 | // 1 = 0b0001 6 | expect(setBit(1, 0)).toBe(1); 7 | expect(setBit(1, 1)).toBe(3); 8 | expect(setBit(1, 2)).toBe(5); 9 | 10 | // 10 = 0b1010 11 | expect(setBit(10, 0)).toBe(11); 12 | expect(setBit(10, 1)).toBe(10); 13 | expect(setBit(10, 2)).toBe(14); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/__test__/switchSign.test.js: -------------------------------------------------------------------------------- 1 | import switchSign from '../switchSign'; 2 | 3 | describe('switchSign', () => { 4 | it('should switch the sign of the number using twos complement approach', () => { 5 | expect(switchSign(0)).toBe(0); 6 | expect(switchSign(1)).toBe(-1); 7 | expect(switchSign(-1)).toBe(1); 8 | expect(switchSign(32)).toBe(-32); 9 | expect(switchSign(-32)).toBe(32); 10 | expect(switchSign(23)).toBe(-23); 11 | expect(switchSign(-23)).toBe(23); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/__test__/updateBit.test.js: -------------------------------------------------------------------------------- 1 | import updateBit from '../updateBit'; 2 | 3 | describe('updateBit', () => { 4 | it('should update bit at specific position', () => { 5 | // 1 = 0b0001 6 | expect(updateBit(1, 0, 1)).toBe(1); 7 | expect(updateBit(1, 0, 0)).toBe(0); 8 | expect(updateBit(1, 1, 1)).toBe(3); 9 | expect(updateBit(1, 2, 1)).toBe(5); 10 | 11 | // 10 = 0b1010 12 | expect(updateBit(10, 0, 1)).toBe(11); 13 | expect(updateBit(10, 0, 0)).toBe(10); 14 | expect(updateBit(10, 1, 1)).toBe(10); 15 | expect(updateBit(10, 1, 0)).toBe(8); 16 | expect(updateBit(10, 2, 1)).toBe(14); 17 | expect(updateBit(10, 2, 0)).toBe(10); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/bitLength.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Trả về số bit được sử dụng trong biểu diễn nhị phân của number. 3 | * 4 | * @param {number} number 5 | * @return {number} 6 | */ 7 | export default function bitLength(number) { 8 | let bitsCounter = 0; 9 | 10 | while ((1 << bitsCounter) <= number) { 11 | bitsCounter += 1; 12 | } 13 | 14 | return bitsCounter; 15 | } 16 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/bitsDiff.js: -------------------------------------------------------------------------------- 1 | import countSetBits from './countSetBits'; 2 | 3 | /** 4 | * Đếm số lượng bit cần để thay đổi thứ tự chuỗi bit 5 | * từ numberA sang numberB 6 | * 7 | * @param {number} numberA 8 | * @param {number} numberB 9 | * @return {number} 10 | */ 11 | export default function bitsDiff(numberA, numberB) { 12 | return countSetBits(numberA ^ numberB); 13 | } 14 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/clearBit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} number 3 | * @param {number} bitPosition - zero based. 4 | * @return {number} 5 | */ 6 | export default function clearBit(number, bitPosition) { 7 | const mask = ~(1 << bitPosition); 8 | 9 | return number & mask; 10 | } 11 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/countSetBits.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} originalNumber 3 | * @return {number} 4 | */ 5 | export default function countSetBits(originalNumber) { 6 | let setBitsCount = 0; 7 | let number = originalNumber; 8 | 9 | while (number) { 10 | // Thêm bit cuối cùng vào tổng số bit có giá trị. 11 | setBitsCount += number & 1; 12 | 13 | // Chuyển số sang phải một bit để kiểm tra các bit khác. 14 | number >>>= 1; 15 | } 16 | 17 | return setBitsCount; 18 | } 19 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/divideByTwo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} number 3 | * @return {number} 4 | */ 5 | export default function divideByTwo(number) { 6 | return number >> 1; 7 | } 8 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/getBit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {number} number 4 | * @param {number} bitPosition - zero based. 5 | * @returns {number} 6 | */ 7 | export default function getBit(number, bitPosition) { 8 | return (number >> bitPosition) & 1; 9 | } -------------------------------------------------------------------------------- /src/algorithms/math/bits/isEven.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} number 3 | * @return {boolean} 4 | */ 5 | export default function isEven(number) { 6 | return (number & 1) === 0; 7 | } 8 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/isPositive.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} number - số nguyên 32-bit. 3 | * @return {boolean} 4 | */ 5 | export default function isPositive(number) { 6 | // Số 0 không phải số âm cũng không phải số dương. 7 | if (number === 0) { 8 | return false; 9 | } 10 | 11 | // Bit thứ 32 sẽ quyết định một số là số âm hay dương. 12 | return ((number >> 31) & 1) === 0; 13 | } 14 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/isPowerOfTwo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} number 3 | * @return bool 4 | */ 5 | export default function isPowerOfTwo(number) { 6 | return (number & (number - 1)) === 0; 7 | } 8 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/multiply.js: -------------------------------------------------------------------------------- 1 | import multiplyByTwo from './multiplyByTwo'; 2 | import divideByTwo from './divideByTwo'; 3 | import isEven from './isEven'; 4 | import isPositive from './isPositive'; 5 | 6 | /** 7 | * Phép nhân hai số nguyên có dấu sử dụng toán tử bit. 8 | * 9 | * Nếu một trong a và b hoặc cả hai là không thì: 10 | * multiply(a, b) = 0 11 | * 12 | * Nếu b là số chẵn: 13 | * multiply(a, b) = multiply(2a, b/2) 14 | * 15 | * Nếu b là số lẻ và nguyên dương: 16 | * multiply(a, b) = multiply(2a, (b-1)/2) + a 17 | * 18 | * Nếu b là số lẻ và nguyên âm: 19 | * multiply(a, b) = multiply(2a, (b+1)/2) - a 20 | * 21 | * Độ phức tạp thời gian: O(log b) 22 | * 23 | * @param {number} a 24 | * @param {number} b 25 | * @return {number} 26 | */ 27 | export default function multiply(a, b) { 28 | // Nếu một trong a và b hoặc cả hai là không thì: 29 | if (b === 0 || a === 0) { 30 | return 0; 31 | } 32 | 33 | // Nếu không, chúng ta sẽ có ba trường hợp khác nhau được mô tả ở trên. 34 | const multiplyByOddPositive = () => multiply(multiplyByTwo(a), divideByTwo(b - 1)) + a; 35 | const multiplyByOddNegative = () => multiply(multiplyByTwo(a), divideByTwo(b + 1)) - a; 36 | 37 | const multiplyByEven = () => multiply(multiplyByTwo(a), divideByTwo(b)); 38 | const multiplyByOdd = () => (isPositive(b) ? multiplyByOddPositive() : multiplyByOddNegative()); 39 | 40 | return isEven(b) ? multiplyByEven() : multiplyByOdd(); 41 | } 42 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/multiplyByTwo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} number 3 | * @return {number} 4 | */ 5 | export default function multiplyByTwo(number) { 6 | return number << 1; 7 | } 8 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/multiplyUnsigned.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Nhân hai số không dấu sử dụng toán tử bit. 3 | * 4 | * Ý tưởng chính cho phép nhân bit này là chia các số thành tổng các luỹ thừa của hai : 5 | * 6 | * I.e. 19 = 2^4 + 2^1 + 2^0 7 | * 8 | * Khi ta thực hiện phép nhân `x` với `19`, nó tương đương: 9 | * 10 | * x * 19 = x * 2^4 + x * 2^1 + x * 2^0 11 | * 12 | * Bây giờ chúng ta cần nhớ rằng `x * 2^4` tương đương với việc dịch chuyển `x` sang trái bởi `4` bit (`x << 4`). 13 | * 14 | * @param {number} number1 15 | * @param {number} number2 16 | * @return {number} 17 | */ 18 | export default function multiplyUnsigned(number1, number2) { 19 | let result = 0; 20 | 21 | // Xem number2 là một cấp số nhân của number1. 22 | let multiplier = number2; 23 | 24 | // Chỉ số bit hiện tại của hệ số nhân 25 | let bitIndex = 0; 26 | 27 | // Đi qua tất cả các bit của number2. 28 | while (multiplier !== 0) { 29 | // Kiểm tra bit hiện tại đã có giá trị chưa. 30 | if (multiplier & 1) { 31 | // Trong trường hợp nếu bit ở vị trí bitIndex có giá trị 32 | // đều đó có nghĩa là ta cần nhân number1 với luỹ thừa của bit. 33 | // với chỉ số nhân bitIndex và cộng thêm nó vào kết quả. 34 | result += (number1 << bitIndex); 35 | } 36 | 37 | bitIndex += 1; 38 | multiplier >>= 1; 39 | } 40 | 41 | return result; 42 | } 43 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/setBit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} number 3 | * @param {number} bitPosition - zero based. 4 | * @return {number} 5 | */ 6 | export default function setBit(number, bitPosition) { 7 | return number | (1 << bitPosition); 8 | } 9 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/switchSign.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Chuyển đổi dấu của số bằng cách sử dụng phương pháp "Twos Complement". 3 | * @param {number} number 4 | * @return {number} 5 | */ 6 | export default function switchSign(number) { 7 | return ~number + 1; 8 | } 9 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/updateBit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} number 3 | * @param {number} bitPosition - zero based. 4 | * @param {number} bitValue - 0 or 1. 5 | * @return {number} 6 | */ 7 | export default function updateBit(number, bitPosition, bitValue) { 8 | // Giá trị bit chuẩn hoá. 9 | const bitValueNormalized = bitValue ? 1 : 0; 10 | 11 | // Đảo bit. 12 | const clearMask = ~(1 << bitPosition); 13 | 14 | //Xoá giá trị bit sau đó thiết lập giá trị mới. 15 | return (number & clearMask) | (bitValueNormalized << bitPosition); 16 | } 17 | -------------------------------------------------------------------------------- /src/algorithms/math/euclidean-algorithm/__test__/euclideanAlgorithm.test.js: -------------------------------------------------------------------------------- 1 | import euclideanAlgorithm from '../euclideanAlgorithm'; 2 | 3 | describe('euclideanAlgorithm', () => { 4 | it('should calculate GCD recursively', () => { 5 | expect(euclideanAlgorithm(0, 0)).toBe(0); 6 | expect(euclideanAlgorithm(2, 0)).toBe(2); 7 | expect(euclideanAlgorithm(0, 2)).toBe(2); 8 | expect(euclideanAlgorithm(1, 2)).toBe(1); 9 | expect(euclideanAlgorithm(2, 1)).toBe(1); 10 | expect(euclideanAlgorithm(6, 6)).toBe(6); 11 | expect(euclideanAlgorithm(2, 4)).toBe(2); 12 | expect(euclideanAlgorithm(4, 2)).toBe(2); 13 | expect(euclideanAlgorithm(12, 4)).toBe(4); 14 | expect(euclideanAlgorithm(4, 12)).toBe(4); 15 | expect(euclideanAlgorithm(5, 13)).toBe(1); 16 | expect(euclideanAlgorithm(27, 13)).toBe(1); 17 | expect(euclideanAlgorithm(24, 60)).toBe(12); 18 | expect(euclideanAlgorithm(60, 24)).toBe(12); 19 | expect(euclideanAlgorithm(252, 105)).toBe(21); 20 | expect(euclideanAlgorithm(105, 252)).toBe(21); 21 | expect(euclideanAlgorithm(1071, 462)).toBe(21); 22 | expect(euclideanAlgorithm(462, 1071)).toBe(21); 23 | expect(euclideanAlgorithm(462, -1071)).toBe(21); 24 | expect(euclideanAlgorithm(-462, -1071)).toBe(21); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/algorithms/math/euclidean-algorithm/__test__/euclideanAlgorithmIterative.test.js: -------------------------------------------------------------------------------- 1 | import euclideanAlgorithmIterative from '../euclideanAlgorithmIterative'; 2 | 3 | describe('euclideanAlgorithmIterative', () => { 4 | it('should calculate GCD iteratively', () => { 5 | expect(euclideanAlgorithmIterative(0, 0)).toBe(0); 6 | expect(euclideanAlgorithmIterative(2, 0)).toBe(2); 7 | expect(euclideanAlgorithmIterative(0, 2)).toBe(2); 8 | expect(euclideanAlgorithmIterative(1, 2)).toBe(1); 9 | expect(euclideanAlgorithmIterative(2, 1)).toBe(1); 10 | expect(euclideanAlgorithmIterative(6, 6)).toBe(6); 11 | expect(euclideanAlgorithmIterative(2, 4)).toBe(2); 12 | expect(euclideanAlgorithmIterative(4, 2)).toBe(2); 13 | expect(euclideanAlgorithmIterative(12, 4)).toBe(4); 14 | expect(euclideanAlgorithmIterative(4, 12)).toBe(4); 15 | expect(euclideanAlgorithmIterative(5, 13)).toBe(1); 16 | expect(euclideanAlgorithmIterative(27, 13)).toBe(1); 17 | expect(euclideanAlgorithmIterative(24, 60)).toBe(12); 18 | expect(euclideanAlgorithmIterative(60, 24)).toBe(12); 19 | expect(euclideanAlgorithmIterative(252, 105)).toBe(21); 20 | expect(euclideanAlgorithmIterative(105, 252)).toBe(21); 21 | expect(euclideanAlgorithmIterative(1071, 462)).toBe(21); 22 | expect(euclideanAlgorithmIterative(462, 1071)).toBe(21); 23 | expect(euclideanAlgorithmIterative(462, -1071)).toBe(21); 24 | expect(euclideanAlgorithmIterative(-462, -1071)).toBe(21); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/algorithms/math/euclidean-algorithm/euclideanAlgorithm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Phiên bản đệ quy của Thuật toán Euclid tìm ước số chung lớn nhất (GCD). 3 | * @param {number} originalA 4 | * @param {number} originalB 5 | * @return {number} 6 | */ 7 | export default function euclideanAlgorithm(originalA, originalB) { 8 | // Làm cho số nhập vào luôn dương. 9 | const a = Math.abs(originalA); 10 | const b = Math.abs(originalB); 11 | 12 | // Để làm cho thuật toán hoạt động nhanh hơn thay vì trừ một số cho số kia 13 | // chúng ta có thể sử dụng thao tác chia lấy phần dư. 14 | return (b === 0) ? a : euclideanAlgorithm(b, a % b); 15 | } 16 | -------------------------------------------------------------------------------- /src/algorithms/math/euclidean-algorithm/euclideanAlgorithmIterative.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Phiên bản dùng vòng lặp của Thuật toán Euclid tìm ước số chung lớn nhất (GCD). 3 | * @param {number} originalA 4 | * @param {number} originalB 5 | * @return {number} 6 | */ 7 | export default function euclideanAlgorithmIterative(originalA, originalB) { 8 | // Làm cho số nhập vào luôn dương. 9 | let a = Math.abs(originalA); 10 | let b = Math.abs(originalB); 11 | 12 | // Trừ một số bởi số lớn hơn cho đến khi chúng bằng nhau. 13 | // Kết quả sẽ là GCD thì chúng sẽ thoát khỏi vòng lặp. Hoặc là chúng bằng 0 14 | while (a && b && a !== b) { 15 | [a, b] = a > b ? [a - b, b] : [a, b - a]; 16 | } 17 | 18 | // Trả về số khác 0 từ phép trừ cuối cùng (đấy là GCD). 19 | return a || b; 20 | } 21 | -------------------------------------------------------------------------------- /src/algorithms/math/euclidean-distance/__tests__/euclideanDistance.test.js: -------------------------------------------------------------------------------- 1 | import euclideanDistance from '../euclideanDistance'; 2 | 3 | describe('euclideanDistance', () => { 4 | it('should calculate euclidean distance between vectors', () => { 5 | expect(euclideanDistance([[1]], [[2]])).toEqual(1); 6 | expect(euclideanDistance([[2]], [[1]])).toEqual(1); 7 | expect(euclideanDistance([[5, 8]], [[7, 3]])).toEqual(5.39); 8 | expect(euclideanDistance([[5], [8]], [[7], [3]])).toEqual(5.39); 9 | expect(euclideanDistance([[8, 2, 6]], [[3, 5, 7]])).toEqual(5.92); 10 | expect(euclideanDistance([[8], [2], [6]], [[3], [5], [7]])).toEqual(5.92); 11 | expect(euclideanDistance([[[8]], [[2]], [[6]]], [[[3]], [[5]], [[7]]])).toEqual(5.92); 12 | }); 13 | 14 | it('should throw an error in case if two matrices are of different shapes', () => { 15 | expect(() => euclideanDistance([[1]], [[[2]]])).toThrowError( 16 | 'Matrices have different dimensions', 17 | ); 18 | 19 | expect(() => euclideanDistance([[1]], [[2, 3]])).toThrowError( 20 | 'Matrices have different shapes', 21 | ); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/algorithms/math/euclidean-distance/euclideanDistance.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('../matrix/Matrix.js').Matrix} Matrix 3 | */ 4 | 5 | import * as mtrx from '../matrix/Matrix'; 6 | 7 | /** 8 | * Tính toán khoảng cách euclid giữa hai ma trận. 9 | * 10 | * @param {Matrix} a 11 | * @param {Matrix} b 12 | * @returns {number} 13 | * @trows {Error} 14 | */ 15 | const euclideanDistance = (a, b) => { 16 | mtrx.validateSameShape(a, b); 17 | 18 | let squaresTotal = 0; 19 | 20 | mtrx.walk(a, (indices, aCellValue) => { 21 | const bCellValue = mtrx.getCellAtIndex(b, indices); 22 | squaresTotal += (aCellValue - bCellValue) ** 2; 23 | }); 24 | 25 | return Number(Math.sqrt(squaresTotal).toFixed(2)); 26 | }; 27 | 28 | export default euclideanDistance; 29 | -------------------------------------------------------------------------------- /src/algorithms/math/factorial/README.md: -------------------------------------------------------------------------------- 1 | # Giai thừa 2 | 3 | Trong toán học, giai thừa của một số nguyên không âm `n` được ký hiệu là `n!`, có giá trị bằng tích các số nguyên dương nhỏ hơn hoặc bằng `n`. Ví dụ : 4 | 5 | ``` 6 | 5! = 5 * 4 * 3 * 2 * 1 = 120 7 | ``` 8 | 9 | | n | n! | 10 | | --- | ----------------: | 11 | | 0 | 1 | 12 | | 1 | 1 | 13 | | 2 | 2 | 14 | | 3 | 6 | 15 | | 4 | 24 | 16 | | 5 | 120 | 17 | | 6 | 720 | 18 | | 7 | 5 040 | 19 | | 8 | 40 320 | 20 | | 9 | 362 880 | 21 | | 10 | 3 628 800 | 22 | | 11 | 39 916 800 | 23 | | 12 | 479 001 600 | 24 | | 13 | 6 227 020 800 | 25 | | 14 | 87 178 291 200 | 26 | | 15 | 1 307 674 368 000 | 27 | 28 | ## Liên kết 29 | 30 | [Wikipedia](https://en.wikipedia.org/wiki/Factorial) -------------------------------------------------------------------------------- /src/algorithms/math/factorial/__test__/factorial.test.js: -------------------------------------------------------------------------------- 1 | import factorial from '../factorial'; 2 | 3 | describe('factorial', () => { 4 | it('should calculate factorial', () => { 5 | expect(factorial(0)).toBe(1); 6 | expect(factorial(1)).toBe(1); 7 | expect(factorial(5)).toBe(120); 8 | expect(factorial(8)).toBe(40320); 9 | expect(factorial(10)).toBe(3628800); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/algorithms/math/factorial/__test__/factorialRecursive.test.js: -------------------------------------------------------------------------------- 1 | import factorialRecursive from '../factorialRecursive'; 2 | 3 | describe('factorialRecursive', () => { 4 | it('should calculate factorial', () => { 5 | expect(factorialRecursive(0)).toBe(1); 6 | expect(factorialRecursive(1)).toBe(1); 7 | expect(factorialRecursive(5)).toBe(120); 8 | expect(factorialRecursive(8)).toBe(40320); 9 | expect(factorialRecursive(10)).toBe(3628800); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/algorithms/math/factorial/factorial.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} number 3 | * @return {number} 4 | */ 5 | export default function factorial(number) { 6 | let result = 1; 7 | 8 | for (let i = 2; i <= number; i += 1) { 9 | result *= i; 10 | } 11 | 12 | return result; 13 | } 14 | -------------------------------------------------------------------------------- /src/algorithms/math/factorial/factorialRecursive.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} number 3 | * @return {number} 4 | */ 5 | export default function factorialRecursive(number) { 6 | return number > 1 ? number * factorialRecursive(number - 1) : 1; 7 | } 8 | -------------------------------------------------------------------------------- /src/algorithms/math/fast-powering/README.md: -------------------------------------------------------------------------------- 1 | ## Giải thuật tính nhanh luỹ thừa 2 | 3 | **Luỹ thừa của một số** là số lần một số được nhân với chính nó. Được viết dưới dạng hai số, cơ số và số mũ. 4 | 5 | Số mũ thường được hiển thị dưới dạng chỉ số trên ở bên phải của cơ số 6 | 7 | ![Power](https://www.mathsisfun.com/algebra/images/exponent-8-2.svg) 8 | 9 | ## Độ phức tạp của giải thuật thông thường 10 | 11 | Làm thế nào để tìm `a` luỹ thừa `b` ? 12 | 13 | Ta sẽ nhân `a` với chính nó `b` lần. Có nghĩa là `a^b = a * a * a * a ... * a` . 14 | 15 | Thao tác này mất `O(n)` thời gian vì chúng ta phải nhân `n` lần. 16 | 17 | ## Giải thuật tính nhanh 18 | 19 | Chúng ta có thể làm tốt hơn giải thuật thông thường ? Thực ta ta có thể chỉ mật `O(log(n))` thời gian với giải thuật này. 20 | 21 | Giải thuật sử dụng phương pháp chia để trị và làm việc với hai số nguyên dương `X` và `Y`. Ý tưởng như sao : 22 | 23 | Với `Y` **chẵn** : 24 | 25 | ```text 26 | X^Y = X^(Y/2) * X^(Y/2) 27 | ``` 28 | Với `Y` **lẻ** : 29 | 30 | ```text 31 | X^Y = X^(Y//2) * X^(Y//2) * X 32 | trong đó Y//2 là kết quả của phép chia Y cho 2 không có dư. 33 | ``` 34 | 35 | **Ví dụ** 36 | ```text 37 | 2^4 = (2 * 2) * (2 * 2) = (2^2) * (2^2) 38 | ``` 39 | 40 | ```text 41 | 2^5 = (2 * 2) * (2 * 2) * 2 = (2^2) * (2^2) * (2) 42 | ``` 43 | 44 | **Độ phức tạp thời gian** 45 | 46 | Vì sau mỗi lần lặp chúng ta chia lũy thừa ra một nửa nên chúng ta sẽ gọi hàm đệ quy `log (n)` lần. Điều này khiến độ phức tạp về thời gian của thuật toán được giảm xuống: 47 | 48 | ```text 49 | O(log(n)) 50 | ``` 51 | 52 | ## Liên kết 53 | 54 | - [YouTube](https://www.youtube.com/watch?v=LUWavfN9zEo&index=80&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8&t=0s) 55 | - [Wikipedia](https://en.wikipedia.org/wiki/Exponentiation_by_squaring) -------------------------------------------------------------------------------- /src/algorithms/math/fast-powering/__test__/fastPowering.test.js: -------------------------------------------------------------------------------- 1 | import fastPowering from '../fastPowering'; 2 | 3 | describe('fastPowering', () => { 4 | it('should compute power in log(n) time', () => { 5 | expect(fastPowering(1, 1)).toBe(1); 6 | expect(fastPowering(2, 0)).toBe(1); 7 | expect(fastPowering(2, 2)).toBe(4); 8 | expect(fastPowering(2, 3)).toBe(8); 9 | expect(fastPowering(2, 4)).toBe(16); 10 | expect(fastPowering(2, 5)).toBe(32); 11 | expect(fastPowering(2, 6)).toBe(64); 12 | expect(fastPowering(2, 7)).toBe(128); 13 | expect(fastPowering(2, 8)).toBe(256); 14 | expect(fastPowering(3, 4)).toBe(81); 15 | expect(fastPowering(190, 2)).toBe(36100); 16 | expect(fastPowering(11, 5)).toBe(161051); 17 | expect(fastPowering(13, 11)).toBe(1792160394037); 18 | expect(fastPowering(9, 16)).toBe(1853020188851841); 19 | expect(fastPowering(16, 16)).toBe(18446744073709552000); 20 | expect(fastPowering(7, 21)).toBe(558545864083284000); 21 | expect(fastPowering(100, 9)).toBe(1000000000000000000); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/algorithms/math/fast-powering/fastPowering.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Giải thuật tính nhanh luỹ thừa 3 | * Sử dụng đệ quy để tính luỹ thừa. 4 | * 5 | * Độ phức tạp: log(n) 6 | * 7 | * @param {number} base - Cơ số. 8 | * @param {number} power - Số mũ. 9 | * @return {number} 10 | */ 11 | export default function fastPowering(base, power) { 12 | if (power === 0) { 13 | // Bất kỳ cơ số nào có số mũ là 0 đều bằng 1. 14 | return 1; 15 | } 16 | 17 | if (power % 2 === 0) { 18 | // Nếu số mũ là chẵn. 19 | // Xác định kết quả bằng cách đệ quy chia đôi nó xuống số mũ nhỏ hơn. 20 | // x^8 = x^4 * x^4. 21 | const multiplier = fastPowering(base, power / 2); 22 | return multiplier * multiplier; 23 | } 24 | 25 | // Nếu số mũ là lẻ. 26 | // Xác định kết quả bằng cách đệ quy chia đôi nó xuống số mũ nhỏ hơn. 27 | // x^9 = x^4 * x^4 * x. 28 | const multiplier = fastPowering(base, Math.floor(power / 2)); 29 | return multiplier * multiplier * base; 30 | } 31 | -------------------------------------------------------------------------------- /src/algorithms/math/fibonacci/README.md: -------------------------------------------------------------------------------- 1 | # Dãy Fibonacci 2 | 3 | Trong toán học, các số Fibonacci là các số trong dãy số nguyên được gọi là dãy Fibonacci, và được nhận diên bởi các số trong dãy sẽ có giá trị bằng tổng hai số trước nó : 4 | 5 | `0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...` 6 | 7 | Các hình vuông có độ dài các cạnh theo dãy số Fibonacci 8 | 9 | ![Fibonacci](https://upload.wikimedia.org/wikipedia/commons/d/db/34%2A21-FibonacciBlocks.png) 10 | 11 | Hình xoắn ốc Fibonacci là một đường xoắn ốc tỉ lệ vàng được tạo ra bằng cách vẽ các cung tròn nối các góc đối diện của các hình vuông Fibonacci; [4] kiểu này sử dụng các hình vuông có kích thước 1, 1, 2, 3, 5, 8, 13 và 21 . 12 | 13 | ![Fibonacci Spiral](https://upload.wikimedia.org/wikipedia/commons/2/2e/FibonacciSpiral.svg) 14 | 15 | ## Liên kết 16 | 17 | - [Wikipedia](https://en.wikipedia.org/wiki/Fibonacci_number) -------------------------------------------------------------------------------- /src/algorithms/math/fibonacci/__test__/fibonacci.test.js: -------------------------------------------------------------------------------- 1 | import fibonacci from '../fibonacci'; 2 | 3 | describe('fibonacci', () => { 4 | it('should calculate fibonacci correctly', () => { 5 | expect(fibonacci(1)).toEqual([1]); 6 | expect(fibonacci(2)).toEqual([1, 1]); 7 | expect(fibonacci(3)).toEqual([1, 1, 2]); 8 | expect(fibonacci(4)).toEqual([1, 1, 2, 3]); 9 | expect(fibonacci(5)).toEqual([1, 1, 2, 3, 5]); 10 | expect(fibonacci(6)).toEqual([1, 1, 2, 3, 5, 8]); 11 | expect(fibonacci(7)).toEqual([1, 1, 2, 3, 5, 8, 13]); 12 | expect(fibonacci(8)).toEqual([1, 1, 2, 3, 5, 8, 13, 21]); 13 | expect(fibonacci(9)).toEqual([1, 1, 2, 3, 5, 8, 13, 21, 34]); 14 | expect(fibonacci(10)).toEqual([1, 1, 2, 3, 5, 8, 13, 21, 34, 55]); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/algorithms/math/fibonacci/__test__/fibonacciNth.test.js: -------------------------------------------------------------------------------- 1 | import fibonacciNth from '../fibonacciNth'; 2 | 3 | describe('fibonacciNth', () => { 4 | it('should calculate fibonacci correctly', () => { 5 | expect(fibonacciNth(1)).toBe(1); 6 | expect(fibonacciNth(2)).toBe(1); 7 | expect(fibonacciNth(3)).toBe(2); 8 | expect(fibonacciNth(4)).toBe(3); 9 | expect(fibonacciNth(5)).toBe(5); 10 | expect(fibonacciNth(6)).toBe(8); 11 | expect(fibonacciNth(7)).toBe(13); 12 | expect(fibonacciNth(8)).toBe(21); 13 | expect(fibonacciNth(20)).toBe(6765); 14 | expect(fibonacciNth(30)).toBe(832040); 15 | expect(fibonacciNth(50)).toBe(12586269025); 16 | expect(fibonacciNth(70)).toBe(190392490709135); 17 | expect(fibonacciNth(71)).toBe(308061521170129); 18 | expect(fibonacciNth(72)).toBe(498454011879264); 19 | expect(fibonacciNth(73)).toBe(806515533049393); 20 | expect(fibonacciNth(74)).toBe(1304969544928657); 21 | expect(fibonacciNth(75)).toBe(2111485077978050); 22 | expect(fibonacciNth(80)).toBe(23416728348467685); 23 | expect(fibonacciNth(90)).toBe(2880067194370816120); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/algorithms/math/fibonacci/__test__/fibonacciNthClosedForm.test.js: -------------------------------------------------------------------------------- 1 | import fibonacciNthClosedForm from '../fibonacciNthClosedForm'; 2 | 3 | describe('fibonacciClosedForm', () => { 4 | it('should throw an error when trying to calculate fibonacci for not allowed positions', () => { 5 | const calculateFibonacciForNotAllowedPosition = () => { 6 | fibonacciNthClosedForm(76); 7 | }; 8 | 9 | expect(calculateFibonacciForNotAllowedPosition).toThrow(); 10 | }); 11 | 12 | it('should calculate fibonacci correctly', () => { 13 | expect(fibonacciNthClosedForm(1)).toBe(1); 14 | expect(fibonacciNthClosedForm(2)).toBe(1); 15 | expect(fibonacciNthClosedForm(3)).toBe(2); 16 | expect(fibonacciNthClosedForm(4)).toBe(3); 17 | expect(fibonacciNthClosedForm(5)).toBe(5); 18 | expect(fibonacciNthClosedForm(6)).toBe(8); 19 | expect(fibonacciNthClosedForm(7)).toBe(13); 20 | expect(fibonacciNthClosedForm(8)).toBe(21); 21 | expect(fibonacciNthClosedForm(20)).toBe(6765); 22 | expect(fibonacciNthClosedForm(30)).toBe(832040); 23 | expect(fibonacciNthClosedForm(50)).toBe(12586269025); 24 | expect(fibonacciNthClosedForm(70)).toBe(190392490709135); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/algorithms/math/fibonacci/fibonacci.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Trả về một chuỗi fibonacci dưới dạng một mảng. 3 | * 4 | * @param n 5 | * @return {number[]} 6 | */ 7 | export default function fibonacci(n) { 8 | const fibSequence = [1]; 9 | 10 | let currentValue = 1; 11 | let previousValue = 0; 12 | 13 | if (n === 1) { 14 | return fibSequence; 15 | } 16 | 17 | let iterationsCounter = n - 1; 18 | 19 | while (iterationsCounter) { 20 | currentValue += previousValue; 21 | previousValue = currentValue - previousValue; 22 | 23 | fibSequence.push(currentValue); 24 | 25 | iterationsCounter -= 1; 26 | } 27 | 28 | return fibSequence; 29 | } 30 | -------------------------------------------------------------------------------- /src/algorithms/math/fibonacci/fibonacciNth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tính giá trị số Fibonacci tại vị trí cụ thể bằng Quy hoạch động. 3 | * 4 | * @param n 5 | * @return {number} 6 | */ 7 | export default function fibonacciNth(n) { 8 | let currentValue = 1; 9 | let previousValue = 0; 10 | 11 | if (n === 1) { 12 | return 1; 13 | } 14 | 15 | let iterationsCounter = n - 1; 16 | 17 | while (iterationsCounter) { 18 | currentValue += previousValue; 19 | previousValue = currentValue - previousValue; 20 | 21 | iterationsCounter -= 1; 22 | } 23 | 24 | return currentValue; 25 | } 26 | -------------------------------------------------------------------------------- /src/algorithms/math/fibonacci/fibonacciNthClosedForm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tính toán số fibonacci tại vị trí cụ thể bằng cách sử dụng hàm dạng đóng (công thức của Binet). 3 | * @see: https://en.wikipedia.org/wiki/Fibonacci_number#Closed-form_expression 4 | * 5 | * @param {number} position - Vị trí trong dãy Fibonacci (từ 1 đến 70). 6 | * @return {number} 7 | */ 8 | export default function fibonacciClosedForm(position) { 9 | const topMaxValidPosition = 70; 10 | 11 | // Kiểm tra vị trí. 12 | if (position < 1 || position > topMaxValidPosition) { 13 | throw new Error(`Can't handle position smaller than 1 or greater than ${topMaxValidPosition}`); 14 | } 15 | 16 | // Tín √5 để sử dụng lại nó trong các công thức khác. 17 | const sqrt5 = Math.sqrt(5); 18 | // Tính hằng số φ (≈ 1.61803). 19 | const phi = (1 + sqrt5) / 2; 20 | 21 | // Tính số fibonacci bằng công thức Binet. 22 | return Math.floor((phi ** position) / sqrt5 + 0.5); 23 | } 24 | -------------------------------------------------------------------------------- /src/algorithms/math/horner-method/README.md: -------------------------------------------------------------------------------- 1 | # Phương pháp Horner 2 | 3 | Trong toán học, phương pháp Horner (hoặc lược đồ Horner) là một giải thuật biến đổi đa thức. Với phương pháp này, ta có thể biến đổi đa thức chỉ với `n` phép cộng và `n` phép nhân. Do đó, yêu cầu lưu trữ của nó gấp 'n' lần số bit của 'x'. 4 | 5 | Phương pháp Horner được xây dựng trên hằng đẳng thức sau : 6 | 7 | ![Horner's rule](https://wikimedia.org/api/rest_v1/media/math/render/svg/2a576e42d875496f8b0f0dda5ebff7c2415532e4) 8 | 9 | Hằng đẳng thức trên còn gọi là _auy tắc Horner_ 10 | 11 | Để giải hằng đẳng thức bên giải, đới với một `x` cho trước, ta bắt đầu bằng cách lặp lại đa thức từ trong ra ngoài, tích luỹ kết quả rồi lặp lại. Sau lần lặp thứ `n`, với `n` là bậc của đa thức, từ kết quả tích luỹ có được ta có thể biến đổi đa thức. 12 | 13 | **Sử dụng đa thức** 14 | 15 | Giả sử ta có đa thức `P(x)`: 16 | `4 * x^4 + 2 * x^3 + 3 * x^2 + x^1 + 3`, hãy phân tích đa thức tại `Q(x=2)`. 17 | 18 | Ta có thể biểu diễn đa thức bằng mảng `[3, 1, 3, 2, 4]` và lặp lại nó qua từng giá trị để lưu vào một biến tích luỹ như `acc += pow(x=2, index) * array[index]`. Về bản hất mỗi luỹ thừa của phép toán (`pow`) là `n-1` phép nhân. Thế nên, ta có tổng cộng `14` phép toán được thực hiện bao gồm `4` phép cộng, `5` phép nhân và `5` phép luỹ thừa(giả định mỗi luỹ thừa được tính bằng cách lặp lại phép nhân). 19 | 20 | **Sửu dụng quy tắc Horner** 21 | 22 | Với cùng đa thức trên ta sử dụng quy tắc Horner như sau : viết lại đa thức thành `x * (x * (x * (4 * x + 2) + 3) + 1) + 3`, biểu diễn mảng `[4, 2, 3, 1, 3]` để có thể lưu lại vòng lặp đầu tiên dưới dạng `acc = arr[0] * (x=2) + arr[1]`, và kết thúc vòng lặp `acc *= (x=2) + arr[index]`. Cùng một bài toán nhưng với quy tắc Horner ta chỉ cần `10` phép tính bao gồm `4` phép cộng và `4` phép nhân. -------------------------------------------------------------------------------- /src/algorithms/math/horner-method/__test__/classicPolynome.test.js: -------------------------------------------------------------------------------- 1 | import classicPolynome from '../classicPolynome'; 2 | 3 | describe('classicPolynome', () => { 4 | it('should evaluate the polynomial for the specified value of x correctly', () => { 5 | expect(classicPolynome([8], 0.1)).toBe(8); 6 | expect(classicPolynome([2, 4, 2, 5], 0.555)).toBe(7.68400775); 7 | expect(classicPolynome([2, 4, 2, 5], 0.75)).toBe(9.59375); 8 | expect(classicPolynome([1, 1, 1, 1, 1], 1.75)).toBe(20.55078125); 9 | expect(classicPolynome([15, 3.5, 0, 2, 1.42, 0.41], 0.315)).toBe(1.1367300651406251); 10 | expect(classicPolynome([0, 0, 2.77, 1.42, 0.41], 1.35)).toBe(7.375325000000001); 11 | expect(classicPolynome([0, 0, 2.77, 1.42, 2.3311], 1.35)).toBe(9.296425000000001); 12 | expect(classicPolynome([2, 0, 0, 5.757, 5.31412, 12.3213], 3.141)).toBe(697.2731167035034); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/algorithms/math/horner-method/__test__/hornerMethod.test.js: -------------------------------------------------------------------------------- 1 | import hornerMethod from '../hornerMethod'; 2 | import classicPolynome from '../classicPolynome'; 3 | 4 | describe('hornerMethod', () => { 5 | it('should evaluate the polynomial for the specified value of x correctly', () => { 6 | expect(hornerMethod([8], 0.1)).toBe(8); 7 | expect(hornerMethod([2, 4, 2, 5], 0.555)).toBe(7.68400775); 8 | expect(hornerMethod([2, 4, 2, 5], 0.75)).toBe(9.59375); 9 | expect(hornerMethod([1, 1, 1, 1, 1], 1.75)).toBe(20.55078125); 10 | expect(hornerMethod([15, 3.5, 0, 2, 1.42, 0.41], 0.315)).toBe(1.136730065140625); 11 | expect(hornerMethod([0, 0, 2.77, 1.42, 0.41], 1.35)).toBe(7.375325000000001); 12 | expect(hornerMethod([0, 0, 2.77, 1.42, 2.3311], 1.35)).toBe(9.296425000000001); 13 | expect(hornerMethod([2, 0, 0, 5.757, 5.31412, 12.3213], 3.141)).toBe(697.2731167035034); 14 | }); 15 | 16 | it('should evaluate the same polynomial value as classical approach', () => { 17 | expect(hornerMethod([8], 0.1)).toBe(classicPolynome([8], 0.1)); 18 | expect(hornerMethod([2, 4, 2, 5], 0.555)).toBe(classicPolynome([2, 4, 2, 5], 0.555)); 19 | expect(hornerMethod([2, 4, 2, 5], 0.75)).toBe(classicPolynome([2, 4, 2, 5], 0.75)); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/algorithms/math/horner-method/classicPolynome.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Trả về biểu diễn của đa thức tại giá trị cụ thể. 4 | * Sử dụng cách tiếp cận đơn giản với luỹ thừa. 5 | * 6 | * @param {number[]} coefficients - i.e. [4, 3, 2] for (4 * x^2 + 3 * x + 2) 7 | * @param {number} xVal 8 | * @return {number} 9 | */ 10 | export default function classicPolynome(coefficients, xVal) { 11 | return coefficients.reverse().reduce( 12 | (accumulator, currentCoefficient, index) => { 13 | return accumulator + currentCoefficient * (xVal ** index); 14 | }, 15 | 0, 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/algorithms/math/horner-method/hornerMethod.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Trả về biểu diễn của đa thức tại giá trị cụ thể. 3 | * Sử dụng quy tắc Horner. 4 | * 5 | * @param {number[]} coefficients - i.e. [4, 3, 2] for (4 * x^2 + 3 * x + 2) 6 | * @param {number} xVal 7 | * @return {number} 8 | */ 9 | export default function hornerMethod(coefficients, xVal) { 10 | return coefficients.reduce( 11 | (accumulator, currentCoefficient) => { 12 | return accumulator * xVal + currentCoefficient; 13 | }, 14 | 0, 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/algorithms/math/integer-partition/README.md: -------------------------------------------------------------------------------- 1 | # Phân Vùng Số Nguyên 2 | 3 | Trong lý thuyết số và tổ hợp, phân vùng của số nguyên dương `n`, còn gọi là **phân hoạch số nguyên**, là một các viết `n` dưới dạng tổng các số nguyên. 4 | 5 | Hai tổng chỉ khác nhau về thứ tự phần tử nhưng giống về giá trị được xem là cùng một phân hoạch. Ví dụ, `4` có năm phân hoạch như sau: 6 | 7 | ``` 8 | 4 9 | 3 + 1 10 | 2 + 2 11 | 2 + 1 + 1 12 | 1 + 1 + 1 + 1 13 | ``` 14 | 15 | Ở đây `1 + 3` là cùng phân hoạch với `3 + 1` dù thứ tự khác nhau, tương tự `1 + 2 +1` và `1 + 1 + 2` là cùng phân hoạch với `2 + 1 +1`. 16 | 17 | Biểu đồ Young mô tả các phân hoạch của số nguyên dương từ `1` đến `8`. Các phân hoạch được sắp xếp theo đường chéo chính của hình vuông từ dưới lên. 18 | 19 | ![Integer Partition](https://upload.wikimedia.org/wikipedia/commons/d/d8/Ferrer_partitioning_diagrams.svg) 20 | 21 | ## Liên kết 22 | 23 | - [Wikipedia](https://en.wikipedia.org/wiki/Partition_(number_theory)) 24 | - [YouTube](https://www.youtube.com/watch?v=ZaVM057DuzE&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) -------------------------------------------------------------------------------- /src/algorithms/math/integer-partition/__test__/integerPartition.test.js: -------------------------------------------------------------------------------- 1 | import integerPartition from '../integerPartition'; 2 | 3 | describe('integerPartition', () => { 4 | it('should partition the number', () => { 5 | expect(integerPartition(1)).toBe(1); 6 | expect(integerPartition(2)).toBe(2); 7 | expect(integerPartition(3)).toBe(3); 8 | expect(integerPartition(4)).toBe(5); 9 | expect(integerPartition(5)).toBe(7); 10 | expect(integerPartition(6)).toBe(11); 11 | expect(integerPartition(7)).toBe(15); 12 | expect(integerPartition(8)).toBe(22); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/algorithms/math/is-power-of-two/README.md: -------------------------------------------------------------------------------- 1 | # Luỹ thừa của hai 2 | 3 | Cho một số nguyên, viết hàm kiểm tra nó phải luỹ thừa của hai hay không. 4 | 5 | **Giải pháp đơn giản** 6 | 7 | Đơn giản nhất, ta chỉ cần liên tục chia lấy dư cho hai mỗi lần làm như vậy ta cần kiểm tra xem phần dữ có luôn bằng `0` không. Nếu có lần nào nó bằng `1` thì số đấy không phải luỹ thừa của hai. 8 | 9 | **Giải pháp bitwise** 10 | 11 | Luỹ thừa của hai ở dạng nhị phân luôn chỉ có một bit có giá trị là `1`. Trừ khi đấy là số nguyên có dấu (ví dụ: số nguyên có dấu 8-bit có giá trị -128 sẽ là : `10000000`) 12 | 13 | ``` 14 | 1: 0001 15 | 2: 0010 16 | 4: 0100 17 | 8: 1000 18 | ``` 19 | 20 | Vì vậy, sau khi kiểm tra rằng số đấy lớn hơn không, chúng ta có thể sử dụng một thủ thuật bitwise để kiểm tra số đó chỉ có một bit là `1`. 21 | 22 | ``` 23 | number & (number - 1) 24 | ``` 25 | 26 | Ví dụ thao tác với số `8` như sau : 27 | 28 | ``` 29 | 1000 30 | - 0001 31 | ---- 32 | 0111 33 | 34 | 1000 35 | & 0111 36 | ---- 37 | 0000 38 | ``` 39 | 40 | ## Liên kết 41 | 42 | - [GeeksForGeeks](https://www.geeksforgeeks.org/program-to-find-whether-a-no-is-power-of-two/) 43 | - [Bitwise Solution on Stanford](http://www.graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2) 44 | - [Binary number subtraction on YouTube](https://www.youtube.com/watch?v=S9LJknZTyos&t=0s&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8&index=66) -------------------------------------------------------------------------------- /src/algorithms/math/is-power-of-two/__test__/isPowerOfTwo.test.js: -------------------------------------------------------------------------------- 1 | import isPowerOfTwo from '../isPowerOfTwo'; 2 | 3 | describe('isPowerOfTwo', () => { 4 | it('should check if the number is made by multiplying twos', () => { 5 | expect(isPowerOfTwo(-1)).toBe(false); 6 | expect(isPowerOfTwo(0)).toBe(false); 7 | expect(isPowerOfTwo(1)).toBe(true); 8 | expect(isPowerOfTwo(2)).toBe(true); 9 | expect(isPowerOfTwo(3)).toBe(false); 10 | expect(isPowerOfTwo(4)).toBe(true); 11 | expect(isPowerOfTwo(5)).toBe(false); 12 | expect(isPowerOfTwo(6)).toBe(false); 13 | expect(isPowerOfTwo(7)).toBe(false); 14 | expect(isPowerOfTwo(8)).toBe(true); 15 | expect(isPowerOfTwo(10)).toBe(false); 16 | expect(isPowerOfTwo(12)).toBe(false); 17 | expect(isPowerOfTwo(16)).toBe(true); 18 | expect(isPowerOfTwo(31)).toBe(false); 19 | expect(isPowerOfTwo(64)).toBe(true); 20 | expect(isPowerOfTwo(1024)).toBe(true); 21 | expect(isPowerOfTwo(1023)).toBe(false); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/algorithms/math/is-power-of-two/__test__/isPowerOfTwoBitwise.test.js: -------------------------------------------------------------------------------- 1 | import isPowerOfTwoBitwise from '../isPowerOfTwoBitwise'; 2 | 3 | describe('isPowerOfTwoBitwise', () => { 4 | it('should check if the number is made by multiplying twos', () => { 5 | expect(isPowerOfTwoBitwise(-1)).toBe(false); 6 | expect(isPowerOfTwoBitwise(0)).toBe(false); 7 | expect(isPowerOfTwoBitwise(1)).toBe(true); 8 | expect(isPowerOfTwoBitwise(2)).toBe(true); 9 | expect(isPowerOfTwoBitwise(3)).toBe(false); 10 | expect(isPowerOfTwoBitwise(4)).toBe(true); 11 | expect(isPowerOfTwoBitwise(5)).toBe(false); 12 | expect(isPowerOfTwoBitwise(6)).toBe(false); 13 | expect(isPowerOfTwoBitwise(7)).toBe(false); 14 | expect(isPowerOfTwoBitwise(8)).toBe(true); 15 | expect(isPowerOfTwoBitwise(10)).toBe(false); 16 | expect(isPowerOfTwoBitwise(12)).toBe(false); 17 | expect(isPowerOfTwoBitwise(16)).toBe(true); 18 | expect(isPowerOfTwoBitwise(31)).toBe(false); 19 | expect(isPowerOfTwoBitwise(64)).toBe(true); 20 | expect(isPowerOfTwoBitwise(1024)).toBe(true); 21 | expect(isPowerOfTwoBitwise(1023)).toBe(false); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/algorithms/math/is-power-of-two/isPowerOfTwo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} number 3 | * @return {boolean} 4 | */ 5 | export default function isPowerOfTwo(number) { 6 | // 1 (2^0) là luỹ thừa của hai nhỏ nhất. 7 | if (number < 1) { 8 | return false; 9 | } 10 | 11 | // Ta sẽ kiểm tra xem có thể chia number cho hai 12 | // nhiều lần mà không có dư. 13 | let dividedNumber = number; 14 | while (dividedNumber !== 1) { 15 | if (dividedNumber % 2 !== 0) { 16 | // Đối với bất kỳ trường hợp nào khi phần dư khác không, chúng ta có thể 17 | // nói rằng số này không phải là lũy thừa của hai. 18 | return false; 19 | } 20 | 21 | dividedNumber /= 2; 22 | } 23 | 24 | return true; 25 | } 26 | -------------------------------------------------------------------------------- /src/algorithms/math/is-power-of-two/isPowerOfTwoBitwise.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} number 3 | * @return {boolean} 4 | */ 5 | export default function isPowerOfTwoBitwise(number) { 6 | // 1 (2^0) là luỹ thừa của hai nhỏ nhất. 7 | if (number < 1) { 8 | return false; 9 | } 10 | 11 | /* 12 | * Luỹ thừa của hai ở dạng nhị phân như sau: 13 | * 1: 0001 14 | * 2: 0010 15 | * 4: 0100 16 | * 8: 1000 17 | * 18 | * Ta thấy luôn chỉ có một bit có giá trị là `1`. Trừ khi đấy là số nguyên có dấu 19 | * (ví dụ: số nguyên có dấu 8-bit có giá trị -128 sẽ là : `10000000`) 20 | * 21 | * Vì vậy, sau khi kiểm tra rằng số đấy lớn hơn không, chúng ta có thể sử dụng 22 | * một thủ thuật bitwise để kiểm tra số đó chỉ có một bit là `1`. 23 | */ 24 | return (number & (number - 1)) === 0; 25 | } 26 | -------------------------------------------------------------------------------- /src/algorithms/math/least-common-multiple/README.md: -------------------------------------------------------------------------------- 1 | # Bội chung nhỏ nhất 2 | 3 | Trong lý thuyết số học, bội số chung nhỏ nhất hoặc bội chung nhỏ nhất của hai số nguyên `a` và `b`, thường được ký hiệu là `LCM (a, b)`, là số nguyên dương nhỏ nhất có thể chia hết cho cả `a` và` b`. Vì phép chia số cho số 0 là undefined, nên định nghĩa này chỉ có nghĩa nếu `a` và `b` đều khác 0. 4 | 5 | ## Ví dụ 6 | 7 | Bội chung nhỏ nhất của 4 và 6? 8 | 9 | Bội số của 4 là : 10 | ``` 11 | 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, ... 12 | ``` 13 | 14 | Và bội số của 6 là : 15 | ``` 16 | 6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, ... 17 | ``` 18 | 19 | Các bội số chung của `4` và `6` trong danh sách là : 20 | 21 | ``` 22 | 12, 24, 36, 48, 60, 72, .... 23 | ``` 24 | 25 | Vì vậy theo danh sách, bội số chung đầu tiên của `4` và `6` cũng là bội số chung nhỏ nhất là `12`. 26 | 27 | ## Tính bội số chung nhỏ nhất 28 | 29 | Công thức sau đây tính bội số chung nhỏ nhất bằng cách lấy tích hai số (luôn dương) chia cho ước số chung lớn nhất (GCD) : 30 | 31 | ``` 32 | lcm(a, b) = |a * b| / gcd(a, b) 33 | ``` 34 | 35 | ![LCM](https://upload.wikimedia.org/wikipedia/commons/c/c9/Symmetrical_5-set_Venn_diagram_LCM_2_3_4_5_7.svg) 36 | 37 | Biểu đồ Venn cho thấy bội số chung nhỏ nhất của kết hợp của `2`, `3`, `4`,`5` và `7` (`6` được bỏ qua dó nó có dạng là `2 × 3`, cả hai số đều đã được biểu diễn). 38 | 39 | ## Liên kết 40 | 41 | [Wikipedia](https://en.wikipedia.org/wiki/Least_common_multiple) 42 | -------------------------------------------------------------------------------- /src/algorithms/math/least-common-multiple/__test__/leastCommonMultiple.test.js: -------------------------------------------------------------------------------- 1 | import leastCommonMultiple from '../leastCommonMultiple'; 2 | 3 | describe('leastCommonMultiple', () => { 4 | it('should find least common multiple', () => { 5 | expect(leastCommonMultiple(0, 0)).toBe(0); 6 | expect(leastCommonMultiple(1, 0)).toBe(0); 7 | expect(leastCommonMultiple(0, 1)).toBe(0); 8 | expect(leastCommonMultiple(4, 6)).toBe(12); 9 | expect(leastCommonMultiple(6, 21)).toBe(42); 10 | expect(leastCommonMultiple(7, 2)).toBe(14); 11 | expect(leastCommonMultiple(3, 5)).toBe(15); 12 | expect(leastCommonMultiple(7, 3)).toBe(21); 13 | expect(leastCommonMultiple(1000000, 2)).toBe(1000000); 14 | expect(leastCommonMultiple(-9, -18)).toBe(18); 15 | expect(leastCommonMultiple(-7, -9)).toBe(63); 16 | expect(leastCommonMultiple(-7, 9)).toBe(63); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/algorithms/math/least-common-multiple/leastCommonMultiple.js: -------------------------------------------------------------------------------- 1 | import euclideanAlgorithm from '../euclidean-algorithm/euclideanAlgorithm'; 2 | 3 | /** 4 | * @param {number} a 5 | * @param {number} b 6 | * @return {number} 7 | */ 8 | 9 | export default function leastCommonMultiple(a, b) { 10 | return ((a === 0) || (b === 0)) ? 0 : Math.abs(a * b) / euclideanAlgorithm(a, b); 11 | } 12 | -------------------------------------------------------------------------------- /src/algorithms/math/pascal-triangle/__test__/pascalTriangle.test.js: -------------------------------------------------------------------------------- 1 | import pascalTriangle from '../pascalTriangle'; 2 | 3 | describe('pascalTriangle', () => { 4 | it('should calculate Pascal Triangle coefficients for specific line number', () => { 5 | expect(pascalTriangle(0)).toEqual([1]); 6 | expect(pascalTriangle(1)).toEqual([1, 1]); 7 | expect(pascalTriangle(2)).toEqual([1, 2, 1]); 8 | expect(pascalTriangle(3)).toEqual([1, 3, 3, 1]); 9 | expect(pascalTriangle(4)).toEqual([1, 4, 6, 4, 1]); 10 | expect(pascalTriangle(5)).toEqual([1, 5, 10, 10, 5, 1]); 11 | expect(pascalTriangle(6)).toEqual([1, 6, 15, 20, 15, 6, 1]); 12 | expect(pascalTriangle(7)).toEqual([1, 7, 21, 35, 35, 21, 7, 1]); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/algorithms/math/pascal-triangle/__test__/pascalTriangleRecursive.test.js: -------------------------------------------------------------------------------- 1 | import pascalTriangleRecursive from '../pascalTriangleRecursive'; 2 | 3 | describe('pascalTriangleRecursive', () => { 4 | it('should calculate Pascal Triangle coefficients for specific line number', () => { 5 | expect(pascalTriangleRecursive(0)).toEqual([1]); 6 | expect(pascalTriangleRecursive(1)).toEqual([1, 1]); 7 | expect(pascalTriangleRecursive(2)).toEqual([1, 2, 1]); 8 | expect(pascalTriangleRecursive(3)).toEqual([1, 3, 3, 1]); 9 | expect(pascalTriangleRecursive(4)).toEqual([1, 4, 6, 4, 1]); 10 | expect(pascalTriangleRecursive(5)).toEqual([1, 5, 10, 10, 5, 1]); 11 | expect(pascalTriangleRecursive(6)).toEqual([1, 6, 15, 20, 15, 6, 1]); 12 | expect(pascalTriangleRecursive(7)).toEqual([1, 7, 21, 35, 35, 21, 7, 1]); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/algorithms/math/pascal-triangle/pascalTriangle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} lineNumber - zero based. 3 | * @return {number[]} 4 | */ 5 | export default function pascalTriangle(lineNumber) { 6 | const currentLine = [1]; 7 | 8 | const currentLineSize = lineNumber + 1; 9 | 10 | for (let numIndex = 1; numIndex < currentLineSize; numIndex += 1) { 11 | // Xem giải thích biểu thức ở README. 12 | currentLine[numIndex] = (currentLine[numIndex - 1] * (lineNumber - numIndex + 1)) / numIndex; 13 | } 14 | 15 | return currentLine; 16 | } 17 | -------------------------------------------------------------------------------- /src/algorithms/math/pascal-triangle/pascalTriangleRecursive.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} lineNumber - zero based. 3 | * @return {number[]} 4 | */ 5 | export default function pascalTriangleRecursive(lineNumber) { 6 | if (lineNumber === 0) { 7 | return [1]; 8 | } 9 | 10 | const currentLineSize = lineNumber + 1; 11 | const previousLineSize = currentLineSize - 1; 12 | 13 | // Tạo vùng chứa cho các giá trị ở dòng hiện tại. 14 | const currentLine = []; 15 | 16 | // Ta sẽ tính toán dòng hiện tại dựa trên dòng trước đó. 17 | const previousLine = pascalTriangleRecursive(lineNumber - 1); 18 | 19 | 20 | // Xem tất cả các phần tử của dòng hiện tại ngoại trừ phần tử đầu tiên và 21 | // phần tử cuối cùng (vì chúng luôn bằng 1) và tính toán 22 | // hệ số hiện tại dựa trên dòng trước đó. 23 | for (let numIndex = 0; numIndex < currentLineSize; numIndex += 1) { 24 | const leftCoefficient = (numIndex - 1) >= 0 ? previousLine[numIndex - 1] : 0; 25 | const rightCoefficient = numIndex < previousLineSize ? previousLine[numIndex] : 0; 26 | 27 | currentLine[numIndex] = leftCoefficient + rightCoefficient; 28 | } 29 | 30 | return currentLine; 31 | } 32 | -------------------------------------------------------------------------------- /src/algorithms/math/primality-test/README.md: -------------------------------------------------------------------------------- 1 | # Kiểm tra nguyên hàm 2 | 3 | Một **Số nguyên tố** là một số tự nhiên lớn hơn `1` không thể viết dưới dạng tích của hai số nhỏ hơn nó. Số tự nhiên lớn hơn 1 và không phải số nguyên tố được gọi là hợp số. Ví dụ `5` là số nguyên tố vì nó không thể viết ở dạng tích, `1 × 5` hay `5 × 1` đều là chính nó. Tuy nhiên, `6` là hợp số vì nó có thể viết dưới dạng tích của hai số khác ``(2 × 3)` và đều nhỏ hơn 6. 4 | 5 | ![Prime Numbers](https://upload.wikimedia.org/wikipedia/commons/f/f0/Primes-vs-composites.svg) 6 | 7 | **Kiểm tra nguyên hàm** là một thuật toán để xác định xem một số được cho có phải là số nguyên tố hay không. Nó cũng được sử dụng cho lĩnh vực mật mã học. Không giống như phân tích thừa số, kiểm tra nguyên hàm thường không đưa ra thừa số nguyên tố, mà chỉ cho biết số đầu vào có phải là số nguyên tố hay không. Thừa số hóa được cho là một vấn đề khó về mặt tính toán, trong khi kiểm tra nguyên hàm tương đối dễ dàng (thời gian chạy của nó là đa thức với cùng kích thước của đầu vào). 8 | 9 | ## Liên kết 10 | 11 | - [Prime Numbers on Wikipedia](https://en.wikipedia.org/wiki/Prime_number) 12 | - [Primality Test on Wikipedia](https://en.wikipedia.org/wiki/Primality_test) -------------------------------------------------------------------------------- /src/algorithms/math/primality-test/__test__/trialDivision.test.js: -------------------------------------------------------------------------------- 1 | import trialDivision from '../trialDivision'; 2 | 3 | /** 4 | * @param {function(n: number)} testFunction 5 | */ 6 | function primalityTest(testFunction) { 7 | expect(testFunction(1)).toBe(false); 8 | expect(testFunction(2)).toBe(true); 9 | expect(testFunction(3)).toBe(true); 10 | expect(testFunction(5)).toBe(true); 11 | expect(testFunction(11)).toBe(true); 12 | expect(testFunction(191)).toBe(true); 13 | expect(testFunction(191)).toBe(true); 14 | expect(testFunction(199)).toBe(true); 15 | 16 | expect(testFunction(-1)).toBe(false); 17 | expect(testFunction(0)).toBe(false); 18 | expect(testFunction(4)).toBe(false); 19 | expect(testFunction(6)).toBe(false); 20 | expect(testFunction(12)).toBe(false); 21 | expect(testFunction(14)).toBe(false); 22 | expect(testFunction(25)).toBe(false); 23 | expect(testFunction(192)).toBe(false); 24 | expect(testFunction(200)).toBe(false); 25 | expect(testFunction(400)).toBe(false); 26 | 27 | // It should also deal with floats. 28 | expect(testFunction(0.5)).toBe(false); 29 | expect(testFunction(1.3)).toBe(false); 30 | expect(testFunction(10.5)).toBe(false); 31 | } 32 | 33 | describe('trialDivision', () => { 34 | it('should detect prime numbers', () => { 35 | primalityTest(trialDivision); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/algorithms/math/primality-test/trialDivision.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} number 3 | * @return {boolean} 4 | */ 5 | export default function trialDivision(number) { 6 | // Kiểm tra xem có phải số nguyên không. 7 | if (number % 1 !== 0) { 8 | return false; 9 | } 10 | 11 | if (number <= 1) { 12 | // Nếu number nhỏ hơn hoặc bằng 1 nó không phải số nguyên tố. 13 | return false; 14 | } 15 | 16 | if (number <= 3) { 17 | // Số 2 và 3 đều là số nguyên tố. 18 | return true; 19 | } 20 | 21 | // Nếu number là số nguyên tố thì phải loại trừ hết các trường hợp số chẵn. 22 | if (number % 2 === 0) { 23 | return false; 24 | } 25 | 26 | // Nếu không có ước nào đến căn bậc hai của number thì nó là số nguyên tố. 27 | const dividerLimit = Math.sqrt(number); 28 | for (let divider = 3; divider <= dividerLimit; divider += 2) { 29 | if (number % divider === 0) { 30 | return false; 31 | } 32 | } 33 | 34 | return true; 35 | } 36 | -------------------------------------------------------------------------------- /src/algorithms/math/prime-factors/primeFactors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tìm thừa số nguyên tố. 3 | * 4 | * @param {number} n - số cần phân tích thừa số nguyên tố. 5 | * @returns {number[]} - dãy thừa số nguyên tố. 6 | */ 7 | export function primeFactors(n) { 8 | // Tạo bản sao n để tránh bị ghi đè. 9 | let nn = n; 10 | 11 | // Mảng lưu trữ tất cả thừa số nguyên tố. 12 | const factors = []; 13 | 14 | // Chạy vòng lặp đến sqrt(n) thay vì n để tối ưu độ phức tạp thời gian. 15 | for (let factor = 2; factor <= Math.sqrt(nn); factor += 1) { 16 | // Kiểm tra thừa số nguyên tố. 17 | while (nn % factor === 0) { 18 | // Ghi đè bản sao n. 19 | nn /= factor; 20 | // Lưu thừa số. 21 | factors.push(factor); 22 | } 23 | } 24 | 25 | // Nhắc thêm vào thừa số nguyên tố cuối cùng, 26 | // trừ khi nó là 1 vì 1 không phải số nguyên tố. 27 | if (nn !== 1) { 28 | factors.push(nn); 29 | } 30 | 31 | return factors; 32 | } 33 | 34 | /** 35 | * Tính gần đúng các thừa số nguyên tố theo Hardy-Ramanujan. 36 | * 37 | * @param {number} n 38 | * @returns {number} - giá trị thừa số nguyên tố gần đúng . 39 | */ 40 | export function hardyRamanujan(n) { 41 | return Math.log(Math.log(n)); 42 | } 43 | -------------------------------------------------------------------------------- /src/algorithms/math/radian/README.md: -------------------------------------------------------------------------------- 1 | ## Radian 2 | 3 | **Radian** (ký hiệu là **rad**) là một đơn vị để đo lường góc, nó là đơn vị đo góc tiêu chuẩn được sử dụng trong nhiều lĩnh vực toán học. 4 | 5 | Đối với đường tròn đơn vị, độ lớn góc radian luôn bằng chiều dài cung tròn, mà chu vi nửa cung tròn là πr, tương đương `180` độ do vậy 1 radian bằng `180/π` xấp xỉ 57,3 độ. 6 | 7 | Độ lớn tính bằng radian của một vòng tròn hoàn chỉnh (360 độ) là bằng chiều dài chu vi chia cho bán kính, tức là bằng 2πr/r hay 2π. 8 | 9 | ![Radian](https://upload.wikimedia.org/wikipedia/commons/4/4e/Circle_radians.gif) 10 | 11 | Một vòng quay tuần hoàn là 2π radian (được hiển thị ở đây với một vòng tròn bán kính là `1` và chu vi là `2π`). 12 | 13 | ![2 pi Radian](https://upload.wikimedia.org/wikipedia/commons/6/67/2pi-unrolled.gif) 14 | 15 | **Chuyển đổi** 16 | 17 | | Radians | Degrees | 18 | | :-----: | :-----: | 19 | | 0 | 0° | 20 | | π/12 | 15° | 21 | | π/6 | 30° | 22 | | π/4 | 45° | 23 | | 1 | 57.3° | 24 | | π/3 | 60° | 25 | | π/2 | 90° | 26 | | π | 180° | 27 | | 2π | 360° | 28 | 29 | 30 | ## Liên kết 31 | 32 | - [Wikipedia](https://en.wikipedia.org/wiki/Radian) -------------------------------------------------------------------------------- /src/algorithms/math/radian/__test__/degreeToRadian.test.js: -------------------------------------------------------------------------------- 1 | import degreeToRadian from '../degreeToRadian'; 2 | 3 | describe('degreeToRadian', () => { 4 | it('should convert degree to radian', () => { 5 | expect(degreeToRadian(0)).toBe(0); 6 | expect(degreeToRadian(45)).toBe(Math.PI / 4); 7 | expect(degreeToRadian(90)).toBe(Math.PI / 2); 8 | expect(degreeToRadian(180)).toBe(Math.PI); 9 | expect(degreeToRadian(270)).toBe((3 * Math.PI) / 2); 10 | expect(degreeToRadian(360)).toBe(2 * Math.PI); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/algorithms/math/radian/__test__/radianToDegree.test.js: -------------------------------------------------------------------------------- 1 | import radianToDegree from '../radianToDegree'; 2 | 3 | describe('radianToDegree', () => { 4 | it('should convert radian to degree', () => { 5 | expect(radianToDegree(0)).toBe(0); 6 | expect(radianToDegree(Math.PI / 4)).toBe(45); 7 | expect(radianToDegree(Math.PI / 2)).toBe(90); 8 | expect(radianToDegree(Math.PI)).toBe(180); 9 | expect(radianToDegree((3 * Math.PI) / 2)).toBe(270); 10 | expect(radianToDegree(2 * Math.PI)).toBe(360); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/algorithms/math/radian/degreeToRadian.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} degree 3 | * @return {number} 4 | */ 5 | export default function degreeToRadian(degree) { 6 | return degree * (Math.PI / 180); 7 | } 8 | -------------------------------------------------------------------------------- /src/algorithms/math/radian/radianToDegree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} radian 3 | * @return {number} 4 | */ 5 | export default function radianToDegree(radian) { 6 | return radian * (180 / Math.PI); 7 | } 8 | -------------------------------------------------------------------------------- /src/algorithms/math/sieve-of-eratosthenes/README.md: -------------------------------------------------------------------------------- 1 | # Sàng Eratosthenes 2 | 3 | Sàng Eratosthenes là một thuật toán tìm tất cả số nguyên tố trong phạm vi `n`. Nó được cho là của Eratosthenes ở Cyrene, một nhà toán học Hy Lạp cổ đại. 4 | 5 | ## Hoạt động 6 | 7 | 1. Tạo một mảng boolean gồm `n + 1` vị trí (để biểu diễn các số từ `0` đến `n`). 8 | 2. Mặc định vị trí `0` và `1` là `false`, phần còn lại là `true`. 9 | 3. Bắt đầu ở vị trí `p = 2` (số nguyên tố đầu tiên). 10 | 4. Đánh dấu là `false` tất cả các bội số của `p` (nghĩa là các vị trí `2 * p`, `3 * p`, `4 * p` ... cho đến khi tới cuối mảng). 11 | 5. Tìm vị trí đầu tiên lớn hơn `p` là `true` trong mảng. Nếu không có vị trí đó, hãy dừng lại. Nếu không, hãy đặt `p` bằng số mới (là số nguyên tố tiếp theo) và lặp lại từ bước 4. 12 | 13 | Khi thuật toán kết thúc, các số còn lại là `true` là các số nguyên tố trong phạm vi `n`. 14 | 15 | Một cải tiến của thuật toán này là ở bước 4, bắt đầu đánh dấu bội số 16 | của `p` từ `p * p`, chứ không phải từ `2 * p`. Lý do tại sao điều này hoạt động là vì tại thời điểm đó, bội số nhỏ hơn của `p` sẽ được đánh dấu là` false`. 17 | 18 | ## Ví dụ 19 | 20 | ![Sieve](https://upload.wikimedia.org/wikipedia/commons/b/b9/Sieve_of_Eratosthenes_animation.gif) 21 | 22 | ## Độ phức tạp 23 | 24 | Độ phức tạp của thuật toán là `O(n log(log n))`. 25 | 26 | ## Liên kết 27 | 28 | - [Wikipedia](https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes) 29 | -------------------------------------------------------------------------------- /src/algorithms/math/sieve-of-eratosthenes/__test__/sieveOfEratosthenes.test.js: -------------------------------------------------------------------------------- 1 | import sieveOfEratosthenes from '../sieveOfEratosthenes'; 2 | 3 | describe('sieveOfEratosthenes', () => { 4 | it('should find all primes less than or equal to n', () => { 5 | expect(sieveOfEratosthenes(5)).toEqual([2, 3, 5]); 6 | expect(sieveOfEratosthenes(10)).toEqual([2, 3, 5, 7]); 7 | expect(sieveOfEratosthenes(100)).toEqual([ 8 | 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 9 | 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 10 | ]); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/algorithms/math/sieve-of-eratosthenes/sieveOfEratosthenes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} maxNumber 3 | * @return {number[]} 4 | */ 5 | export default function sieveOfEratosthenes(maxNumber) { 6 | const isPrime = new Array(maxNumber + 1).fill(true); 7 | isPrime[0] = false; 8 | isPrime[1] = false; 9 | 10 | const primes = []; 11 | 12 | for (let number = 2; number <= maxNumber; number += 1) { 13 | if (isPrime[number] === true) { 14 | primes.push(number); 15 | 16 | /* 17 | * Tối ưu . 18 | * Bắt đầu đánh dấu bội số của `p` từ `p * p`, thay vì `2 * p`. 19 | * Lý do nó hoạt động là vì tại thời điểm này, bội số nhỏ hơn của `p` 20 | * sẽ được đánh dấu là` false` 21 | * 22 | * Cảnh báo: khi làm việc với các con số lớn, dòng sau có thể gây tràn dữ liệu 23 | * trong trường hợp đó ta đổi lại thành: 24 | * let nextNumber = 2 * number; 25 | */ 26 | let nextNumber = number * number; 27 | 28 | while (nextNumber <= maxNumber) { 29 | isPrime[nextNumber] = false; 30 | nextNumber += number; 31 | } 32 | } 33 | } 34 | 35 | return primes; 36 | } 37 | -------------------------------------------------------------------------------- /src/algorithms/math/square-root/squareRoot.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tính căn bậc hai của một số với dung sai(độ chính xác) 3 | * bằng phương pháp Newton 4 | * 5 | * @param number - số mà ta muốn tìm căn bậc hai the number we want to find a square root for. 6 | * @param [tolerance] - độ chính xác, số chữ số sau dấu phẩy động mà ta muốn lấy 7 | * @return {number} 8 | */ 9 | export default function squareRoot(number, tolerance = 0) { 10 | // Chi hỗ trợ với các số nguyên dương. 11 | if (number < 0) { 12 | throw new Error('The method supports only positive integers'); 13 | } 14 | 15 | 16 | // Trường hợp căn bậc hai của 0. 17 | if (number === 0) { 18 | return 0; 19 | } 20 | 21 | // Ta sẽ bắt đầu tính xấp xỉ từ giá trị 1. 22 | let root = 1; 23 | 24 | // Delta là độ chính xác mong muốn dựa trên dung sai. 25 | // - nếu tolerance=0 thì delta=1 26 | // - nếu tolerance=1 thì delta=0.1 27 | // - nếu tolerance=2 thì delta=0.01 28 | // - and so on... 29 | const requiredDelta = 1 / (10 ** tolerance); 30 | 31 | // Tính xấp xỉ giá trị căn bậc hai đến khi nhận được độ chính xác mong muốn. 32 | while (Math.abs(number - (root ** 2)) > requiredDelta) { 33 | // Trong trường hợp này, phương pháp Newton rút gọn thành phương pháp Babylon. 34 | // Các phương pháp này thường mang lại kết quả gần đúng, nhưng có thể được thực hiện 35 | // chính xác tùy ý bằng cách tăng số bước tính toán. 36 | root -= ((root ** 2) - number) / (2 * root); 37 | } 38 | 39 | // Cắt bỏ các phần chữ số sau dấu phẩy động không cần thiết 40 | // và trả về giá trị căn bậc hai. 41 | return Math.round(root * (10 ** tolerance)) / (10 ** tolerance); 42 | } 43 | -------------------------------------------------------------------------------- /src/algorithms/others/best-time-to-buy-sell-stocks/accumulatorBestTimeToBuySellStocks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tìm lợi nhuận lớn nhất từ việc mua bán cổ phiếu bẳng giải pháp 3 | * TÍCH LUỸ. 4 | * 5 | * @param {number[]} prices - Mảng giá cổ phiếu, vd [7, 6, 4, 3, 1] 6 | * @param {function(): void} visit - Truy cập hàm callback để tính toán số lần lặp lại. 7 | * @return {number} - Lợi nhuận lớn nhất 8 | */ 9 | const accumulatorBestTimeToBuySellStocks = (prices, visit = () => { }) => { 10 | visit(); 11 | let profit = 0; 12 | for (let day = 1; day < prices.length; day += 1) { 13 | visit(); 14 | // Cộng phần tăng giá từ hôm qua đến hôm nay (nếu có) vào lợi nhuận. 15 | profit += Math.max(prices[day] - prices[day - 1], 0); 16 | } 17 | return profit; 18 | }; 19 | 20 | export default accumulatorBestTimeToBuySellStocks; 21 | -------------------------------------------------------------------------------- /src/algorithms/others/best-time-to-buy-sell-stocks/dpBestTimeToBuySellStocks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tìm lợi nhuận lớn nhất từ việc mua bán cổ phiếu bẳng giải pháp 3 | * QUY HOẠCH ĐỘNG 4 | * 5 | * @param {number[]} prices - Mảng giá cổ phiếu, vd [7, 6, 4, 3, 1] 6 | * @param {function(): void} visit - Truy cập hàm callback để tính toán số lần lặp lại. 7 | * @return {number} - Lợi nhuận lớn nhất 8 | */ 9 | const dpBestTimeToBuySellStocks = (prices, visit = () => { }) => { 10 | visit(); 11 | let lastBuy = -prices[0]; 12 | let lastSold = 0; 13 | 14 | for (let day = 1; day < prices.length; day += 1) { 15 | visit(); 16 | const curBuy = Math.max(lastBuy, lastSold - prices[day]); 17 | const curSold = Math.max(lastSold, lastBuy + prices[day]); 18 | lastBuy = curBuy; 19 | lastSold = curSold; 20 | } 21 | 22 | return lastSold; 23 | }; 24 | 25 | export default dpBestTimeToBuySellStocks; 26 | -------------------------------------------------------------------------------- /src/algorithms/others/best-time-to-buy-sell-stocks/dqBestTimeToBuySellStocks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tìm lợi nhuận lớn nhất từ việc mua bán cổ phiếu bẳng giải pháp 3 | * CHIA ĐỂ TRỊ. 4 | * 5 | * @param {number[]} prices - Mảng giá cổ phiếu, vd [7, 6, 4, 3, 1] 6 | * @param {function(): void} visit - Truy cập hàm callback để tính toán số lần lặp lại. 7 | * @return {number} - Lợi nhuận lớn nhất 8 | */ 9 | const dqBestTimeToBuySellStocks = (prices, visit = () => { }) => { 10 | /** 11 | * Triển khai đệ quy ở hàm main. Nó được ẩn với người dùng. 12 | * 13 | * @param {boolean} buy - Lựa chọn mua hoặc bán 14 | * @param {number} day - ngày hiện tại của giao dịch (chỉ số hiện tại trong mảng) 15 | * @returns {number} - lợi nhuận lớn nhất từ việc mua/bán 16 | */ 17 | const recursiveBuyerSeller = (buy, day) => { 18 | // Gọi đệ quy để tính toán độ phức tạp. 19 | visit(); 20 | 21 | // Thoát đệ quy nếu đây là ngày giao dịch cuối cùng (mảng giá đã kết thúc). 22 | if (day === prices.length) { 23 | return 0; 24 | } 25 | 26 | // Nếu ta mua - sẽ mất tiền (-1), nếu ta bán - sẽ nhận tiền (+1) 27 | const operationSign = buy ? -1 : +1; 28 | return Math.max( 29 | // Lựa chọn 1: Đừng làm gì. 30 | recursiveBuyerSeller(buy, day + 1), 31 | // Lựa chọn 2: Mua hoặc bán với giá hiên tại. 32 | operationSign * prices[day] + recursiveBuyerSeller(!buy, day + 1), 33 | ); 34 | }; 35 | 36 | const buy = true; 37 | const day = 0; 38 | 39 | return recursiveBuyerSeller(buy, day); 40 | }; 41 | 42 | export default dqBestTimeToBuySellStocks; 43 | -------------------------------------------------------------------------------- /src/algorithms/others/best-time-to-buy-sell-stocks/peakvalleyBestTimeToBuySellStocks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tìm lợi nhuận lớn nhất từ việc mua bán cổ phiếu bẳng giải pháp 3 | * PEAK VALLEY 4 | * 5 | * @param {number[]} prices - Mảng giá cổ phiếu, vd [7, 6, 4, 3, 1] 6 | * @param {function(): void} visit - Truy cập hàm callback để tính toán số lần lặp lại. 7 | * @return {number} - Lợi nhuận lớn nhất 8 | */ 9 | const peakvalleyBestTimeToBuySellStocks = (prices, visit = () => { }) => { 10 | visit(); 11 | let profit = 0; 12 | let low = prices[0]; 13 | let high = prices[0]; 14 | 15 | prices.slice(1).forEach((currentPrice) => { 16 | visit(); 17 | if (currentPrice < high) { 18 | // Nếu giá đi xuống, ta sẽ mua lại nó. 19 | profit += high - low; 20 | low = currentPrice; 21 | high = currentPrice; 22 | } else { 23 | // Nếu giá tăng, ta không cần làm gì ngoài việc tăng bản ghi. 24 | high = currentPrice; 25 | } 26 | }); 27 | 28 | // Trong trường hợp nếu giá tăng trong ngày cuối cùng và 29 | // ta không có cơ hội bán trong vòng lặp forEach. 30 | profit += high - low; 31 | 32 | return profit; 33 | }; 34 | 35 | export default peakvalleyBestTimeToBuySellStocks; 36 | -------------------------------------------------------------------------------- /src/algorithms/others/hanoi-tower/README.md: -------------------------------------------------------------------------------- 1 | # Tháp Hà Nội 2 | 3 | Tháp Hà Nội (còn gọi là tháp Brahma hay tháp Lucas) là một trò chơi hay câu đố toán học. 4 | Trò chơi này gồm một bộ các đĩa kích thước khác nhau, nằm xuyên trên ba cái cọc, có thể di chuyển từ cọc này sang cọc khác. Trò chơi bắt đầu bằng cách sắp xếp các đĩa theo trật tự kích thước, sao cho trên mỗi cọc đĩa nhỏ nhất luôn nằm trên cùng, tạo ra một dạng hình nón. 5 | 6 | Yêu cầu của trò chơi là di chuyển toàn bộ đĩa sang một cọc khác, với các quy tắc sau: 7 | - Mỗi đĩa chỉ được di chuyển một lần. 8 | - Mỗi lần di chuyển bao gồm các bước lấy đĩa khỏi cọc và đặt nó lên cọc khác. 9 | - Không được đặt đĩa lên đĩa nhỏ hơn. 10 | 11 | ![Hanoi Tower](https://upload.wikimedia.org/wikipedia/commons/8/8d/Iterative_algorithm_solving_a_6_disks_Tower_of_Hanoi.gif) 12 | 13 | Ảnh động thuật toán giải với 6 đĩa. 14 | 15 | Với `3` đĩa, câu đó có thể giải trong `7` bước. Số nước đi tối thiểu để giải một câu đố Tháp Hà Nội là `2^n - 1`, trong đó `n` là số đĩa. 16 | 17 | ## Liên kết 18 | 19 | - [Wikipedia](https://en.wikipedia.org/wiki/Tower_of_Hanoi) 20 | - [HackerEarth](https://www.hackerearth.com/blog/algorithms/tower-hanoi-recursion-game-algorithm-explained/) 21 | -------------------------------------------------------------------------------- /src/algorithms/others/jump-game/__test__/backtrackingJumpGame.test.js: -------------------------------------------------------------------------------- 1 | import backtrackingJumpGame from '../backtrackingJumpGame'; 2 | 3 | describe('backtrackingJumpGame', () => { 4 | it('should solve Jump Game problem in backtracking manner', () => { 5 | expect(backtrackingJumpGame([1, 0])).toBe(true); 6 | expect(backtrackingJumpGame([100, 0])).toBe(true); 7 | expect(backtrackingJumpGame([2, 3, 1, 1, 4])).toBe(true); 8 | expect(backtrackingJumpGame([1, 1, 1, 1, 1])).toBe(true); 9 | expect(backtrackingJumpGame([1, 1, 1, 10, 1])).toBe(true); 10 | expect(backtrackingJumpGame([1, 5, 2, 1, 0, 2, 0])).toBe(true); 11 | 12 | expect(backtrackingJumpGame([1, 0, 1])).toBe(false); 13 | expect(backtrackingJumpGame([3, 2, 1, 0, 4])).toBe(false); 14 | expect(backtrackingJumpGame([0, 0, 0, 0, 0])).toBe(false); 15 | expect(backtrackingJumpGame([5, 4, 3, 2, 1, 0, 0])).toBe(false); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/algorithms/others/jump-game/__test__/dpBottomUpJumpGame.test.js: -------------------------------------------------------------------------------- 1 | import dpBottomUpJumpGame from '../dpBottomUpJumpGame'; 2 | 3 | describe('dpBottomUpJumpGame', () => { 4 | it('should solve Jump Game problem in bottom-up dynamic programming manner', () => { 5 | expect(dpBottomUpJumpGame([1, 0])).toBe(true); 6 | expect(dpBottomUpJumpGame([100, 0])).toBe(true); 7 | expect(dpBottomUpJumpGame([2, 3, 1, 1, 4])).toBe(true); 8 | expect(dpBottomUpJumpGame([1, 1, 1, 1, 1])).toBe(true); 9 | expect(dpBottomUpJumpGame([1, 1, 1, 10, 1])).toBe(true); 10 | expect(dpBottomUpJumpGame([1, 5, 2, 1, 0, 2, 0])).toBe(true); 11 | 12 | expect(dpBottomUpJumpGame([1, 0, 1])).toBe(false); 13 | expect(dpBottomUpJumpGame([3, 2, 1, 0, 4])).toBe(false); 14 | expect(dpBottomUpJumpGame([0, 0, 0, 0, 0])).toBe(false); 15 | expect(dpBottomUpJumpGame([5, 4, 3, 2, 1, 0, 0])).toBe(false); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/algorithms/others/jump-game/__test__/dpTopDownJumpGame.test.js: -------------------------------------------------------------------------------- 1 | import dpTopDownJumpGame from '../dpTopDownJumpGame'; 2 | 3 | describe('dpTopDownJumpGame', () => { 4 | it('should solve Jump Game problem in top-down dynamic programming manner', () => { 5 | expect(dpTopDownJumpGame([1, 0])).toBe(true); 6 | expect(dpTopDownJumpGame([100, 0])).toBe(true); 7 | expect(dpTopDownJumpGame([2, 3, 1, 1, 4])).toBe(true); 8 | expect(dpTopDownJumpGame([1, 1, 1, 1, 1])).toBe(true); 9 | expect(dpTopDownJumpGame([1, 1, 1, 10, 1])).toBe(true); 10 | expect(dpTopDownJumpGame([1, 5, 2, 1, 0, 2, 0])).toBe(true); 11 | 12 | expect(dpTopDownJumpGame([1, 0, 1])).toBe(false); 13 | expect(dpTopDownJumpGame([3, 2, 1, 0, 4])).toBe(false); 14 | expect(dpTopDownJumpGame([0, 0, 0, 0, 0])).toBe(false); 15 | expect(dpTopDownJumpGame([5, 4, 3, 2, 1, 0, 0])).toBe(false); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/algorithms/others/jump-game/__test__/greedyJumpGame.test.js: -------------------------------------------------------------------------------- 1 | import greedyJumpGame from '../greedyJumpGame'; 2 | 3 | describe('greedyJumpGame', () => { 4 | it('should solve Jump Game problem in greedy manner', () => { 5 | expect(greedyJumpGame([1, 0])).toBe(true); 6 | expect(greedyJumpGame([100, 0])).toBe(true); 7 | expect(greedyJumpGame([2, 3, 1, 1, 4])).toBe(true); 8 | expect(greedyJumpGame([1, 1, 1, 1, 1])).toBe(true); 9 | expect(greedyJumpGame([1, 1, 1, 10, 1])).toBe(true); 10 | expect(greedyJumpGame([1, 5, 2, 1, 0, 2, 0])).toBe(true); 11 | 12 | expect(greedyJumpGame([1, 0, 1])).toBe(false); 13 | expect(greedyJumpGame([3, 2, 1, 0, 4])).toBe(false); 14 | expect(greedyJumpGame([0, 0, 0, 0, 0])).toBe(false); 15 | expect(greedyJumpGame([5, 4, 3, 2, 1, 0, 0])).toBe(false); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/algorithms/others/jump-game/greedyJumpGame.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Giải pháp tham lam để giải Jump Game. 3 | * 4 | * Đây gọi là tối ưu hoá của giải pháp QUY HOẠCH ĐỘNG TỪ DƯỚI LÊN. 5 | * 6 | * Khi ta code theo giải pháp từ dưới lên, ta có thể thực hiện quan sát 7 | * cuối cùng, quan trọng nhất. Từ vị trí đã cho, khi ta tìm xem có thể 8 | * nhảy tới vị trí cuối cùng không, ta có thể chỉ tìm xem có thể nhảy 9 | * đến một vị trí GOOD khác hay không. Nói cách khác, ta có thể theo dõi 10 | * vị trị GOOD như một biến riêng biệt mà tránh việc sử dụng bảng ghi nhớ. 11 | * 12 | * Ta gọi vị trí trong mảng là "good" nếu nó bắt đầu từ nó có thể đi đến 13 | * cuối mảng. Ngược lại ta gọi vị trí đấy là "bad". 14 | * 15 | * @param {number[]} numbers - array of possible jump length. 16 | * @return {boolean} 17 | */ 18 | export default function greedyJumpGame(numbers) { 19 | // Ô "goog" là ô mà từ nó ta có thể nhảy đến ô cuối cùng của mảng số. 20 | 21 | // Ô cuối cùng của mảng số chắc chắn "goog" vì nó là đích mà ta cần đến. 22 | let leftGoodPosition = numbers.length - 1; 23 | 24 | // Đi xuyên qua mảng từ phải sang trái. 25 | for (let numberIndex = numbers.length - 2; numberIndex >= 0; numberIndex -= 1) { 26 | // Nếu ta có thể đến một ô "good" nào đó từ ô hiện tại. Thì ô hiện tại chắc chắn 27 | // cũng là "good". Vì sau cùng ta có thể đến cuối mảng từ nó. 28 | const maxCurrentJumpLength = numberIndex + numbers[numberIndex]; 29 | if (maxCurrentJumpLength >= leftGoodPosition) { 30 | leftGoodPosition = numberIndex; 31 | } 32 | } 33 | 34 | // Nếu vị trí tốt nhất bên trái là 0 thì ta có thể nói rằng 35 | // có thể đi đến cuối mảng từ ô đầu tiên. 36 | return leftGoodPosition === 0; 37 | } 38 | -------------------------------------------------------------------------------- /src/algorithms/others/knight-tour/README.md: -------------------------------------------------------------------------------- 1 | # Bài Toán Mã Đi Tuần 2 | 3 | **Bài toán mã đi tuần** là bài toán về việc di chuyển một quân mã trên bàn cờ vua, sao cho nó đi qua mỗi ô trên bàn cờ đúng một lần. Nếu quân mã kết thục ở ô mà nó bắt đầu trên bàn cờ ta gọi nó là một hành trình **đóng**, ngược lại gọi là hành trình **mở**. 4 | 5 | Đây là một bài toán thường gặp với những sinh viên khoa học máy tính. Kích thước chuẩn của bàn cờ trong bài toán là `8x8`, tuy nhiên đôi khi cũng có vài biến thể với kích thước khác. 6 | 7 | Bài toán mã đi tuần là một dạng của bài toán tổng quát hơn là **bài toán tìm đường đi Hamilton** trong l‎ý thuyết đồ thị. Bài toán tìm hành trình đóng của quân mã là một bài toán cụ thể của bài toán tìm chu trình Hamilton. 8 | 9 | ![Knight's Tour](https://upload.wikimedia.org/wikipedia/commons/d/da/Knight%27s_tour_anim_2.gif) 10 | 11 | Hành trình mở của quân mã trên bàn cờ. 12 | 13 | ![Knight's Tour](https://upload.wikimedia.org/wikipedia/commons/c/ca/Knights-Tour-Animation.gif) 14 | 15 | Ảnh động về hành trình mở của quân mã trên bàn cờ kích thước `5x5`. 16 | 17 | ## Liên kết 18 | 19 | - [Wikipedia](https://en.wikipedia.org/wiki/Knight%27s_tour) 20 | - [GeeksForGeeks](https://www.geeksforgeeks.org/backtracking-set-1-the-knights-tour-problem/) 21 | -------------------------------------------------------------------------------- /src/algorithms/others/knight-tour/__test__/knightTour.test.js: -------------------------------------------------------------------------------- 1 | import knightTour from '../knightTour'; 2 | 3 | describe('knightTour', () => { 4 | it('should not find solution on 3x3 board', () => { 5 | const moves = knightTour(3); 6 | 7 | expect(moves.length).toBe(0); 8 | }); 9 | 10 | it('should find one solution to do knight tour on 5x5 board', () => { 11 | const moves = knightTour(5); 12 | 13 | expect(moves.length).toBe(25); 14 | 15 | expect(moves).toEqual([ 16 | [0, 0], 17 | [1, 2], 18 | [2, 0], 19 | [0, 1], 20 | [1, 3], 21 | [3, 4], 22 | [2, 2], 23 | [4, 1], 24 | [3, 3], 25 | [1, 4], 26 | [0, 2], 27 | [1, 0], 28 | [3, 1], 29 | [4, 3], 30 | [2, 4], 31 | [0, 3], 32 | [1, 1], 33 | [3, 0], 34 | [4, 2], 35 | [2, 1], 36 | [4, 0], 37 | [3, 2], 38 | [4, 4], 39 | [2, 3], 40 | [0, 4], 41 | ]); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/algorithms/others/n-queens/QueenPosition.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Lớp biểu diễn quân hậu trên bàn cờ. 3 | */ 4 | export default class QueenPosition { 5 | /** 6 | * @param {number} rowIndex 7 | * @param {number} columnIndex 8 | */ 9 | constructor(rowIndex, columnIndex) { 10 | this.rowIndex = rowIndex; 11 | this.columnIndex = columnIndex; 12 | } 13 | 14 | /** 15 | * @return {number} 16 | */ 17 | get leftDiagonal() { 18 | // Mỗi vị trí trên cùng một đường chéo bên trái (\) có cùng sự khác biệt về rowIndex và columnIndex. 19 | // Dữ kiện này có thể được sử dụng để nhanh chóng kiểm tra xem hai vị trí (quân hậu) có nằm trên 20 | // cùng một đường chéo bên trái hay không. 21 | // @see https://youtu.be/xouin83ebxE?t=1m59s 22 | return this.rowIndex - this.columnIndex; 23 | } 24 | 25 | /** 26 | * @return {number} 27 | */ 28 | get rightDiagonal() { 29 | // Mỗi vị trí trên cùng một đường chéo bên phải (/) có cùng một tổng của rowIndex và columnIndex. 30 | // Dữ kiện này có thể được sử dụng để nhanh chóng kiểm tra xem hai vị trí (quân hậu) có nằm trên 31 | // cùng một đường chéo bên phải hay không. 32 | // @see https://youtu.be/xouin83ebxE?t=1m59s 33 | return this.rowIndex + this.columnIndex; 34 | } 35 | 36 | toString() { 37 | return `${this.rowIndex},${this.columnIndex}`; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/algorithms/others/n-queens/__test__/QueensPosition.test.js: -------------------------------------------------------------------------------- 1 | import QueenPosition from '../QueenPosition'; 2 | 3 | describe('QueenPosition', () => { 4 | it('should store queen position on chessboard', () => { 5 | const position1 = new QueenPosition(0, 0); 6 | const position2 = new QueenPosition(2, 1); 7 | 8 | expect(position2.columnIndex).toBe(1); 9 | expect(position2.rowIndex).toBe(2); 10 | expect(position1.leftDiagonal).toBe(0); 11 | expect(position1.rightDiagonal).toBe(0); 12 | expect(position2.leftDiagonal).toBe(1); 13 | expect(position2.rightDiagonal).toBe(3); 14 | expect(position2.toString()).toBe('2,1'); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/algorithms/others/n-queens/__test__/nQueens.test.js: -------------------------------------------------------------------------------- 1 | import nQueens from '../nQueens'; 2 | 3 | describe('nQueens', () => { 4 | it('should not hae solution for 3 queens', () => { 5 | const solutions = nQueens(3); 6 | expect(solutions.length).toBe(0); 7 | }); 8 | 9 | it('should solve n-queens problem for 4 queens', () => { 10 | const solutions = nQueens(4); 11 | expect(solutions.length).toBe(2); 12 | 13 | // First solution. 14 | expect(solutions[0][0].toString()).toBe('0,1'); 15 | expect(solutions[0][1].toString()).toBe('1,3'); 16 | expect(solutions[0][2].toString()).toBe('2,0'); 17 | expect(solutions[0][3].toString()).toBe('3,2'); 18 | 19 | // Second solution (mirrored). 20 | expect(solutions[1][0].toString()).toBe('0,2'); 21 | expect(solutions[1][1].toString()).toBe('1,0'); 22 | expect(solutions[1][2].toString()).toBe('2,3'); 23 | expect(solutions[1][3].toString()).toBe('3,1'); 24 | }); 25 | 26 | it('should solve n-queens problem for 6 queens', () => { 27 | const solutions = nQueens(6); 28 | expect(solutions.length).toBe(4); 29 | 30 | // First solution. 31 | expect(solutions[0][0].toString()).toBe('0,1'); 32 | expect(solutions[0][1].toString()).toBe('1,3'); 33 | expect(solutions[0][2].toString()).toBe('2,5'); 34 | expect(solutions[0][3].toString()).toBe('3,0'); 35 | expect(solutions[0][4].toString()).toBe('4,2'); 36 | expect(solutions[0][5].toString()).toBe('5,4'); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /src/algorithms/others/n-queens/__test__/nQueensBitwise.test.js: -------------------------------------------------------------------------------- 1 | import nQueensBitwise from '../nQueensBitwise'; 2 | 3 | describe('nQueensBitwise', () => { 4 | it('should have solutions for 4 to N queens', () => { 5 | expect(nQueensBitwise(4)).toBe(2); 6 | expect(nQueensBitwise(5)).toBe(10); 7 | expect(nQueensBitwise(6)).toBe(4); 8 | expect(nQueensBitwise(7)).toBe(40); 9 | expect(nQueensBitwise(8)).toBe(92); 10 | expect(nQueensBitwise(9)).toBe(352); 11 | expect(nQueensBitwise(10)).toBe(724); 12 | expect(nQueensBitwise(11)).toBe(2680); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/algorithms/others/rain-terraces/__test__/bfRainTerraces.test.js: -------------------------------------------------------------------------------- 1 | import bfRainTerraces from '../bfRainTerraces'; 2 | 3 | describe('bfRainTerraces', () => { 4 | it('should find the amount of water collected after raining', () => { 5 | expect(bfRainTerraces([1])).toBe(0); 6 | expect(bfRainTerraces([1, 0])).toBe(0); 7 | expect(bfRainTerraces([0, 1])).toBe(0); 8 | expect(bfRainTerraces([0, 1, 0])).toBe(0); 9 | expect(bfRainTerraces([0, 1, 0, 0])).toBe(0); 10 | expect(bfRainTerraces([0, 1, 0, 0, 1, 0])).toBe(2); 11 | expect(bfRainTerraces([0, 2, 0, 0, 1, 0])).toBe(2); 12 | expect(bfRainTerraces([2, 0, 2])).toBe(2); 13 | expect(bfRainTerraces([2, 0, 5])).toBe(2); 14 | expect(bfRainTerraces([3, 0, 0, 2, 0, 4])).toBe(10); 15 | expect(bfRainTerraces([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1])).toBe(6); 16 | expect(bfRainTerraces([1, 1, 1, 1, 1])).toBe(0); 17 | expect(bfRainTerraces([1, 2, 3, 4, 5])).toBe(0); 18 | expect(bfRainTerraces([4, 1, 3, 1, 2, 1, 2, 1])).toBe(4); 19 | expect(bfRainTerraces([0, 2, 4, 3, 4, 2, 4, 0, 8, 7, 0])).toBe(7); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/algorithms/others/rain-terraces/__test__/dpRainTerraces.test.js: -------------------------------------------------------------------------------- 1 | import dpRainTerraces from '../dpRainTerraces'; 2 | 3 | describe('dpRainTerraces', () => { 4 | it('should find the amount of water collected after raining', () => { 5 | expect(dpRainTerraces([1])).toBe(0); 6 | expect(dpRainTerraces([1, 0])).toBe(0); 7 | expect(dpRainTerraces([0, 1])).toBe(0); 8 | expect(dpRainTerraces([0, 1, 0])).toBe(0); 9 | expect(dpRainTerraces([0, 1, 0, 0])).toBe(0); 10 | expect(dpRainTerraces([0, 1, 0, 0, 1, 0])).toBe(2); 11 | expect(dpRainTerraces([0, 2, 0, 0, 1, 0])).toBe(2); 12 | expect(dpRainTerraces([2, 0, 2])).toBe(2); 13 | expect(dpRainTerraces([2, 0, 5])).toBe(2); 14 | expect(dpRainTerraces([3, 0, 0, 2, 0, 4])).toBe(10); 15 | expect(dpRainTerraces([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1])).toBe(6); 16 | expect(dpRainTerraces([1, 1, 1, 1, 1])).toBe(0); 17 | expect(dpRainTerraces([1, 2, 3, 4, 5])).toBe(0); 18 | expect(dpRainTerraces([4, 1, 3, 1, 2, 1, 2, 1])).toBe(4); 19 | expect(dpRainTerraces([0, 2, 4, 3, 4, 2, 4, 0, 8, 7, 0])).toBe(7); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/algorithms/others/rain-terraces/bfRainTerraces.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Giải pháp PHÁ MÃ để giải quyết bài toán Thềm Nước Mưa. 3 | * 4 | * @param {number[]} terraces 5 | * @return {number} 6 | */ 7 | export default function bfRainTerraces(terraces) { 8 | let waterAmount = 0; 9 | 10 | for (let terraceIndex = 0; terraceIndex < terraces.length; terraceIndex += 1) { 11 | // Lấy bậc cao nhất bên trái. 12 | let leftHighestLevel = 0; 13 | for (let leftIndex = terraceIndex - 1; leftIndex >= 0; leftIndex -= 1) { 14 | leftHighestLevel = Math.max(leftHighestLevel, terraces[leftIndex]); 15 | } 16 | 17 | // Lấy bậc cao nhất bên phải. 18 | let rightHighestLevel = 0; 19 | for (let rightIndex = terraceIndex + 1; rightIndex < terraces.length; rightIndex += 1) { 20 | rightHighestLevel = Math.max(rightHighestLevel, terraces[rightIndex]); 21 | } 22 | 23 | // Thêm lượng nước trên thềm hiện tại. 24 | const terraceBoundaryLevel = Math.min(leftHighestLevel, rightHighestLevel); 25 | if (terraceBoundaryLevel > terraces[terraceIndex]) { 26 | // Thềm có thể đọng nước nếu phần thấp nhất của hai bậc cao nhất 27 | // bên trái và phải vẫn cao hơn bậc hiện tại 28 | waterAmount += terraceBoundaryLevel - terraces[terraceIndex]; 29 | } 30 | } 31 | 32 | return waterAmount; 33 | } 34 | -------------------------------------------------------------------------------- /src/algorithms/others/recursive-staircase/README.md: -------------------------------------------------------------------------------- 1 | # Bài Toán Đệ Quy Cầu Thang 2 | 3 | ## Vấn đề 4 | 5 | Có `n` bậc thang, người đứng dưới cầu thang muốn đi lên trên cùng. Họ có thể lựa chọn bước `1` hoặc `2` bậc tại một thời điểm. _Đếm số cách mà một người có thể đi lên trên cầu thang_. 6 | 7 | ![](https://cdncontribute.geeksforgeeks.org/wp-content/uploads/nth-stair.png) 8 | 9 | ## Giải pháp 10 | 11 | Đây là một bài toán thú vị vì có nhiều cách để giải nó, dưới đây là minh hoạ của các giải pháp lập trình khác nhau. 12 | 13 | - [Phá Mã](./recursiveStaircaseBF.js) - Thời gian: `O(2^n)`; Không gian: `O(1)` 14 | - [Dùng Bộ Nhớ Phụ](./recursiveStaircaseMEM.js) - Thời gian: `O(n)`; Không gian: `O(n)` 15 | - [Quy Hoạch Động](./recursiveStaircaseDP.js) - Thời gian: `O(n)`; Không gian: `O(n)` 16 | - [Vòng Lặp](./recursiveStaircaseIT.js) - Time: `O(n)`; Space: `O(1)` 17 | 18 | ## Vòng Lặp 19 | 20 | - [On YouTube by Gayle Laakmann McDowell](https://www.youtube.com/watch?v=eREiwuvzaUM&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8&index=81&t=0s) 21 | - [GeeksForGeeks](https://www.geeksforgeeks.org/count-ways-reach-nth-stair/) -------------------------------------------------------------------------------- /src/algorithms/others/recursive-staircase/__test__/recursiveStaircaseBF.test.js: -------------------------------------------------------------------------------- 1 | import recursiveStaircaseBF from '../recursiveStaircaseBF'; 2 | 3 | describe('recursiveStaircaseBF', () => { 4 | it('should calculate number of variants using Brute Force solution', () => { 5 | expect(recursiveStaircaseBF(-1)).toBe(0); 6 | expect(recursiveStaircaseBF(0)).toBe(0); 7 | expect(recursiveStaircaseBF(1)).toBe(1); 8 | expect(recursiveStaircaseBF(2)).toBe(2); 9 | expect(recursiveStaircaseBF(3)).toBe(3); 10 | expect(recursiveStaircaseBF(4)).toBe(5); 11 | expect(recursiveStaircaseBF(5)).toBe(8); 12 | expect(recursiveStaircaseBF(6)).toBe(13); 13 | expect(recursiveStaircaseBF(7)).toBe(21); 14 | expect(recursiveStaircaseBF(8)).toBe(34); 15 | expect(recursiveStaircaseBF(9)).toBe(55); 16 | expect(recursiveStaircaseBF(10)).toBe(89); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/algorithms/others/recursive-staircase/__test__/recursiveStaircaseDP.test.js: -------------------------------------------------------------------------------- 1 | import recursiveStaircaseDP from '../recursiveStaircaseDP'; 2 | 3 | describe('recursiveStaircaseDP', () => { 4 | it('should calculate number of variants using Dynamic Programming solution', () => { 5 | expect(recursiveStaircaseDP(-1)).toBe(0); 6 | expect(recursiveStaircaseDP(0)).toBe(0); 7 | expect(recursiveStaircaseDP(1)).toBe(1); 8 | expect(recursiveStaircaseDP(2)).toBe(2); 9 | expect(recursiveStaircaseDP(3)).toBe(3); 10 | expect(recursiveStaircaseDP(4)).toBe(5); 11 | expect(recursiveStaircaseDP(5)).toBe(8); 12 | expect(recursiveStaircaseDP(6)).toBe(13); 13 | expect(recursiveStaircaseDP(7)).toBe(21); 14 | expect(recursiveStaircaseDP(8)).toBe(34); 15 | expect(recursiveStaircaseDP(9)).toBe(55); 16 | expect(recursiveStaircaseDP(10)).toBe(89); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/algorithms/others/recursive-staircase/__test__/recursiveStaircaseIT.test.js: -------------------------------------------------------------------------------- 1 | import recursiveStaircaseIT from '../recursiveStaircaseIT'; 2 | 3 | describe('recursiveStaircaseIT', () => { 4 | it('should calculate number of variants using Iterative solution', () => { 5 | expect(recursiveStaircaseIT(-1)).toBe(0); 6 | expect(recursiveStaircaseIT(0)).toBe(0); 7 | expect(recursiveStaircaseIT(1)).toBe(1); 8 | expect(recursiveStaircaseIT(2)).toBe(2); 9 | expect(recursiveStaircaseIT(3)).toBe(3); 10 | expect(recursiveStaircaseIT(4)).toBe(5); 11 | expect(recursiveStaircaseIT(5)).toBe(8); 12 | expect(recursiveStaircaseIT(6)).toBe(13); 13 | expect(recursiveStaircaseIT(7)).toBe(21); 14 | expect(recursiveStaircaseIT(8)).toBe(34); 15 | expect(recursiveStaircaseIT(9)).toBe(55); 16 | expect(recursiveStaircaseIT(10)).toBe(89); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/algorithms/others/recursive-staircase/__test__/recursiveStaircaseMEM.test.js: -------------------------------------------------------------------------------- 1 | import recursiveStaircaseMEM from '../recursiveStaircaseMEM'; 2 | 3 | describe('recursiveStaircaseMEM', () => { 4 | it('should calculate number of variants using Brute Force with Memoization', () => { 5 | expect(recursiveStaircaseMEM(-1)).toBe(0); 6 | expect(recursiveStaircaseMEM(0)).toBe(0); 7 | expect(recursiveStaircaseMEM(1)).toBe(1); 8 | expect(recursiveStaircaseMEM(2)).toBe(2); 9 | expect(recursiveStaircaseMEM(3)).toBe(3); 10 | expect(recursiveStaircaseMEM(4)).toBe(5); 11 | expect(recursiveStaircaseMEM(5)).toBe(8); 12 | expect(recursiveStaircaseMEM(6)).toBe(13); 13 | expect(recursiveStaircaseMEM(7)).toBe(21); 14 | expect(recursiveStaircaseMEM(8)).toBe(34); 15 | expect(recursiveStaircaseMEM(9)).toBe(55); 16 | expect(recursiveStaircaseMEM(10)).toBe(89); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/algorithms/others/recursive-staircase/recursiveStaircaseBF.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Bài Toán Đệ Quy Cầu Thang (Phá Mã) 3 | * 4 | * @param {number} stairsNum - số bậc có thể bước. 5 | * @return {number} - Số cách để bước lên cầu thang. 6 | */ 7 | export default function recursiveStaircaseBF(stairsNum) { 8 | if (stairsNum <= 0) { 9 | // Số bậc nhỏ hơn 1 thì không có gì để bước 10 | return 0; 11 | } 12 | 13 | if (stairsNum === 1) { 14 | // Nếu chỉ có 1 bậc chỉ có một cách bước. 15 | return 1; 16 | } 17 | 18 | if (stairsNum === 2) { 19 | // Nếu có hai bậc có hai cách bước là (1+1) và (2). 20 | return 2; 21 | } 22 | 23 | // Tính tổng số bước ta cần thực hiện sau khi thực hiện một bước 24 | // với số bước ta cần thực hiện sau khi thực hiện hai bước lên. 25 | return recursiveStaircaseBF(stairsNum - 1) + recursiveStaircaseBF(stairsNum - 2); 26 | } 27 | -------------------------------------------------------------------------------- /src/algorithms/others/recursive-staircase/recursiveStaircaseDP.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Bài Toán Đệ Quy Cầu Thang (Quy Hoạch Động) 3 | * 4 | * @param {number} stairsNum - số bậc có thể bước. 5 | * @return {number} - Số cách để bước lên cầu thang. 6 | */ 7 | export default function recursiveStaircaseDP(stairsNum) { 8 | if (stairsNum < 0) { 9 | // Không có bậc thang nào để bước. 10 | return 0; 11 | } 12 | 13 | // Khởi tạo vector "steps" sẽ chứa tất cả các cách có thể đến bước tương ứng. 14 | const steps = new Array(stairsNum + 1).fill(0); 15 | 16 | // Khởi tạo số cách để đến bậc thứ 0, 1 và 2. 17 | steps[0] = 0; 18 | steps[1] = 1; 19 | steps[2] = 2; 20 | 21 | if (stairsNum <= 2) { 22 | // Trả về số cách để đến bậc thứ 0, 1 và 2. 23 | return steps[stairsNum]; 24 | } 25 | 26 | // Tính toán bước kế tiếp dựa trên hai bước trước nó. 27 | for (let currentStep = 3; currentStep <= stairsNum; currentStep += 1) { 28 | steps[currentStep] = steps[currentStep - 1] + steps[currentStep - 2]; 29 | } 30 | 31 | // Trả về các cách để có thể đi hết cầu thang. 32 | return steps[stairsNum]; 33 | } 34 | -------------------------------------------------------------------------------- /src/algorithms/others/recursive-staircase/recursiveStaircaseIT.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Bài Toán Đệ Quy Cầu Thang (Vòng Lặp) 3 | * 4 | * @param {number} totalStairs - số bậc có thể bước. 5 | * @return {number} - Số cách để bước lên cầu thang. 6 | */ 7 | export default function recursiveStaircaseIT(stairsNum) { 8 | if (stairsNum <= 0) { 9 | // Số bậc nhỏ hơn 1 thì không có gì để bước 10 | return 0; 11 | } 12 | 13 | // Tạo mảng lưu số cách đến bậc thứ 0, 1 và 2. 14 | const steps = [1, 2]; 15 | 16 | if (stairsNum <= 2) { 17 | // Trả về số cách có thể để đến bậc thứ 1 hoặc thứ 2. 18 | return steps[stairsNum - 1]; 19 | } 20 | 21 | // Tính số cách đi đến bước thứ n dựa trên những cách trước đó. 22 | // So với giải pháp Quy hoạch động, ta chỉ lưu trữ thông tin cho hai bước 23 | // trước nó thay vì lưu tất cả các bước. 24 | for (let currentStep = 3; currentStep <= stairsNum; currentStep += 1) { 25 | [steps[0], steps[1]] = [steps[1], steps[0] + steps[1]]; 26 | } 27 | 28 | // Trả lại các cách có thể để đến bậc được yêu cầu. 29 | return steps[1]; 30 | } 31 | -------------------------------------------------------------------------------- /src/algorithms/others/recursive-staircase/recursiveStaircaseMEM.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Bài Toán Đệ Quy Cầu Thang (Giải Pháp dùng Bộ Nhớ Phụ) 3 | * 4 | * @param {number} totalStairs - số bậc có thể bước. 5 | * @return {number} - Số cách để bước lên cầu thang. 6 | */ 7 | export default function recursiveStaircaseMEM(totalStairs) { 8 | // Bảng ghi nhớ sẽ chứa tất cả các kết quả được tính toán đệ quy để 9 | // tránh tính toán chúng nhiều lần. 10 | const memo = []; 11 | 12 | // Đệ quy với closure. 13 | const getSteps = (stairsNum) => { 14 | if (stairsNum <= 0) { 15 | // Số bậc nhỏ hơn 1 thì không có gì để bước 16 | return 0; 17 | } 18 | 19 | if (stairsNum === 1) { 20 | // Nếu chỉ có 1 bậc chỉ có một cách bước. 21 | return 1; 22 | } 23 | 24 | if (stairsNum === 2) { 25 | // Nếu có hai bậc có hai cách bước là (1+1) và (2). 26 | return 2; 27 | } 28 | 29 | // Tránh đệ quy cho các bước đã tính toán gần đây. 30 | if (memo[stairsNum]) { 31 | return memo[stairsNum]; 32 | } 33 | 34 | // Tính tổng số bước ta cần thực hiện sau khi thực hiện một bước 35 | // với số bước ta cần thực hiện sau khi thực hiện hai bước lên. 36 | memo[stairsNum] = getSteps(stairsNum - 1) + getSteps(stairsNum - 2); 37 | 38 | return memo[stairsNum]; 39 | }; 40 | 41 | // Trả về các cách để có thể đi hết cầu thang. 42 | return getSteps(totalStairs); 43 | } 44 | -------------------------------------------------------------------------------- /src/algorithms/others/square-matrix-rotation/__test__/squareMatrixRotation.test.js: -------------------------------------------------------------------------------- 1 | import squareMatrixRotation from '../squareMatrixRotation'; 2 | 3 | describe('squareMatrixRotation', () => { 4 | it('should rotate matrix #0 in-place', () => { 5 | const matrix = [[1]]; 6 | 7 | const rotatedMatrix = [[1]]; 8 | 9 | expect(squareMatrixRotation(matrix)).toEqual(rotatedMatrix); 10 | }); 11 | 12 | it('should rotate matrix #1 in-place', () => { 13 | const matrix = [ 14 | [1, 2], 15 | [3, 4], 16 | ]; 17 | 18 | const rotatedMatrix = [ 19 | [3, 1], 20 | [4, 2], 21 | ]; 22 | 23 | expect(squareMatrixRotation(matrix)).toEqual(rotatedMatrix); 24 | }); 25 | 26 | it('should rotate matrix #2 in-place', () => { 27 | const matrix = [ 28 | [1, 2, 3], 29 | [4, 5, 6], 30 | [7, 8, 9], 31 | ]; 32 | 33 | const rotatedMatrix = [ 34 | [7, 4, 1], 35 | [8, 5, 2], 36 | [9, 6, 3], 37 | ]; 38 | 39 | expect(squareMatrixRotation(matrix)).toEqual(rotatedMatrix); 40 | }); 41 | 42 | it('should rotate matrix #3 in-place', () => { 43 | const matrix = [ 44 | [5, 1, 9, 11], 45 | [2, 4, 8, 10], 46 | [13, 3, 6, 7], 47 | [15, 14, 12, 16], 48 | ]; 49 | 50 | const rotatedMatrix = [ 51 | [15, 13, 2, 5], 52 | [14, 3, 4, 1], 53 | [12, 6, 8, 9], 54 | [16, 7, 10, 11], 55 | ]; 56 | 57 | expect(squareMatrixRotation(matrix)).toEqual(rotatedMatrix); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /src/algorithms/others/square-matrix-rotation/squareMatrixRotation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {*[][]} originalMatrix 3 | * @return {*[][]} 4 | */ 5 | export default function squareMatrixRotation(originalMatrix) { 6 | const matrix = originalMatrix.slice(); 7 | 8 | // Thực hiện phản chiếu chéo trên cùng bên phải/dưới cùng bên trái của ma trận. 9 | for (let rowIndex = 0; rowIndex < matrix.length; rowIndex += 1) { 10 | for (let columnIndex = rowIndex + 1; columnIndex < matrix.length; columnIndex += 1) { 11 | // Hoán đổi phần tử. 12 | [ 13 | matrix[columnIndex][rowIndex], 14 | matrix[rowIndex][columnIndex], 15 | ] = [ 16 | matrix[rowIndex][columnIndex], 17 | matrix[columnIndex][rowIndex], 18 | ]; 19 | } 20 | } 21 | 22 | // Thực hiện phản chiếu ngang của ma trận. 23 | for (let rowIndex = 0; rowIndex < matrix.length; rowIndex += 1) { 24 | for (let columnIndex = 0; columnIndex < matrix.length / 2; columnIndex += 1) { 25 | // Hoán đổi phần tử. 26 | [ 27 | matrix[rowIndex][matrix.length - columnIndex - 1], 28 | matrix[rowIndex][columnIndex], 29 | ] = [ 30 | matrix[rowIndex][columnIndex], 31 | matrix[rowIndex][matrix.length - columnIndex - 1], 32 | ]; 33 | } 34 | } 35 | 36 | return matrix; 37 | } 38 | -------------------------------------------------------------------------------- /src/algorithms/others/unique-paths/__test__/btUniquePaths.test.js: -------------------------------------------------------------------------------- 1 | import btUniquePaths from '../btUniquePaths'; 2 | 3 | describe('btUniquePaths', () => { 4 | it('should find the number of unique paths on board', () => { 5 | expect(btUniquePaths(3, 2)).toBe(3); 6 | expect(btUniquePaths(7, 3)).toBe(28); 7 | expect(btUniquePaths(3, 7)).toBe(28); 8 | expect(btUniquePaths(10, 10)).toBe(48620); 9 | expect(btUniquePaths(100, 1)).toBe(1); 10 | expect(btUniquePaths(1, 100)).toBe(1); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/algorithms/others/unique-paths/__test__/dpUniquePaths.test.js: -------------------------------------------------------------------------------- 1 | import dpUniquePaths from '../dpUniquePaths'; 2 | 3 | describe('dpUniquePaths', () => { 4 | it('should find the number of unique paths on board', () => { 5 | expect(dpUniquePaths(3, 2)).toBe(3); 6 | expect(dpUniquePaths(7, 3)).toBe(28); 7 | expect(dpUniquePaths(3, 7)).toBe(28); 8 | expect(dpUniquePaths(10, 10)).toBe(48620); 9 | expect(dpUniquePaths(100, 1)).toBe(1); 10 | expect(dpUniquePaths(1, 100)).toBe(1); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/algorithms/others/unique-paths/__test__/uniquePaths.test.js: -------------------------------------------------------------------------------- 1 | import uniquePaths from '../uniquePaths'; 2 | 3 | describe('uniquePaths', () => { 4 | it('should find the number of unique paths on board', () => { 5 | expect(uniquePaths(3, 2)).toBe(3); 6 | expect(uniquePaths(7, 3)).toBe(28); 7 | expect(uniquePaths(3, 7)).toBe(28); 8 | expect(uniquePaths(10, 10)).toBe(48620); 9 | expect(uniquePaths(100, 1)).toBe(1); 10 | expect(uniquePaths(1, 100)).toBe(1); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/algorithms/others/unique-paths/dpUniquePaths.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Giải pháp QUY HOẠCH ĐỘNG để giải bài toán Đường Đi Đơn Nhất. 3 | * 4 | * @param {number} width - Chiều dài của bảng. 5 | * @param {number} height - Chiểu rộng của bảng. 6 | * @return {number} - Số đường đi đơn nhất. 7 | */ 8 | export default function dpUniquePaths(width, height) { 9 | // Khởi tạo bảng. 10 | const board = Array(height).fill(null).map(() => { 11 | return Array(width).fill(0); 12 | }); 13 | 14 | // Trường hợp cơ bản. 15 | // Chỉ có một cách để đến board[0][any] và cũng 16 | // chỉ có một cách để đến board[any][0]. Điều này là do ta bị hạn chế 17 | // chỉ di chuyển sang phải và xuống. 18 | for (let rowIndex = 0; rowIndex < height; rowIndex += 1) { 19 | for (let columnIndex = 0; columnIndex < width; columnIndex += 1) { 20 | if (rowIndex === 0 || columnIndex === 0) { 21 | board[rowIndex][columnIndex] = 1; 22 | } 23 | } 24 | } 25 | 26 | // Bây giờ, vì ta bị hạn chế là chỉ di chuyển sang phải và xuống, 27 | // ta có thể nói rằng số lượng đường đi đơn nhất đến ô hiện tại 28 | //là tổng số đường đi đơn nhất đến ô phía trên ô hiện tại 29 | // và đến ô bên trái của hiện tại. 30 | for (let rowIndex = 1; rowIndex < height; rowIndex += 1) { 31 | for (let columnIndex = 1; columnIndex < width; columnIndex += 1) { 32 | const uniquesFromTop = board[rowIndex - 1][columnIndex]; 33 | const uniquesFromLeft = board[rowIndex][columnIndex - 1]; 34 | board[rowIndex][columnIndex] = uniquesFromTop + uniquesFromLeft; 35 | } 36 | } 37 | 38 | return board[height - 1][width - 1]; 39 | } 40 | -------------------------------------------------------------------------------- /src/algorithms/others/unique-paths/uniquePaths.js: -------------------------------------------------------------------------------- 1 | import pascalTriangle from '../../math/pascal-triangle/pascalTriangle'; 2 | 3 | /** 4 | * @param {number} width 5 | * @param {number} height 6 | * @return {number} 7 | */ 8 | export default function uniquePaths(width, height) { 9 | const pascalLine = width + height - 2; 10 | const pascalLinePosition = Math.min(width, height) - 1; 11 | 12 | return pascalTriangle(pascalLine)[pascalLinePosition]; 13 | } 14 | -------------------------------------------------------------------------------- /src/algorithms/search/binary-search/README.md: -------------------------------------------------------------------------------- 1 | # Tìm kiếm nhị phân 2 | 3 | Trong khoa học máy tính, tìm kiếm nhị phân còn gọi là tìm kiếm nữa khoảng, tìm kiếm logarit hay binary chop, là thuật toán tìm kiếm vị trí của giá trị cần tìm trong một mảng đã sắp xếp. Tìm kiếm nhị phân so sánh giá trị cần tìm với phần tử ở giữa của mảng. Nếu hai giá trị không bằng nhau, phần nửa mảng không chứa giá trị cần tìm sẽ bị bỏ qua và tiếp tục tìm kiếm trên nửa còn lại, một lần nữa lấy phần tử ở giữa và so sánh với giá trị cần tìm, cứ thế lặp lại cho đến khi tìm thấy giá trị đó. Nếu phép tìm kiếm kết thúc khi nửa còn lại trống thì giá trị cần tìm không có trong mảng. 4 | 5 | ![Binary Search](https://upload.wikimedia.org/wikipedia/commons/8/83/Binary_Search_Depiction.svg) 6 | 7 | ## Độ phức tạp 8 | 9 | **Độ phức tạp thời gian** : `O(log(n)) - vì ta chia phần tìm kiếm thành hai phần sau mỗi lần lặp. 10 | 11 | ## Liên kết 12 | - [Wikipedia](https://en.wikipedia.org/wiki/Binary_search_algorithm) 13 | - [YouTube](https://www.youtube.com/watch?v=P3YID7liBug&index=29&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) -------------------------------------------------------------------------------- /src/algorithms/search/binary-search/__test__/binarySearch.test.js: -------------------------------------------------------------------------------- 1 | import binarySearch from '../binarySearch'; 2 | 3 | describe('binarySearch', () => { 4 | it('should search number in sorted array', () => { 5 | expect(binarySearch([], 1)).toBe(-1); 6 | expect(binarySearch([1], 1)).toBe(0); 7 | expect(binarySearch([1, 2], 1)).toBe(0); 8 | expect(binarySearch([1, 2], 2)).toBe(1); 9 | expect(binarySearch([1, 5, 10, 12], 1)).toBe(0); 10 | expect(binarySearch([1, 5, 10, 12, 14, 17, 22, 100], 17)).toBe(5); 11 | expect(binarySearch([1, 5, 10, 12, 14, 17, 22, 100], 1)).toBe(0); 12 | expect(binarySearch([1, 5, 10, 12, 14, 17, 22, 100], 100)).toBe(7); 13 | expect(binarySearch([1, 5, 10, 12, 14, 17, 22, 100], 0)).toBe(-1); 14 | }); 15 | 16 | it('should search object in sorted array', () => { 17 | const sortedArrayOfObjects = [ 18 | { key: 1, value: 'value1' }, 19 | { key: 2, value: 'value2' }, 20 | { key: 3, value: 'value3' }, 21 | ]; 22 | 23 | const comparator = (a, b) => { 24 | if (a.key === b.key) return 0; 25 | return a.key < b.key ? -1 : 1; 26 | }; 27 | 28 | expect(binarySearch([], { key: 1 }, comparator)).toBe(-1); 29 | expect(binarySearch(sortedArrayOfObjects, { key: 4 }, comparator)).toBe(-1); 30 | expect(binarySearch(sortedArrayOfObjects, { key: 1 }, comparator)).toBe(0); 31 | expect(binarySearch(sortedArrayOfObjects, { key: 2 }, comparator)).toBe(1); 32 | expect(binarySearch(sortedArrayOfObjects, { key: 3 }, comparator)).toBe(2); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /src/algorithms/search/binary-search/binarySearch.js: -------------------------------------------------------------------------------- 1 | import Comparator from '../../../utils/Comparator'; 2 | 3 | /** 4 | * Triển khai tìm kiếm nhị phân. 5 | * 6 | * @param {*[]} sortedArray 7 | * @param {*} seekElement 8 | * @param {function(a, b)} [comparatorCallback] 9 | * @return {number} 10 | */ 11 | 12 | export default function binarySearch(sortedArray, seekElement, comparatorCallback) { 13 | // Tạo bộ so sánh dựa trên hàm comparatorCallback. 14 | // Đối tượng so sánh sẽ cung cấp cho ta những phương thức phổ biến như equal() và lessThen(). 15 | const comparator = new Comparator(comparatorCallback); 16 | 17 | // Hai chỉ số này sẽ là phạm vi ranh giới của các mảng con hiện tại. 18 | let startIndex = 0; 19 | let endIndex = sortedArray.length - 1; 20 | 21 | // Tiếp tục chia nhỏ mảng này đến khi không thể chia được nữa. 22 | while (startIndex <= endIndex) { 23 | // Tính toán chỉ mục (vị trí) của phần tử ở giữa. 24 | const middleIndex = startIndex + Math.floor((endIndex - startIndex) / 2); 25 | 26 | // Nếu chúng ta tìm thấy phần tử phù hợp trả về vị trí của nó. 27 | if (comparator.equal(sortedArray[middleIndex], seekElement)) { 28 | return middleIndex; 29 | } 30 | 31 | // Nếu không, ta phải tiếp tục tìm kiếm ở mảng con bên trái hoặc phải 32 | if (comparator.lessThan(sortedArray[middleIndex], seekElement)) { 33 | // Tìm kiếm ở mảng con bên phải. 34 | startIndex = middleIndex + 1; 35 | } else { 36 | // Tìm kiếm ở mảng con bên trái. 37 | endIndex = middleIndex - 1; 38 | } 39 | } 40 | 41 | // Trả về -1 nếu không tìm thấy. 42 | return -1; 43 | } 44 | -------------------------------------------------------------------------------- /src/algorithms/search/interpolation-search/README.md: -------------------------------------------------------------------------------- 1 | # Tìm kiếm nội suy 2 | 3 | **Tìm kiếm nội suy** là thuật toán tìm kiếm khoá trong một mảng đã được sắp xếp bởi các giá trị số được gán cho các khoá (giá trị khoá). 4 | 5 | Dể hiểu thì chúng ta có một mảng `arr[]` đã sắp xếp với `n` các giá trị được phần phối đồng đều, và chúng ta cần tìm phần tử `x` trong mảng. 6 | 7 | **Linear Search** tìm mất `O(n)` thời gian, **Jump Search** mất `O(√ n)` thời gian và **Binary Search** là `O(Log n)` thời gian. 8 | 9 | **Tìm kiếm nội suy** là một cải tiến của tìm kiếm nhị phân cho các trường hợp, trong đó các giá trị trong một mảng đã sắp xếp được phân phối _đồng nhất_. Tìm kiếm nhị phân thường kiểm tra phần tử ở giữa, trong khi tìm kiếm nội suy sẽ chọn các vị trí khác nhau tuỳ theo giá trị của khoá tìm kiếm. Ví dụ, nếu giá trị của khoá là gần phần tử cuối cùng, tìm kiếm nội suy có thể sẽ bắt đầu tìm kiếm về phía cuối. 10 | 11 | Để xác định ví trí cần tìm, ta có công thức : 12 | 13 | ``` 14 | // Ý tưởng cho công thức này là trả về giá trị lớn hơn của pos 15 | // khi phần tử cần tìm gần arr[hi]. 16 | // Và giá trị nhỏ hơn khi phần tử cần tìm gần arr[lo] 17 | pos = lo + ((x - arr[lo]) * (hi - lo) / (arr[hi] - arr[Lo])) 18 | 19 | arr[] - Mảng nơi cần tìm x 20 | x - Phần tử cần tìm 21 | lo - Chỉ số bắt đầu trong arr[] 22 | hi - Chỉ số kết thúc trong arr[] 23 | ``` 24 | 25 | ## Độ phức tạp 26 | 27 | **Độ phức tạp thời gian** : `O(log(log(n)))` 28 | 29 | # Liên kết 30 | 31 | - [GeeksForGeeks](https://www.geeksforgeeks.org/interpolation-search/) 32 | - [Wikipedia](https://en.wikipedia.org/wiki/Interpolation_search) -------------------------------------------------------------------------------- /src/algorithms/search/interpolation-search/__test__/interpolationSearch.test.js: -------------------------------------------------------------------------------- 1 | import interpolationSearch from '../interpolationSearch'; 2 | 3 | describe('interpolationSearch', () => { 4 | it('should search elements in sorted array of numbers', () => { 5 | expect(interpolationSearch([], 1)).toBe(-1); 6 | expect(interpolationSearch([1], 1)).toBe(0); 7 | expect(interpolationSearch([1], 0)).toBe(-1); 8 | expect(interpolationSearch([1, 1], 1)).toBe(0); 9 | expect(interpolationSearch([1, 2], 1)).toBe(0); 10 | expect(interpolationSearch([1, 2], 2)).toBe(1); 11 | expect(interpolationSearch([10, 20, 30, 40, 50], 40)).toBe(3); 12 | expect(interpolationSearch([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 14)).toBe(13); 13 | expect(interpolationSearch([1, 6, 7, 8, 12, 13, 14, 19, 21, 23, 24, 24, 24, 300], 24)).toBe(10); 14 | expect(interpolationSearch([1, 2, 3, 700, 800, 1200, 1300, 1400, 1900], 600)).toBe(-1); 15 | expect(interpolationSearch([1, 2, 3, 700, 800, 1200, 1300, 1400, 1900], 1)).toBe(0); 16 | expect(interpolationSearch([1, 2, 3, 700, 800, 1200, 1300, 1400, 1900], 2)).toBe(1); 17 | expect(interpolationSearch([1, 2, 3, 700, 800, 1200, 1300, 1400, 1900], 3)).toBe(2); 18 | expect(interpolationSearch([1, 2, 3, 700, 800, 1200, 1300, 1400, 1900], 700)).toBe(3); 19 | expect(interpolationSearch([1, 2, 3, 700, 800, 1200, 1300, 1400, 1900], 800)).toBe(4); 20 | expect(interpolationSearch([0, 2, 3, 700, 800, 1200, 1300, 1400, 1900], 1200)).toBe(5); 21 | expect(interpolationSearch([1, 2, 3, 700, 800, 1200, 1300, 1400, 19000], 800)).toBe(4); 22 | expect(interpolationSearch([0, 10, 11, 12, 13, 14, 15], 10)).toBe(1); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/algorithms/search/jump-search/README.md: -------------------------------------------------------------------------------- 1 | # Tìm kiếm nhảy 2 | 3 | Giống như tìm kiếm nhị phân, **tìm kiếm nhảy** cũng là một thuật toán trên một mảng đã sắp xếp. Về cơ bản thì ý tưởng của nó là kiểm tra một vài phần tử (ít hơn tìm kiếm tuyến tính) bằng cách thực hiện các bước nhảy cố định hoặc bỏ qua một vài phần tử thay vì tìm kiếm tất cả. 4 | 5 | Ví dụ, chúng ta có một mảng là `arr[]` với kích thước `n` và một khối(bước) có kích thước `m`. Khi tìm kiếm ta đánh dấu chỉ mục `arr[0]`, `arr[m]`, `arr[2 * m]`, ..., `arr[k * m]`,... Một khi chúng ta tìm được khoảng `arr[k * m] < x < arr[(k+1) * m]`, thì ta chỉ cần tìm tuyến tính trong khoảng đấy để tìm được phần tử `x`. 6 | 7 | **Tối ưu kích thước khối(bước) có thể bỏ qua?** 8 | Trong trường hợp tệ nhất, ta thực hiện `n/m` bước nhảy và nếu giá trị kiểm tra cuối cùng lớn hơn phần tử cần tìm kiếm, ta thực hiện `m-1` lần so sánh cho tìm kiếm tuyến tính. Do đó tổng số lần so sánh trong trường hợp tệ nhất sẽ là `((n/m) + m -1)`. Giá trị của hàm `((n/m) + m -1)` sẽ là nhỏ nhất khi `m = √n`. 9 | Do đó, bước nhảy tốt nhất là `m = √n`. 10 | 11 | ## Độ phức tạp 12 | 13 | **Độ phức tạp thời gian**: `O(√n)` - bởi vì ta tìm kiếm bằng khối(bước) có kích thước `√n`. 14 | 15 | ## Liên kết 16 | 17 | - [GeeksForGeeks](https://www.geeksforgeeks.org/jump-search/) 18 | - [Wikipedia](https://en.wikipedia.org/wiki/Jump_search) -------------------------------------------------------------------------------- /src/algorithms/search/linear-search/README.md: -------------------------------------------------------------------------------- 1 | # Tìm kiếm tuyến tính 2 | 3 | Trong khoa học máy tính, tìm kiếm tuyến tính hay tìm kiếm tuần tự là một phương thức tìm kiếm giá trị trong danh sách. Nó tuần tự kiểm tra các phần tử trong danh sách đến khi tìm được giá trị phù hợp hoặc đã kiểm hết danh sách. Tìm kiếm tuyến tính hoạt động tệ nhất khi giá trị cần tìm ở cuối danh sách tức nó phải thực hiên `n` phép so sánh, với `n` là độ dài danh sách. 4 | 5 | ![Linear Search](https://www.tutorialspoint.com/data_structures_algorithms/images/linear_search.gif) 6 | 7 | ## Độ phức tạp 8 | 9 | **Độ phức tạp thời gian** : `O(n)` - Vì trong trường hợp xấu nhất ta sẽ kiểm tra tất cả các phần tử. 10 | 11 | ## Liên kết 12 | - [Wikipedia](https://en.wikipedia.org/wiki/Linear_search) 13 | - [TutorialsPoint](https://www.tutorialspoint.com/data_structures_algorithms/linear_search_algorithm.htm) 14 | - [Youtube](https://www.youtube.com/watch?v=SGU9duLE30w) -------------------------------------------------------------------------------- /src/algorithms/search/linear-search/__test__/linearSearch.test.js: -------------------------------------------------------------------------------- 1 | import linearSearch from '../linearSearch'; 2 | 3 | describe('linearSearch', () => { 4 | it('should search all numbers in array', () => { 5 | const array = [1, 2, 4, 6, 2]; 6 | 7 | expect(linearSearch(array, 10)).toEqual([]); 8 | expect(linearSearch(array, 1)).toEqual([0]); 9 | expect(linearSearch(array, 2)).toEqual([1, 4]); 10 | }); 11 | 12 | it('should search all strings in array', () => { 13 | const array = ['a', 'b', 'a']; 14 | 15 | expect(linearSearch(array, 'c')).toEqual([]); 16 | expect(linearSearch(array, 'b')).toEqual([1]); 17 | expect(linearSearch(array, 'a')).toEqual([0, 2]); 18 | }); 19 | 20 | it('should search through objects as well', () => { 21 | const comparatorCallback = (a, b) => { 22 | if (a.key === b.key) { 23 | return 0; 24 | } 25 | 26 | return a.key <= b.key ? -1 : 1; 27 | }; 28 | 29 | const array = [ 30 | { key: 5 }, 31 | { key: 6 }, 32 | { key: 7 }, 33 | { key: 6 }, 34 | ]; 35 | 36 | expect(linearSearch(array, { key: 10 }, comparatorCallback)).toEqual([]); 37 | expect(linearSearch(array, { key: 5 }, comparatorCallback)).toEqual([0]); 38 | expect(linearSearch(array, { key: 6 }, comparatorCallback)).toEqual([1, 3]); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/algorithms/search/linear-search/linearSearch.js: -------------------------------------------------------------------------------- 1 | import Comparator from '../../../utils/Comparator'; 2 | 3 | /** 4 | * Triển khai tìm kiếm tuyến tính. 5 | * 6 | * @param {*[]} array 7 | * @param {*} seekElement 8 | * @param {function(a, b)} [comparatorCallback] 9 | * @return {number[]} 10 | */ 11 | export default function linearSearch(array, seekElement, comparatorCallback) { 12 | const comparator = new Comparator(comparatorCallback); 13 | const foundIndices = []; 14 | 15 | array.forEach((element, index) => { 16 | if (comparator.equal(element, seekElement)) { 17 | foundIndices.push(index); 18 | } 19 | }); 20 | 21 | return foundIndices; 22 | } 23 | -------------------------------------------------------------------------------- /src/algorithms/sets/cartesian-product/README.md: -------------------------------------------------------------------------------- 1 | # Tích Descartes 2 | 3 | Trong lý thuyết tập hợp, tích Descartes là một phép toán trả về một tập hợp (hoặc một tích) từ nhiều tập hợp. Đối với tập hợp A và B, tích Descartes A × B là tập hợp của tất cả các cặp có thứ tự (a, b) trong đó a ∈ A và b ∈ B. 4 | 5 | Tích Descartes `AxB` của hai tập hợp `A = {x, y, z}` và `B = {1, 2, 3}`. 6 | 7 | ![Cartesian Product of Two Sets](https://upload.wikimedia.org/wikipedia/commons/4/4e/Cartesian_Product_qtl1.svg) 8 | 9 | ## Liên kết 10 | 11 | [Wikipedia](https://en.wikipedia.org/wiki/Cartesian_product) 12 | -------------------------------------------------------------------------------- /src/algorithms/sets/cartesian-product/__test__/cartesianProduct.test.js: -------------------------------------------------------------------------------- 1 | import cartesianProduct from '../cartesianProduct'; 2 | 3 | describe('cartesianProduct', () => { 4 | it('should return null if there is not enough info for calculation', () => { 5 | const product1 = cartesianProduct([1], null); 6 | const product2 = cartesianProduct([], null); 7 | 8 | expect(product1).toBeNull(); 9 | expect(product2).toBeNull(); 10 | }); 11 | 12 | it('should calculate the product of two sets', () => { 13 | const product1 = cartesianProduct([1], [1]); 14 | const product2 = cartesianProduct([1, 2], [3, 5]); 15 | 16 | expect(product1).toEqual([[1, 1]]); 17 | expect(product2).toEqual([[1, 3], [1, 5], [2, 3], [2, 5]]); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/algorithms/sets/cartesian-product/cartesianProduct.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tích Descartes của hai tập hợp. 3 | * @param {*[]} setA 4 | * @param {*[]} setB 5 | * @return {*[]} 6 | */ 7 | export default function cartesianProduct(setA, setB) { 8 | // Kiểm tra nếu tập hợp đầu vào là rỗng. 9 | // thì trả về null vì không thể tính tích Descartes. 10 | if (!setA || !setB || !setA.length || !setB.length) { 11 | return null; 12 | } 13 | 14 | // Khởi tạo tập hợp tích. 15 | const product = []; 16 | 17 | // Bây giờ, ta đi qua tất cả phần tử của tập hợp thứ nhất và thứ hai để tạo ra tất cả các cặp có thể. 18 | for (let indexA = 0; indexA < setA.length; indexA += 1) { 19 | for (let indexB = 0; indexB < setB.length; indexB += 1) { 20 | // Thêm cặp tích hiện tại vào tập hợp tích. 21 | product.push([setA[indexA], setB[indexB]]); 22 | } 23 | } 24 | 25 | // Trả về tập hợp tích Descartes. 26 | return product; 27 | } 28 | -------------------------------------------------------------------------------- /src/algorithms/sets/combinations/combineWithRepetitions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {*[]} comboOptions 3 | * @param {number} comboLength 4 | * @return {*[]} 5 | */ 6 | export default function combineWithRepetitions(comboOptions, comboLength) { 7 | // Nếu độ dài của tổ hợp là 1 thì mỗi phần tử của mảng gốc là tổ hợp của chính nó. 8 | if (comboLength === 1) { 9 | return comboOptions.map((comboOption) => [comboOption]); 10 | } 11 | 12 | // Tạo mảng tổ hợp. 13 | const combos = []; 14 | 15 | // Ghi nhớ từng ký tự và nối chúng thành các tổ hợp có độ dài nhỏ hơn. 16 | // Không trích xuất phần tử vì ở đây được lặp lại. 17 | comboOptions.forEach((currentOption, optionIndex) => { 18 | // Tạo tổ hợp có kích thước nhỏ hơn. 19 | const smallerCombos = combineWithRepetitions( 20 | comboOptions.slice(optionIndex), 21 | comboLength - 1, 22 | ); 23 | 24 | // Nối currentOption vào tất cả tổ hợp có kích thước nhỏ hơn. 25 | smallerCombos.forEach((smallerCombo) => { 26 | combos.push([currentOption].concat(smallerCombo)); 27 | }); 28 | }); 29 | 30 | return combos; 31 | } 32 | -------------------------------------------------------------------------------- /src/algorithms/sets/combinations/combineWithoutRepetitions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {*[]} comboOptions 3 | * @param {number} comboLength 4 | * @return {*[]} 5 | */ 6 | export default function combineWithoutRepetitions(comboOptions, comboLength) { 7 | // Nếu độ dài của tổ hợp là 1 thì mỗi phần tử của mảng gốc là tổ hợp của chính nó. 8 | if (comboLength === 1) { 9 | return comboOptions.map((comboOption) => [comboOption]); 10 | } 11 | 12 | // Tạo mảng tổ hợp. 13 | const combos = []; 14 | 15 | // Trích xuất từng ký tự và nối chúng thành các tổ hợp có độ dài nhỏ hơn. 16 | // Ta cần trích xuất vì ở đây ta không được lặp lại sau khi nối. 17 | comboOptions.forEach((currentOption, optionIndex) => { 18 | // Tạo tổ hợp có kích thước nhỏ hơn. 19 | const smallerCombos = combineWithoutRepetitions( 20 | comboOptions.slice(optionIndex + 1), 21 | comboLength - 1, 22 | ); 23 | 24 | // Nối currentOption vào tất cả tổ hợp có kích thước nhỏ hơn. 25 | smallerCombos.forEach((smallerCombo) => { 26 | combos.push([currentOption].concat(smallerCombo)); 27 | }); 28 | }); 29 | 30 | return combos; 31 | } 32 | -------------------------------------------------------------------------------- /src/algorithms/sets/fisher-yates/README.md: -------------------------------------------------------------------------------- 1 | # Xáo trộn Fisher-Yates 2 | 3 | Sự xáo trộn Fisher-Yates là một thuật toán để tạo ra một hoán vị ngẫu nhiên của một chuỗi hữu hạn — nói một cách dễ hiểu, đấy là thuật toán xáo trộn chuỗi. Thuật toán hoạt động hiệu quả bằng cách đưa tất cả các phần tử vào một cái mũ (hat); nó liên tục xác định phần tử tiếp theo bằng cách rút ngẫu nhiên một phần tử ra khỏi chiếc mũ cho đến khi không còn phần tử nào. Thuật toán tạo ra một hoán vị ngẫu nhiên : mọi hoán vị đều có khả năng xảy ra như nhau. Phiên bản hiện đại của thuật toán rất hiệu quả: cần thời gian tỷ lệ thuận với số lượng các phần tử được xáo trộn và xáo trộn chúng tại chỗ. 4 | 5 | ## Liên kết 6 | 7 | [Wikipedia](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle) 8 | -------------------------------------------------------------------------------- /src/algorithms/sets/fisher-yates/__test__/fisherYates.test.js: -------------------------------------------------------------------------------- 1 | import fisherYates from '../fisherYates'; 2 | import { sortedArr } from '../../../sorting/SortTester'; 3 | import QuickSort from '../../../sorting/quick-sort/QuickSort'; 4 | 5 | describe('fisherYates', () => { 6 | it('should shuffle small arrays', () => { 7 | expect(fisherYates([])).toEqual([]); 8 | expect(fisherYates([1])).toEqual([1]); 9 | }); 10 | 11 | it('should shuffle array randomly', () => { 12 | const shuffledArray = fisherYates(sortedArr); 13 | const sorter = new QuickSort(); 14 | 15 | expect(shuffledArray.length).toBe(sortedArr.length); 16 | expect(shuffledArray).not.toEqual(sortedArr); 17 | expect(sorter.sort(shuffledArray)).toEqual(sortedArr); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/algorithms/sets/fisher-yates/fisherYates.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {*[]} originalArray 3 | * @return {*[]} 4 | */ 5 | export default function fisherYates(originalArray) { 6 | // Tạo mảng sao chép để ngăn không cho mảng ban đầu thay đổi (cho mục đích thử nghiệm). 7 | const array = originalArray.slice(0); 8 | 9 | for (let i = (array.length - 1); i > 0; i -= 1) { 10 | const randomIndex = Math.floor(Math.random() * (i + 1)); 11 | [array[i], array[randomIndex]] = [array[randomIndex], array[i]]; 12 | } 13 | 14 | return array; 15 | } 16 | -------------------------------------------------------------------------------- /src/algorithms/sets/longest-common-subsequence/README.md: -------------------------------------------------------------------------------- 1 | # Bài toán chuỗi con chung dài nhất 2 | 3 | **Vấn đề chuỗi con chung dài nhất (LCS)** là vấn đề trong việc tìm kiếm một chuỗi con chung dài nhất cho tất cả các chuỗi trong một bộ chuỗi (thường chỉ hai chuỗi). Nó khác với vấn đề về xâu con chung dài nhất ở chỗ: không giống như các xâu con, các chuỗi con không bắt buộc phải chiếm các vị trí liên tiếp trong các chuỗi ban đầu. 4 | 5 | ## Ứng dụng 6 | 7 | Bài toán chuỗi con chung dài nhất là một trong những bài toán khoa học máy tính cổ điển, là cơ sở của các chương trình so sánh dữ liệu như diff, và có các ứng dụng trong ngôn ngữ học tính toán và tin sinh học. 8 | 9 | Nó cũng được sử dụng rộng rãi bởi các hệ thống quản lý phiên bản như Git để điều chỉnh nhiều thay đổi được thực hiện cho một bộ sưu tập tệp được kiểm soát sửa đổi. 10 | 11 | ## Ví dụ 12 | 13 | - LCS của chuỗi đầu vào `ABCDGH` và `AEDFHR` là `ADH` có độ dài là 3. 14 | - LCS của chuỗi đầu vào `AGGTAB` và `GXTXAYB` là `GTAB` có độ dài là 4. 15 | 16 | ## Liên kết 17 | 18 | - [Wikipedia](https://en.wikipedia.org/wiki/Longest_common_subsequence_problem) 19 | - [YouTube](https://www.youtube.com/watch?v=NnD96abizww&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) -------------------------------------------------------------------------------- /src/algorithms/sets/longest-common-subsequence/__test__/longestCommonSubsequence.test.js: -------------------------------------------------------------------------------- 1 | import longestCommonSubsequence from '../longestCommonSubsequence'; 2 | 3 | describe('longestCommonSubsequence', () => { 4 | it('should find longest common subsequence for two strings', () => { 5 | expect(longestCommonSubsequence([''], [''])).toEqual(['']); 6 | 7 | expect(longestCommonSubsequence([''], ['A', 'B', 'C'])).toEqual(['']); 8 | 9 | expect(longestCommonSubsequence(['A', 'B', 'C'], [''])).toEqual(['']); 10 | 11 | expect(longestCommonSubsequence( 12 | ['A', 'B', 'C'], 13 | ['D', 'E', 'F', 'G'], 14 | )).toEqual(['']); 15 | 16 | expect(longestCommonSubsequence( 17 | ['A', 'B', 'C', 'D', 'G', 'H'], 18 | ['A', 'E', 'D', 'F', 'H', 'R'], 19 | )).toEqual(['A', 'D', 'H']); 20 | 21 | expect(longestCommonSubsequence( 22 | ['A', 'G', 'G', 'T', 'A', 'B'], 23 | ['G', 'X', 'T', 'X', 'A', 'Y', 'B'], 24 | )).toEqual(['G', 'T', 'A', 'B']); 25 | 26 | expect(longestCommonSubsequence( 27 | ['A', 'B', 'C', 'D', 'A', 'F'], 28 | ['A', 'C', 'B', 'C', 'F'], 29 | )).toEqual(['A', 'B', 'C', 'F']); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/algorithms/sets/longest-increasing-subsequence/README.md: -------------------------------------------------------------------------------- 1 | # Chuỗi Con Tăng Dần Dài Nhất 2 | 3 | Bài toán về chuỗi con tăng dần dài nhất là tìm chuỗi con của một chuỗi đã cho, trong đó các phần tử của chuỗi con được sắp xếp theo thứ tự, từ thấp nhất đến cao nhất, và đấy là chuỗi con dài nhất có thể. Chuỗi con này không nhất thiết phải liền kề hoặc duy nhất. 4 | 5 | ## Độ phức tạp 6 | 7 | Chuỗi con tăng dần dài nhất có thời gian giải là `O(n log n)`, với `n` là độ dài chuỗi đầu vào. 8 | 9 | Phương pháp quy hoạch động có độ phức tạp là `O(n*n)`. 10 | 11 | ## Ví dụ 12 | 13 | Ta có 16 số hạng đầu tiên của chuỗi nhị phân Van der Corput: 14 | 15 | ```text 16 | 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 17 | ``` 18 | 19 | có chuỗi con tăng dần dài nhất là: 20 | 21 | ```text 22 | 0, 2, 6, 9, 11, 15. 23 | ``` 24 | 25 | Chuỗi con tăng dần dài nhất trong ví dụ này không phải là duy nhất, ví dụ: 26 | 27 | ```text 28 | 0, 4, 6, 9, 11, 15 or 29 | 0, 2, 6, 9, 13, 15 or 30 | 0, 4, 6, 9, 13, 15 31 | ``` 32 | 33 | là các chuỗi con tăng dần khác có độ dài bằng nhau trong cùng một chuỗi đầu vào. 34 | 35 | ## Liên kết 36 | 37 | - [Wikipedia](https://en.wikipedia.org/wiki/Longest_increasing_subsequence) 38 | - [Dynamic Programming Approach on YouTube](https://www.youtube.com/watch?v=CE2b_-XfVDk&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) -------------------------------------------------------------------------------- /src/algorithms/sets/longest-increasing-subsequence/__test__/dpLongestIncreasingSubsequence.test.js: -------------------------------------------------------------------------------- 1 | import dpLongestIncreasingSubsequence from '../dpLongestIncreasingSubsequence'; 2 | 3 | describe('dpLongestIncreasingSubsequence', () => { 4 | it('should find longest increasing subsequence length', () => { 5 | // Should be: 6 | // 9 or 7 | // 8 or 8 | // 7 or 9 | // 6 or 10 | // ... 11 | expect(dpLongestIncreasingSubsequence([ 12 | 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 13 | ])).toBe(1); 14 | 15 | // Should be: 16 | // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 17 | expect(dpLongestIncreasingSubsequence([ 18 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 19 | ])).toBe(10); 20 | 21 | // Should be: 22 | // -1, 0, 2, 3 23 | expect(dpLongestIncreasingSubsequence([ 24 | 3, 4, -1, 0, 6, 2, 3, 25 | ])).toBe(4); 26 | 27 | // Should be: 28 | // 0, 2, 6, 9, 11, 15 or 29 | // 0, 4, 6, 9, 11, 15 or 30 | // 0, 2, 6, 9, 13, 15 or 31 | // 0, 4, 6, 9, 13, 15 32 | expect(dpLongestIncreasingSubsequence([ 33 | 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15, 34 | ])).toBe(6); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/algorithms/sets/permutations/README.md: -------------------------------------------------------------------------------- 1 | # Hoán vị 2 | 3 | Khi thứ tự không quan trọng, ta gọi đấy là **Tổ hợp**. 4 | 5 | Khi thứ tự rất quan trọng, ta gọi đấy là **Hoán vị**. 6 | 7 | **Hoán vị là 472**. Ta cần quan tâm đến thứ tự, nó không phải là `724` hay `247`, nó chính xác phải là `4-7-2`. 8 | 9 | ## Hoán vị không có vòng lặp 10 | 11 | Một hoán vị là một sự sắp xếp lại các phần tử trong danh sách trật tự `S`, là song ánh của tập `S` với chính nó. 12 | 13 | Ta có hoán vị của một chuỗi `ABC` là: 14 | 15 | `ABC ACB BAC BCA CBA CAB` 16 | 17 | **Số lượng hoán vị** 18 | 19 | ``` 20 | n * (n-1) * (n -2) * ... * 1 = n! 21 | ``` 22 | 23 | ## Hoán vị với vòng lặp 24 | 25 | Khi sử dụng lặp lại, chúng ta có hoán vị với các lần lặp lại. 26 | Ví dụ bên dưới, nó có thể là `333`. 27 | 28 | ![Permutation Lock](https://www.mathsisfun.com/combinatorics/images/combination-lock.jpg) 29 | 30 | **Số lượng hoán vị** 31 | 32 | ``` 33 | n * n * n ... (r times) = n^r 34 | ``` 35 | 36 | ## Cheat Sheets 37 | 38 | Hoán vị 39 | 40 | ![Permutations Cheat Sheet](https://cdn-images-1.medium.com/max/2000/1*JNK-n0Pt0Vbxk0lxVpgT5A.png) 41 | 42 | Tổ hợp 43 | 44 | ![Combinations Cheat Sheet](https://cdn-images-1.medium.com/max/2000/1*7cFRn8jW4g_91YgDAbmxRQ.png) 45 | 46 | Ý tưởng thuật toán hoán vị/tổ hợp. 47 | 48 | ![Algorithms Idea](https://cdn-images-1.medium.com/max/2000/1*vLsSsZMnesCFPCYTYMbxrQ.png) 49 | 50 | ## References 51 | 52 | - [Math Is Fun](https://www.mathsisfun.com/combinatorics/combinations-permutations.html) 53 | - [Permutations/combinations cheat sheets](https://medium.com/@trekhleb/permutations-combinations-algorithms-cheat-sheet-68c14879aba5) 54 | -------------------------------------------------------------------------------- /src/algorithms/sets/permutations/__test__/permutateWithRepetitions.test.js: -------------------------------------------------------------------------------- 1 | import permutateWithRepetitions from '../permutateWithRepetitions'; 2 | 3 | describe('permutateWithRepetitions', () => { 4 | it('should permutate string with repetition', () => { 5 | const permutations1 = permutateWithRepetitions(['A']); 6 | expect(permutations1).toEqual([ 7 | ['A'], 8 | ]); 9 | 10 | const permutations2 = permutateWithRepetitions(['A', 'B']); 11 | expect(permutations2).toEqual([ 12 | ['A', 'A'], 13 | ['A', 'B'], 14 | ['B', 'A'], 15 | ['B', 'B'], 16 | ]); 17 | 18 | const permutations3 = permutateWithRepetitions(['A', 'B', 'C']); 19 | expect(permutations3).toEqual([ 20 | ['A', 'A', 'A'], 21 | ['A', 'A', 'B'], 22 | ['A', 'A', 'C'], 23 | ['A', 'B', 'A'], 24 | ['A', 'B', 'B'], 25 | ['A', 'B', 'C'], 26 | ['A', 'C', 'A'], 27 | ['A', 'C', 'B'], 28 | ['A', 'C', 'C'], 29 | ['B', 'A', 'A'], 30 | ['B', 'A', 'B'], 31 | ['B', 'A', 'C'], 32 | ['B', 'B', 'A'], 33 | ['B', 'B', 'B'], 34 | ['B', 'B', 'C'], 35 | ['B', 'C', 'A'], 36 | ['B', 'C', 'B'], 37 | ['B', 'C', 'C'], 38 | ['C', 'A', 'A'], 39 | ['C', 'A', 'B'], 40 | ['C', 'A', 'C'], 41 | ['C', 'B', 'A'], 42 | ['C', 'B', 'B'], 43 | ['C', 'B', 'C'], 44 | ['C', 'C', 'A'], 45 | ['C', 'C', 'B'], 46 | ['C', 'C', 'C'], 47 | ]); 48 | 49 | const permutations4 = permutateWithRepetitions(['A', 'B', 'C', 'D']); 50 | expect(permutations4.length).toBe(4 * 4 * 4 * 4); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /src/algorithms/sets/permutations/permutateWithRepetitions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {*[]} permutationOptions 3 | * @param {number} permutationLength 4 | * @return {*[]} 5 | */ 6 | export default function permutateWithRepetitions( 7 | permutationOptions, 8 | permutationLength = permutationOptions.length, 9 | ) { 10 | if (permutationLength === 1) { 11 | return permutationOptions.map((permutationOption) => [permutationOption]); 12 | } 13 | 14 | // Khởi tạo mảng hoán vị. 15 | const permutations = []; 16 | 17 | // Lấy hoán vị nhỏ nhất. 18 | const smallerPermutations = permutateWithRepetitions( 19 | permutationOptions, 20 | permutationLength - 1, 21 | ); 22 | 23 | // Đi tới tất cả lựa chọn và kết hợp nó với hoán vị nhỏ nhất. 24 | permutationOptions.forEach((currentOption) => { 25 | smallerPermutations.forEach((smallerPermutation) => { 26 | permutations.push([currentOption].concat(smallerPermutation)); 27 | }); 28 | }); 29 | 30 | return permutations; 31 | } 32 | -------------------------------------------------------------------------------- /src/algorithms/sets/permutations/permutateWithoutRepetitions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {*[]} permutationOptions 3 | * @return {*[]} 4 | */ 5 | export default function permutateWithoutRepetitions(permutationOptions) { 6 | if (permutationOptions.length === 1) { 7 | return [permutationOptions]; 8 | } 9 | 10 | // Khởi tạo mảng hoán vị. 11 | const permutations = []; 12 | 13 | // Lấy tất cả hoán vị cho permutationOptions ngoại trừ phần tử đầu tiên. 14 | const smallerPermutations = permutateWithoutRepetitions(permutationOptions.slice(1)); 15 | 16 | // Thêm lựa chọn đầu tiên vào mọi vị trí có thể có của mọi hoán vị nhỏ hơn. 17 | const firstOption = permutationOptions[0]; 18 | 19 | for (let permIndex = 0; permIndex < smallerPermutations.length; permIndex += 1) { 20 | const smallerPermutation = smallerPermutations[permIndex]; 21 | 22 | // Thêm lựa chọn đầu tiên vào mọi vị trí có thể có của smallerPermutation. 23 | for (let positionIndex = 0; positionIndex <= smallerPermutation.length; positionIndex += 1) { 24 | const permutationPrefix = smallerPermutation.slice(0, positionIndex); 25 | const permutationSuffix = smallerPermutation.slice(positionIndex); 26 | permutations.push(permutationPrefix.concat([firstOption], permutationSuffix)); 27 | } 28 | } 29 | 30 | return permutations; 31 | } 32 | -------------------------------------------------------------------------------- /src/algorithms/sets/power-set/bwPowerSet.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tìm tập luỹ thừa của tập hợp bằng giải pháp BITWISE. 3 | * 4 | * @param {*[]} originalSet 5 | * @return {*[][]} 6 | */ 7 | export default function bwPowerSet(originalSet) { 8 | const subSets = []; 9 | 10 | // Ta sẽ có 2^n kết hợp khả dụng (với n là độ dài của tập hợp gốc). 11 | // Bởi vì tất cả phần tử của tập hợp gốc sẽ có lựa chọn là thuộc 12 | // hoặc không thuộc (2 lựa chọn với mỗi phần tử). 13 | const numberOfCombinations = 2 ** originalSet.length; 14 | 15 | // Mỗi số sẽ được biểu diễn nhị phân trong đoạn từ 0 đến 2^n, ta cần làm: 16 | // biểu diễn bởi bit(0 hoặc 1) rằng phần tử nằm trong tập con hoặc không. 17 | // Ví dụ, với tập hợp {1, 2, 3} có dạng nhị phần là 0b010 sẽ chỉ có 18 | // phần tử "2" nằm trong tập con hiện tại. 19 | for (let combinationIndex = 0; combinationIndex < numberOfCombinations; combinationIndex += 1) { 20 | const subSet = []; 21 | 22 | for (let setElementIndex = 0; setElementIndex < originalSet.length; setElementIndex += 1) { 23 | // Quyết định ta có cần thêm phần tử hiện tại vào tập con hay không. 24 | if (combinationIndex & (1 << setElementIndex)) { 25 | subSet.push(originalSet[setElementIndex]); 26 | } 27 | } 28 | 29 | // Thêm tập con vào danh sách tất cả tập con. 30 | subSets.push(subSet); 31 | } 32 | 33 | return subSets; 34 | } 35 | -------------------------------------------------------------------------------- /src/algorithms/sets/shortest-common-supersequence/README.md: -------------------------------------------------------------------------------- 1 | # Chuỗi Chung Ngắn Nhất 2 | 3 | Chuỗi chung ngắn nhất (SCS) của hai chuỗi `X` và `Y` là chuỗi ngắn nhất chứa `X` và `Y` như là chuỗi con. 4 | 5 | Nói đơn giản, là cho hai chuỗi `str1` và `str2`, hãy tìm chuỗi ngắn nhất chứa cả hai chuỗi `str1` và `str2`. 6 | 7 | Bài toán này có liên quan chặc chẽ với tìm chuỗi con chung dài nhất (LCS). 8 | 9 | ## Ví dụ 10 | 11 | ``` 12 | Input: str1 = "geek", str2 = "eke" 13 | Output: "geeke" 14 | Input: str1 = "AGGTAB", str2 = "GXTXAYB" 15 | Output: "AGXGTXAYB" 16 | ``` 17 | 18 | ## Liên kết 19 | 20 | - [GeeksForGeeks](https://www.geeksforgeeks.org/shortest-common-supersequence/) -------------------------------------------------------------------------------- /src/algorithms/sets/shortest-common-supersequence/__test__/shortestCommonSupersequence.test.js: -------------------------------------------------------------------------------- 1 | import shortestCommonSupersequence from '../shortestCommonSupersequence'; 2 | 3 | describe('shortestCommonSupersequence', () => { 4 | it('should find shortest common supersequence of two sequences', () => { 5 | // LCS (longest common subsequence) is empty 6 | expect(shortestCommonSupersequence( 7 | ['A', 'B', 'C'], 8 | ['D', 'E', 'F'], 9 | )).toEqual(['A', 'B', 'C', 'D', 'E', 'F']); 10 | 11 | // LCS (longest common subsequence) is "EE" 12 | expect(shortestCommonSupersequence( 13 | ['G', 'E', 'E', 'K'], 14 | ['E', 'K', 'E'], 15 | )).toEqual(['G', 'E', 'K', 'E', 'K']); 16 | 17 | // LCS (longest common subsequence) is "GTAB" 18 | expect(shortestCommonSupersequence( 19 | ['A', 'G', 'G', 'T', 'A', 'B'], 20 | ['G', 'X', 'T', 'X', 'A', 'Y', 'B'], 21 | )).toEqual(['A', 'G', 'G', 'X', 'T', 'X', 'A', 'Y', 'B']); 22 | 23 | // LCS (longest common subsequence) is "BCBA". 24 | expect(shortestCommonSupersequence( 25 | ['A', 'B', 'C', 'B', 'D', 'A', 'B'], 26 | ['B', 'D', 'C', 'A', 'B', 'A'], 27 | )).toEqual(['A', 'B', 'D', 'C', 'A', 'B', 'D', 'A', 'B']); 28 | 29 | // LCS (longest common subsequence) is "BDABA". 30 | expect(shortestCommonSupersequence( 31 | ['B', 'D', 'C', 'A', 'B', 'A'], 32 | ['A', 'B', 'C', 'B', 'D', 'A', 'B', 'A', 'C'], 33 | )).toEqual(['A', 'B', 'C', 'B', 'D', 'C', 'A', 'B', 'A', 'C']); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/algorithms/sorting/Sort.js: -------------------------------------------------------------------------------- 1 | import Comparator from '../../utils/Comparator'; 2 | 3 | /** 4 | * @typedef {Object} SorterCallbacks 5 | * @property {function(a: *, b: *)} compareCallback - Nếu được cung cấp, 6 | * thì tất cả phần tử sẽ được so sánh thông qua lệnh callback. 7 | * @property {function(a: *)} visitingCallback - Nếu được cung cấp, 8 | * thì nó sẽ được gọi mỗi khi hàm sắp xếp truy cập vào phần tử tiếp theo. 9 | */ 10 | 11 | export default class Sort { 12 | constructor(originalCallbacks) { 13 | this.callbacks = Sort.initSortingCallbacks(originalCallbacks); 14 | this.comparator = new Comparator(this.callbacks.compareCallback); 15 | } 16 | 17 | /** 18 | * @param {SorterCallbacks} originalCallbacks 19 | * @returns {SorterCallbacks} 20 | */ 21 | static initSortingCallbacks(originalCallbacks) { 22 | const callbacks = originalCallbacks || {}; 23 | const stubCallback = () => { }; 24 | 25 | callbacks.compareCallback = callbacks.compareCallback || undefined; 26 | callbacks.visitingCallback = callbacks.visitingCallback || stubCallback; 27 | 28 | return callbacks; 29 | } 30 | 31 | sort() { 32 | throw new Error('sort method must be implemented'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/algorithms/sorting/__test__/Sort.test.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | 3 | describe('Sort', () => { 4 | it('should throw an error when trying to call Sort.sort() method directly', () => { 5 | function doForbiddenSort() { 6 | const sorter = new Sort(); 7 | sorter.sort(); 8 | } 9 | 10 | expect(doForbiddenSort).toThrow(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/algorithms/sorting/bubble-sort/BubbleSort.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | 3 | export default class BubbleSort extends Sort { 4 | sort(originalArray) { 5 | // Gắn cờ lưu giữ thông tin về việc hoán đổi có xảy ra hay không. 6 | let swapped = false; 7 | // Sao chép mảng ban đầu để tránh việc thay đổi nó. 8 | const array = [...originalArray]; 9 | 10 | for (let i = 1; i < array.length; i += 1) { 11 | swapped = false; 12 | 13 | // Gọi hàm callback. 14 | this.callbacks.visitingCallback(array[i]); 15 | 16 | for (let j = 0; j < array.length - i; j += 1) { 17 | // Gọi hàm callback. 18 | this.callbacks.visitingCallback(array[j]); 19 | 20 | // Hoán đổi phần tử nếu nó sai thứ tự. 21 | if (this.comparator.lessThan(array[j + 1], array[j])) { 22 | [array[j], array[j + 1]] = [array[j + 1], array[j]]; 23 | 24 | // Đánh dấu đã đổi. 25 | swapped = true; 26 | } 27 | } 28 | 29 | // Nếu không có hoán đổi thì mảng đã được sắp xếp và không cần tiếp tục. 30 | if (!swapped) { 31 | return array; 32 | } 33 | } 34 | 35 | return array; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/algorithms/sorting/bubble-sort/README.md: -------------------------------------------------------------------------------- 1 | # Sắp xếp nổi bọt 2 | 3 | Sắp xếp nổi bọt là một thuật toán sắp xếp đơn giản bằng việc lặp đi lặp các bước so sánh các phần tử liền kề và hoán đổi chúng nếu chúng không theo đúng thứ tự (sắp xếp tăng dần hoặc giảm dần). Các hành động này được lặp lại cho đến khi không cần phải hoán đổi nữa, tức là lúc này danh sách đã được sắp xếp. 4 | 5 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/c/c8/Bubble-sort-example-300px.gif) 6 | 7 | ## Độ phức tạp 8 | 9 | | Name | Best | Average | Worst | Memory | Stable | Comments | 10 | | --------------------- | :-------------: | :-----------------: | :-----------------: | :-------: | :-------: | :-------- | 11 | | **Bubble sort** | n | n2 | n2 | 1 | Yes | | 12 | 13 | ## Liên kết 14 | 15 | - [Wikipedia](https://en.wikipedia.org/wiki/Bubble_sort) 16 | - [YouTube](https://www.youtube.com/watch?v=6Gv8vg0kcHc&index=27&t=0s&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) -------------------------------------------------------------------------------- /src/algorithms/sorting/heap-sort/HeapSort.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | import MinHeap from '../../../data-structures/heap/MinHeap'; 3 | 4 | export default class HeapSort extends Sort { 5 | sort(originalArray) { 6 | const sortedArray = []; 7 | const minHeap = new MinHeap(this.callbacks.compareCallback); 8 | 9 | // Chèn tất cả phần tử trong mảng vào heap. 10 | originalArray.forEach((element) => { 11 | // Gọi hàm callback. 12 | this.callbacks.visitingCallback(element); 13 | 14 | minHeap.add(element); 15 | }); 16 | 17 | // Giờ ta có min heap với phần tử nhỏ nhất luôn ở trên cùng. 18 | // Kiểm tra vòng từng phần tử cực tiểu và tạo thành mảng đã sắp xếp. 19 | while (!minHeap.isEmpty()) { 20 | const nextMinElement = minHeap.poll(); 21 | 22 | // Gọi hàm callback. 23 | this.callbacks.visitingCallback(nextMinElement); 24 | 25 | sortedArray.push(nextMinElement); 26 | } 27 | 28 | return sortedArray; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/algorithms/sorting/heap-sort/README.md: -------------------------------------------------------------------------------- 1 | # Sắp xếp vun đống 2 | 3 | Sắp xếp vun đống thuộc loại sắp xếp so sánh. 4 | Sắp xếp vun đống có thể coi là một cải tiến của sắp xếp chọn: nó chia đầu vào thành vùng đã sắp xếp và vùng chưa được sắp xếp, sau đó thu nhỏ vùng chưa sắp xếp bằng cách lấy phần tử lớn nhất và thêm nó vào mảng đã sắp xếp. Lặp đi lặp lại nhiều lần để có được mảng đã sắp xếp hoàn toàn. Cải tiến ở đây nằm ở việc sử dụng cấu trúc dữ liệu heap chứ vẫn là tìm kiếm giá trị lớn nhất theo thời gian tuyến tính. 5 | 6 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/1/1b/Sorting_heapsort_anim.gif) 7 | 8 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/4/4d/Heapsort-example.gif) 9 | 10 | ## Độ phức tạp 11 | 12 | | Name | Best | Average | Worst | Memory | Stable | Comments | 13 | | --------------------- | :-------------: | :-----------------: | :-----------------: | :-------: | :-------: | :-------- | 14 | | **Heap sort** | n log(n) | n log(n) | n log(n) | 1 | No | | 15 | 16 | ## Liên kết 17 | 18 | [Wikipedia](https://en.wikipedia.org/wiki/Heapsort) 19 | -------------------------------------------------------------------------------- /src/algorithms/sorting/insertion-sort/InsertionSort.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | 3 | export default class InsertionSort extends Sort { 4 | sort(originalArray) { 5 | const array = [...originalArray]; 6 | 7 | // Đi qua tất cả phần tử của mảng... 8 | for (let i = 1; i < array.length; i += 1) { 9 | let currentIndex = i; 10 | 11 | // Gọi hàm callback. 12 | this.callbacks.visitingCallback(array[i]); 13 | 14 | // Kiểm tra xem phần tử trước đó có lớn hơn phần tử hiện tại hay không. 15 | // Nếu có, hãy hoán đổi hai phần tử. 16 | while ( 17 | array[currentIndex - 1] !== undefined 18 | && this.comparator.lessThan(array[currentIndex], array[currentIndex - 1]) 19 | ) { 20 | // Gọi hàm callback. 21 | this.callbacks.visitingCallback(array[currentIndex - 1]); 22 | 23 | // Hoán đổi phần tử. 24 | [ 25 | array[currentIndex - 1], 26 | array[currentIndex], 27 | ] = [ 28 | array[currentIndex], 29 | array[currentIndex - 1], 30 | ]; 31 | 32 | // Chuyển sang chỉ mục bên trái. 33 | currentIndex -= 1; 34 | } 35 | } 36 | 37 | return array; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/algorithms/sorting/insertion-sort/README.md: -------------------------------------------------------------------------------- 1 | # Sắp xếp chèn 2 | 3 | Sắp xếp chèn là thuật toán sắp xếp đơn giản để xây dựng một mảng (danh sách) cuối cùng bằng việc thêm từng mục tại một thời điểm. Nó kém hiệu quả hơn nhiều trên các danh sách lớn so với các thuật toán nâng cao hơn như quicksort, heapsort hoặc merge sort. 4 | 5 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/4/42/Insertion_sort.gif) 6 | 7 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/0/0f/Insertion-sort-example-300px.gif) 8 | 9 | ## Độ phức tạp 10 | 11 | | Name | Best | Average | Worst | Memory | Stable | Comments | 12 | | --------------------- | :-------------: | :-----------------: | :-----------------: | :-------: | :-------: | :-------- | 13 | | **Insertion sort** | n | n2 | n2 | 1 | Yes | | 14 | 15 | ## Liên kết 16 | 17 | [Wikipedia](https://en.wikipedia.org/wiki/Insertion_sort) -------------------------------------------------------------------------------- /src/algorithms/sorting/merge-sort/README.md: -------------------------------------------------------------------------------- 1 | # Sắp xếp trộn 2 | 3 | Trong khoa học máy tính, sắp xếp trộn là một thuật toán sắp xếp đa dụng có hiệu quả rất cao. Nó được xếp vào thể loại sắp xếp so sánh. Trong hầu hết trường hợp nó sẽ bảo tồn thứ tự đầu vào của các phần tử bằng nhau sau khi sắp xếp. Sắp xếp trộn là một ví dụ về thuật toán chia để trị do John von Neumann phát minh năm 1945. 4 | 5 | Ví dụ về sắp xếp trộn. Nó chia danh sách thành các đơn vị nhỏ nhất (1 phần tử), so sánh từng phần tử giữa các đơn vị liền kề, sắp xếp và hợp nhất hai danh sách liền kề. Cuối cùng tất cả các phần tử được sắp xếp theo thứ tự. 6 | 7 | ![Merge Sort](https://upload.wikimedia.org/wikipedia/commons/c/cc/Merge-sort-example-300px.gif) 8 | 9 | Sắp xếp trộn bằng đệ quy trên một mảng 7 phần tử. Đây là những bước sắp xếp trộn từ dưới lên. 10 | 11 | ![Merge Sort](https://upload.wikimedia.org/wikipedia/commons/e/e6/Merge_sort_algorithm_diagram.svg) 12 | 13 | ## Độ phức tạp 14 | 15 | | Name | Best | Average | Worst | Memory | Stable | Comments | 16 | | --------------------- | :-------------: | :-----------------: | :-----------------: | :-------: | :-------: | :-------- | 17 | | **Merge sort** | n log(n) | n log(n) | n log(n) | n | Yes | | 18 | 19 | ## Liên kết 20 | 21 | - [Wikipedia](https://en.wikipedia.org/wiki/Merge_sort) 22 | - [YouTube](https://www.youtube.com/watch?v=KF2j-9iSf4Q&index=27&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 23 | -------------------------------------------------------------------------------- /src/algorithms/sorting/quick-sort/QuickSort.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | 3 | export default class QuickSort extends Sort { 4 | /** 5 | * @param {*[]} originalArray 6 | * @return {*[]} 7 | */ 8 | sort(originalArray) { 9 | // Sao chép mảng ban đầu để tránh việc thay đổi nó. 10 | const array = [...originalArray]; 11 | 12 | // Nếu mảng rỗng hoặc chỉ có một phần tử trả về mảng ban đầu không cần sắp xếp. 13 | if (array.length <= 1) { 14 | return array; 15 | } 16 | 17 | // Tạo mảng trái và mảng phải. 18 | const leftArray = []; 19 | const rightArray = []; 20 | 21 | // Lấy phần tử đầu tiên của mảng làm chốt. 22 | const pivotElement = array.shift(); 23 | const centerArray = [pivotElement]; 24 | 25 | // Chia mảng thành các mảng con bên trái, bên phải và ở giữa. 26 | while (array.length) { 27 | const currentElement = array.shift(); 28 | 29 | // Gọi hàm callback. 30 | this.callbacks.visitingCallback(currentElement); 31 | 32 | if (this.comparator.equal(currentElement, pivotElement)) { 33 | centerArray.push(currentElement); 34 | } else if (this.comparator.lessThan(currentElement, pivotElement)) { 35 | leftArray.push(currentElement); 36 | } else { 37 | rightArray.push(currentElement); 38 | } 39 | } 40 | 41 | // Sắp xếp mảng trái và phải 42 | const leftArraySorted = this.sort(leftArray); 43 | const rightArraySorted = this.sort(rightArray); 44 | 45 | // Giờ ta nối mảng bên trái đã sắp xếp với mảng ở giữa và mảng bên phải đã sắp xếp. 46 | return leftArraySorted.concat(centerArray, rightArraySorted); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/algorithms/sorting/quick-sort/README.md: -------------------------------------------------------------------------------- 1 | # Sắp xếp nhanh 2 | 3 | Sắp xếp nhanh là một giải thuật chia để trị. 4 | Sắp xếp nhanh chia một mảng lớn thành hai mảng con nhỏ hơn: mảng các phần tử nhỏ và các phần tử lớn. Sắp xếp nhanh có thể thiết kế bằng đệ quy. 5 | 6 | Các bước : 7 | 8 | 1. Chọn một phần tử gọi là chốt, từ mảng. 9 | 2. Phân vùng: sắp xếp lại thứ tự mảng để tất cả các phần tử có giá trị nhỏ hơn chốt đều nằm ở trước chốt, trong khi các phần tử có giá trị lớn hơn chốt đều đứng sau nó (các giá trị bằng chốt có thể nằm ở trước hay sau đều được). Sau khi phân vùng, chốt sẽ ở vị trí cuối cùng cùng nó. Đây được gọi là thao tác phân vùng. 10 | 3. Áp dụng đệ quy các bước trên cho mảng con gồm các phần tử có giá trị nhỏ hơn và cả mảng con gồm các phần tử có giá trị lớn hơn. 11 | 12 | Ảnh động mô tả giải thuật sắp xếp nhanh, dòng ngang là giá trị chốt. 13 | 14 | ![Quicksort](https://upload.wikimedia.org/wikipedia/commons/6/6a/Sorting_quicksort_anim.gif) 15 | 16 | ## Độ phức tạp 17 | 18 | | Name | Best | Average | Worst | Memory | Stable | Comments | 19 | | --------------------- | :-------------: | :-----------------: | :-----------------: | :-------: | :-------: | :-------- | 20 | | **Quick sort** | n log(n) | n log(n) | n2 | log(n) | No | Quicksort is usually done in-place with O(log(n)) stack space | 21 | 22 | ## Liên kết 23 | 24 | - [Wikipedia](https://en.wikipedia.org/wiki/Quicksort) 25 | - [YouTube](https://www.youtube.com/watch?v=SLauY6PpjW4&index=28&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) -------------------------------------------------------------------------------- /src/algorithms/sorting/radix-sort/__test__/RadixSort.test.js: -------------------------------------------------------------------------------- 1 | import RadixSort from '../RadixSort'; 2 | import { SortTester } from '../../SortTester'; 3 | 4 | // Complexity constants. 5 | const ARRAY_OF_STRINGS_VISIT_COUNT = 24; 6 | const ARRAY_OF_INTEGERS_VISIT_COUNT = 77; 7 | describe('RadixSort', () => { 8 | it('should sort array', () => { 9 | SortTester.testSort(RadixSort); 10 | }); 11 | 12 | it('should visit array of strings n (number of strings) x m (length of longest element) times', () => { 13 | SortTester.testAlgorithmTimeComplexity( 14 | RadixSort, 15 | ['zzz', 'bb', 'a', 'rr', 'rrb', 'rrba'], 16 | ARRAY_OF_STRINGS_VISIT_COUNT, 17 | ); 18 | }); 19 | 20 | it('should visit array of integers n (number of elements) x m (length of longest integer) times', () => { 21 | SortTester.testAlgorithmTimeComplexity( 22 | RadixSort, 23 | [3, 1, 75, 32, 884, 523, 4343456, 232, 123, 656, 343], 24 | ARRAY_OF_INTEGERS_VISIT_COUNT, 25 | ); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/algorithms/sorting/selection-sort/README.md: -------------------------------------------------------------------------------- 1 | # Sắp xếp chọn 2 | 3 | Sắp xếp chọn là một thuật toán sắp xếp, cụ thể là sắp xếp so sánh tại chỗ. Nó có độ phức tạp thời gian là O (n2), khiến nó không hiệu quả với các danh sách lớn và thường hoạt động kém hơn so với các thuật toán sắp xếp tương tự. 4 | 5 | Sắp xếp chọn được quan tâm vì tính đơn giản của nó và lợi thế về hiệu suất so với các thuật toán phức tạp hơn trong một số tình huống nhất định, đặc biệt khi bộ nhớ phụ bị hạn chế. 6 | 7 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/b/b0/Selection_sort_animation.gif) 8 | 9 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/9/94/Selection-Sort-Animation.gif) 10 | 11 | ## Độ phức tạp 12 | 13 | | Name | Best | Average | Worst | Memory | Stable | Comments | 14 | | --------------------- | :-------------: | :-----------------: | :-----------------: | :-------: | :-------: | :-------- | 15 | | **Selection sort** | n2 | n2 | n2 | 1 | No | | 16 | 17 | ## Liên kết 18 | 19 | [Wikipedia](https://en.wikipedia.org/wiki/Selection_sort) -------------------------------------------------------------------------------- /src/algorithms/sorting/selection-sort/SelectionSort.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | 3 | export default class SelectionSort extends Sort { 4 | sort(originalArray) { 5 | // Sao chép mảng ban đầu để tránh việc thay đổi nó. 6 | const array = [...originalArray]; 7 | 8 | for (let i = 0; i < array.length - 1; i += 1) { 9 | let minIndex = i; 10 | 11 | // Gọi hàm callback. 12 | this.callbacks.visitingCallback(array[i]); 13 | 14 | // Tìm phần tử nhỏ nhất trong phần còn lại của mảng. 15 | for (let j = i + 1; j < array.length; j += 1) { 16 | // Gọi hàm callback. 17 | this.callbacks.visitingCallback(array[j]); 18 | 19 | if (this.comparator.lessThan(array[j], array[minIndex])) { 20 | minIndex = j; 21 | } 22 | } 23 | 24 | // Nếu phần tử nhỏ nhất mới được tìm thấy thì hãy hoán đổi nó với phần tử thứ i hiện tại. 25 | if (minIndex !== i) { 26 | [array[i], array[minIndex]] = [array[minIndex], array[i]]; 27 | } 28 | } 29 | 30 | return array; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/algorithms/sorting/shell-sort/ShellSort.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | 3 | export default class ShellSort extends Sort { 4 | sort(originalArray) { 5 | // Sao chép mảng ban đầu để tránh việc thay đổi nó. 6 | const array = [...originalArray]; 7 | 8 | // Xác định khoảng cách gap. 9 | let gap = Math.floor(array.length / 2); 10 | 11 | // Khi gap lớn hơn 0 thì thực hiện so sánh và hoán đổi các phần tử. 12 | while (gap > 0) { 13 | // Đi đến và so sánh tất cả các cặp phần tử ở xa. 14 | for (let i = 0; i < (array.length - gap); i += 1) { 15 | let currentIndex = i; 16 | let gapShiftedIndex = i + gap; 17 | 18 | while (currentIndex >= 0) { 19 | // Gọi hàm callback. 20 | this.callbacks.visitingCallback(array[currentIndex]); 21 | 22 | // So sánh và hoán đổi các phần tử của mảng nếu cần. 23 | if (this.comparator.lessThan(array[gapShiftedIndex], array[currentIndex])) { 24 | const tmp = array[currentIndex]; 25 | array[currentIndex] = array[gapShiftedIndex]; 26 | array[gapShiftedIndex] = tmp; 27 | } 28 | 29 | gapShiftedIndex = currentIndex; 30 | currentIndex -= gap; 31 | } 32 | } 33 | 34 | // Thu hẹp gap. 35 | gap = Math.floor(gap / 2); 36 | } 37 | 38 | // Trả về bản sao đã sắp xếp của mảng ban đầu. 39 | return array; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/algorithms/sorting/shell-sort/__test__/ShellSort.test.js: -------------------------------------------------------------------------------- 1 | import ShellSort from '../ShellSort'; 2 | import { 3 | equalArr, 4 | notSortedArr, 5 | reverseArr, 6 | sortedArr, 7 | SortTester, 8 | } from '../../SortTester'; 9 | 10 | // Complexity constants. 11 | const SORTED_ARRAY_VISITING_COUNT = 320; 12 | const NOT_SORTED_ARRAY_VISITING_COUNT = 320; 13 | const REVERSE_SORTED_ARRAY_VISITING_COUNT = 320; 14 | const EQUAL_ARRAY_VISITING_COUNT = 320; 15 | 16 | describe('ShellSort', () => { 17 | it('should sort array', () => { 18 | SortTester.testSort(ShellSort); 19 | }); 20 | 21 | it('should sort array with custom comparator', () => { 22 | SortTester.testSortWithCustomComparator(ShellSort); 23 | }); 24 | 25 | it('should sort negative numbers', () => { 26 | SortTester.testNegativeNumbersSort(ShellSort); 27 | }); 28 | 29 | it('should visit EQUAL array element specified number of times', () => { 30 | SortTester.testAlgorithmTimeComplexity( 31 | ShellSort, 32 | equalArr, 33 | EQUAL_ARRAY_VISITING_COUNT, 34 | ); 35 | }); 36 | 37 | it('should visit SORTED array element specified number of times', () => { 38 | SortTester.testAlgorithmTimeComplexity( 39 | ShellSort, 40 | sortedArr, 41 | SORTED_ARRAY_VISITING_COUNT, 42 | ); 43 | }); 44 | 45 | it('should visit NOT SORTED array element specified number of times', () => { 46 | SortTester.testAlgorithmTimeComplexity( 47 | ShellSort, 48 | notSortedArr, 49 | NOT_SORTED_ARRAY_VISITING_COUNT, 50 | ); 51 | }); 52 | 53 | it('should visit REVERSE SORTED array element specified number of times', () => { 54 | SortTester.testAlgorithmTimeComplexity( 55 | ShellSort, 56 | reverseArr, 57 | REVERSE_SORTED_ARRAY_VISITING_COUNT, 58 | ); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /src/algorithms/string/hamming-distance/README.md: -------------------------------------------------------------------------------- 1 | # Khoảng cách Hamming 2 | 3 | Trong lý thuyết thông tin, Khoảng cách Hamming giữa hai chuỗi (strings) có chiều dài bằng nhau là số các ký hiệu ở vị trí tương đương có giá trị khác nhau. Nói một cách khác, nó đo số lượng thay thế cần phải có để đổi giá trị của một chuỗi ký tự sang một chuỗi ký tự khác, hay số lượng lỗi xảy ra biến đổi một chuỗi ký tự sang một chuỗi ký tự khác. Trong ngữ cảnh rộng hơn, khoảng cách Hamming là một đơn vị để đo khoảng cách chỉnh sửa giữa hai chuỗi. 4 | 5 | ## Ví dụ 6 | 7 | Khoảng cách Hamming giữa : 8 | 9 | - "ka**rol**in" và "ka**thr**in" là **3**. 10 | - "k**a**r**ol**in" và "k**e**r**st**in" là **3**. 11 | - 10**1**1**1**01 và 10**0**1**0**01 là **2**. 12 | - 2**17**3**8**96 và 2**23**3**7**96 là **3**. 13 | 14 | ## Liên kết 15 | 16 | [Wikipedia](https://en.wikipedia.org/wiki/Hamming_distance) 17 | -------------------------------------------------------------------------------- /src/algorithms/string/hamming-distance/__test__/hammingDistance.test.js: -------------------------------------------------------------------------------- 1 | import hammingDistance from '../hammingDistance'; 2 | 3 | describe('hammingDistance', () => { 4 | it('should throw an error when trying to compare the strings of different lengths', () => { 5 | const compareStringsOfDifferentLength = () => { 6 | hammingDistance('a', 'aa'); 7 | }; 8 | 9 | expect(compareStringsOfDifferentLength).toThrowError(); 10 | }); 11 | 12 | it('should calculate difference between two strings', () => { 13 | expect(hammingDistance('a', 'a')).toBe(0); 14 | expect(hammingDistance('a', 'b')).toBe(1); 15 | expect(hammingDistance('abc', 'add')).toBe(2); 16 | expect(hammingDistance('karolin', 'kathrin')).toBe(3); 17 | expect(hammingDistance('karolin', 'kerstin')).toBe(3); 18 | expect(hammingDistance('1011101', '1001001')).toBe(2); 19 | expect(hammingDistance('2173896', '2233796')).toBe(3); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/algorithms/string/hamming-distance/hammingDistance.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} a 3 | * @param {string} b 4 | * @return {number} 5 | */ 6 | export default function hammingDistance(a, b) { 7 | if (a.length !== b.length) { 8 | throw new Error('Strings must be of the same length'); 9 | } 10 | 11 | let distance = 0; 12 | 13 | for (let i = 0; i < a.length; i += 1) { 14 | if (a[i] !== b[i]) { 15 | distance += 1; 16 | } 17 | } 18 | 19 | return distance; 20 | } 21 | -------------------------------------------------------------------------------- /src/algorithms/string/knuth-morris-pratt/README.md: -------------------------------------------------------------------------------- 1 | # Thuật toán Knuth–Morris–Pratt 2 | 3 | Thuật toán tìm kiếm chuỗi Knuth-Morris-Pratt (hoặc thuật toán KMP) tìm kiếm sự xuất hiện của từ `W` trong chuỗi văn bản chính `T` bằng cách quan sát khi nào sự không phù hợp diễn ra, bản thân từ `W` đã bao gồm các thông tin hữu ích để xác định vị trị bắt đầu của ký tự so sánh tiếp theo, do đó sẽ bỏ qua quá trình kiểm tra lại các ký tự đã so sánh trước đó. 4 | 5 | ## Độ phức tạp 6 | 7 | - **Độ phức tạp thời gian:** `O(|W| + |T|)` (nhanh hơn nhiều so với bình thường `O(|W| * |T|)`) 8 | - **Độ phức tạp không gian:** `O(|W|)` 9 | 10 | ## Liên kết 11 | 12 | - [Wikipedia](https://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm) 13 | - [YouTube](https://www.youtube.com/watch?v=GTJr8OvyEVQ&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 14 | -------------------------------------------------------------------------------- /src/algorithms/string/knuth-morris-pratt/__test__/knuthMorrisPratt.test.js: -------------------------------------------------------------------------------- 1 | import knuthMorrisPratt from '../knuthMorrisPratt'; 2 | 3 | describe('knuthMorrisPratt', () => { 4 | it('should find word position in given text', () => { 5 | expect(knuthMorrisPratt('', '')).toBe(0); 6 | expect(knuthMorrisPratt('a', '')).toBe(0); 7 | expect(knuthMorrisPratt('a', 'a')).toBe(0); 8 | expect(knuthMorrisPratt('abcbcglx', 'abca')).toBe(-1); 9 | expect(knuthMorrisPratt('abcbcglx', 'bcgl')).toBe(3); 10 | expect(knuthMorrisPratt('abcxabcdabxabcdabcdabcy', 'abcdabcy')).toBe(15); 11 | expect(knuthMorrisPratt('abcxabcdabxabcdabcdabcy', 'abcdabca')).toBe(-1); 12 | expect(knuthMorrisPratt('abcxabcdabxaabcdabcabcdabcdabcy', 'abcdabca')).toBe(12); 13 | expect(knuthMorrisPratt('abcxabcdabxaabaabaaaabcdabcdabcy', 'aabaabaaa')).toBe(11); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/algorithms/string/knuth-morris-pratt/knuthMorrisPratt.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://www.youtube.com/watch?v=GTJr8OvyEVQ 3 | * @param {string} word 4 | * @return {number[]} 5 | */ 6 | function buildPatternTable(word) { 7 | const patternTable = [0]; 8 | let prefixIndex = 0; 9 | let suffixIndex = 1; 10 | 11 | while (suffixIndex < word.length) { 12 | if (word[prefixIndex] === word[suffixIndex]) { 13 | patternTable[suffixIndex] = prefixIndex + 1; 14 | suffixIndex += 1; 15 | prefixIndex += 1; 16 | } else if (prefixIndex === 0) { 17 | patternTable[suffixIndex] = 0; 18 | suffixIndex += 1; 19 | } else { 20 | prefixIndex = patternTable[prefixIndex - 1]; 21 | } 22 | } 23 | 24 | return patternTable; 25 | } 26 | 27 | /** 28 | * @param {string} text 29 | * @param {string} word 30 | * @return {number} 31 | */ 32 | export default function knuthMorrisPratt(text, word) { 33 | if (word.length === 0) { 34 | return 0; 35 | } 36 | 37 | let textIndex = 0; 38 | let wordIndex = 0; 39 | 40 | const patternTable = buildPatternTable(word); 41 | 42 | while (textIndex < text.length) { 43 | if (text[textIndex] === word[wordIndex]) { 44 | // Ta đã tìm được cái tương ứng. 45 | if (wordIndex === word.length - 1) { 46 | return (textIndex - word.length) + 1; 47 | } 48 | wordIndex += 1; 49 | textIndex += 1; 50 | } else if (wordIndex > 0) { 51 | wordIndex = patternTable[wordIndex - 1]; 52 | } else { 53 | wordIndex = 0; 54 | textIndex += 1; 55 | } 56 | } 57 | 58 | return -1; 59 | } 60 | -------------------------------------------------------------------------------- /src/algorithms/string/levenshtein-distance/__test__/levenshteinDistance.test.js: -------------------------------------------------------------------------------- 1 | import levenshteinDistance from '../levenshteinDistance'; 2 | 3 | describe('levenshteinDistance', () => { 4 | it('should calculate edit distance between two strings', () => { 5 | expect(levenshteinDistance('', '')).toBe(0); 6 | expect(levenshteinDistance('a', '')).toBe(1); 7 | expect(levenshteinDistance('', 'a')).toBe(1); 8 | expect(levenshteinDistance('abc', '')).toBe(3); 9 | expect(levenshteinDistance('', 'abc')).toBe(3); 10 | 11 | // Should just add I to the beginning. 12 | expect(levenshteinDistance('islander', 'slander')).toBe(1); 13 | 14 | // Needs to substitute M by K, T by M and add an A to the end 15 | expect(levenshteinDistance('mart', 'karma')).toBe(3); 16 | 17 | // Substitute K by S, E by I and insert G at the end. 18 | expect(levenshteinDistance('kitten', 'sitting')).toBe(3); 19 | 20 | // Should add 4 letters FOOT at the beginning. 21 | expect(levenshteinDistance('ball', 'football')).toBe(4); 22 | 23 | // Should delete 4 letters FOOT at the beginning. 24 | expect(levenshteinDistance('football', 'foot')).toBe(4); 25 | 26 | // Needs to substitute the first 5 chars: INTEN by EXECU 27 | expect(levenshteinDistance('intention', 'execution')).toBe(5); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/algorithms/string/levenshtein-distance/levenshteinDistance.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} a 3 | * @param {string} b 4 | * @return {number} 5 | */ 6 | export default function levenshteinDistance(a, b) { 7 | // Tạo ma trận khoảng cách chỉnh sửa trống cho tất cả thay đổi khả dụng 8 | // của chuỗi con của a thành chuỗi con của b. 9 | const distanceMatrix = Array(b.length + 1).fill(null).map(() => Array(a.length + 1).fill(null)); 10 | 11 | // Điền hàng đầu tiên của ma trận. 12 | // Nếu đây là hàng đầu tiênm thì ta chuyển đổi chuỗi trống thành a. 13 | // Trong trường hợp này số chuyển đổi bằng với kích cỡ của chuỗi con a. 14 | for (let i = 0; i <= a.length; i += 1) { 15 | distanceMatrix[0][i] = i; 16 | } 17 | 18 | // Điều vào cột đầu tiên của ma trận. 19 | // Nếu là cột đầu tiên thì ta chuyển đổi chuỗi trống thành b. 20 | // Trong trường hợp này số chuyển đổi bằng với kích cở của chuỗi con b. 21 | for (let j = 0; j <= b.length; j += 1) { 22 | distanceMatrix[j][0] = j; 23 | } 24 | 25 | for (let j = 1; j <= b.length; j += 1) { 26 | for (let i = 1; i <= a.length; i += 1) { 27 | const indicator = a[i - 1] === b[j - 1] ? 0 : 1; 28 | distanceMatrix[j][i] = Math.min( 29 | distanceMatrix[j][i - 1] + 1, // xoá 30 | distanceMatrix[j - 1][i] + 1, // chèn 31 | distanceMatrix[j - 1][i - 1] + indicator, // thay thế 32 | ); 33 | } 34 | } 35 | 36 | return distanceMatrix[b.length][a.length]; 37 | } 38 | -------------------------------------------------------------------------------- /src/algorithms/tree/depth-first-search/README.md: -------------------------------------------------------------------------------- 1 | # Tìm kiếm theo chiều sâu (DFS) 2 | 3 | Tìm kiếm ưu tiên chiều sâu hay tìm kiếm theo chiều sâu là một thuật toán duyệt hoặc tìm kiếm trên cấu trúc cây hoặc đồ thị. Thuật toán bắt đầu tại gốc (hoặc chọn một đỉnh nào đó coi như gốc) và khám phá xa nhất có thể theo mỗi nhánh. 4 | 5 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/7/7f/Depth-First-Search.gif) 6 | 7 | ## Liên kết 8 | 9 | - [Wikipedia](https://en.wikipedia.org/wiki/Depth-first_search) 10 | - [Tree Traversals (Inorder, Preorder and Postorder)](https://www.geeksforgeeks.org/tree-traversals-inorder-preorder-and-postorder/) 11 | - [BFS vs DFS](https://www.geeksforgeeks.org/bfs-vs-dfs-binary-tree/) 12 | -------------------------------------------------------------------------------- /src/data-structures/disjoint-set/README.md: -------------------------------------------------------------------------------- 1 | # Tập hợp không giao nhau 2 | 3 | Cấu trúc dữ liệu **tập hợp không giao nhau** hay tập hợp rời rạc là một cấu trúc dữ liệu để lưu trữ một tập hợp các phần tử được phân chia thành nhiều tập hợp con không giao nhau. 4 | 5 | Nó cung cấp các thao tác với thời gian gần như không đổi (được giới hạn bởi hàm ngược Ackermann) để *thêm các tập hợp mới*, *hợp nhất các tập hợp hiện có* và *xác định xem các phần tử có trong cùng một tập hợp hay không*. Ngoài ra còn nhiều công dụng khác (xem phần Ứng dụng), các tập hợp rời rạc đóng một vai trò quan trọng trong thuật toán Kruskal để tìm cây bao trùm nhỏ nhất của một đồ thị. 6 | 7 | ![disjoint set](https://upload.wikimedia.org/wikipedia/commons/6/67/Dsu_disjoint_sets_init.svg) 8 | 9 | Thao tác *Tạo-Tập* tạo ra 8 phần tử. 10 | 11 | ![disjoint set](https://upload.wikimedia.org/wikipedia/commons/a/ac/Dsu_disjoint_sets_final.svg) 12 | 13 | Sau một số thao tác *Hợp*, một số tập hợp đã được nhóm lại với nhau. 14 | 15 | ## Liên kết 16 | 17 | - [Wikipedia](https://en.wikipedia.org/wiki/Disjoint-set_data_structure) 18 | - [By Abdul Bari on YouTube](https://www.youtube.com/watch?v=wU6udHRIkcc&index=14&t=0s&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) -------------------------------------------------------------------------------- /src/data-structures/doubly-linked-list/DoublyLinkedListNode.js: -------------------------------------------------------------------------------- 1 | export default class DoublyLinkedListNode { 2 | constructor(value, next = null, previous = null) { 3 | this.value = value; 4 | this.next = next; 5 | this.previous = previous; 6 | } 7 | 8 | toString(callback) { 9 | return callback ? callback(this.value) : `${this.value}`; 10 | } 11 | } -------------------------------------------------------------------------------- /src/data-structures/graph/GraphEdge.js: -------------------------------------------------------------------------------- 1 | export default class GraphEdge { 2 | /** 3 | * @param {GraphVertex} startVertex 4 | * @param {GraphVertex} endVertex 5 | * @param {number} [weight=1] 6 | */ 7 | constructor(startVertex, endVertex, weight = 0) { 8 | this.startVertex = startVertex; 9 | this.endVertex = endVertex; 10 | this.weight = weight; 11 | } 12 | 13 | /** 14 | * @return {string} 15 | */ 16 | getKey() { 17 | const startVertexKey = this.startVertex.getKey(); 18 | const endVertexKey = this.endVertex.getKey(); 19 | 20 | return `${startVertexKey}_${endVertexKey}`; 21 | } 22 | 23 | /** 24 | * @return {GraphEdge} 25 | */ 26 | reverse() { 27 | const tmp = this.startVertex; 28 | this.startVertex = this.endVertex; 29 | this.endVertex = tmp; 30 | 31 | return this; 32 | } 33 | 34 | /** 35 | * @return {string} 36 | */ 37 | toString() { 38 | return this.getKey(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/data-structures/graph/README.md: -------------------------------------------------------------------------------- 1 | # Đồ thị 2 | 3 | Trong khoa học máy tính, **đồ thị** là một kiểu dữ liệu trừu tượng để triển khai đồ thị vô hướng và có hướng trong toán học, đặc biệt trong mảng lý thuyết đồ thị. 4 | 5 | Cấu trúc dữ liệu đồ thị bao gồm một hữu hạn (có thể thay đổi) tập hợp đỉnh hoặc nút hoặc điểm, cùng với một tập hợp không tuần tự cặp đỉnh cho đồ thị vô hướng và tập hợp tuần tự cặp đỉnh cho đồ thị có hướng. Cặp đỉnh còn được biết đến là cạnh, vòng hoặc đoạn trong đồ thị vô hướng và là mũi tên, cạnh có hướng hoặc đoạn có hướng trong đồ thị có hướng. Các đỉnh có thể là một phần của cấu trúc đồ thị hoặc có thể là các thực thể bên ngoài được biểu diễn bằng các chỉ số nguyên hoặc tham chiếu. 6 | 7 | ![Graph](https://www.tutorialspoint.com/data_structures_algorithms/images/graph.jpg) 8 | 9 | ## Liên kết 10 | 11 | - [Wikipedia](https://en.wikipedia.org/wiki/Graph_(abstract_data_type)) 12 | - [Introduction to Graphs on YouTube](https://www.youtube.com/watch?v=gXgEDyodOJU&index=9&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 13 | - [Graphs representation on YouTube](https://www.youtube.com/watch?v=k1wraWzqtvQ&index=10&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) -------------------------------------------------------------------------------- /src/data-structures/graph/__test__/GraphEdge.test.js: -------------------------------------------------------------------------------- 1 | import GraphEdge from '../GraphEdge'; 2 | import GraphVertex from '../GraphVertex'; 3 | 4 | describe('GraphEdge', () => { 5 | it('should create graph edge with default weight', () => { 6 | const startVertex = new GraphVertex('A'); 7 | const endVertex = new GraphVertex('B'); 8 | const edge = new GraphEdge(startVertex, endVertex); 9 | 10 | expect(edge.getKey()).toBe('A_B'); 11 | expect(edge.toString()).toBe('A_B'); 12 | expect(edge.startVertex).toEqual(startVertex); 13 | expect(edge.endVertex).toEqual(endVertex); 14 | expect(edge.weight).toEqual(0); 15 | }); 16 | 17 | it('should create graph edge with predefined weight', () => { 18 | const startVertex = new GraphVertex('A'); 19 | const endVertex = new GraphVertex('B'); 20 | const edge = new GraphEdge(startVertex, endVertex, 10); 21 | 22 | expect(edge.startVertex).toEqual(startVertex); 23 | expect(edge.endVertex).toEqual(endVertex); 24 | expect(edge.weight).toEqual(10); 25 | }); 26 | 27 | it('should be possible to do edge reverse', () => { 28 | const vertexA = new GraphVertex('A'); 29 | const vertexB = new GraphVertex('B'); 30 | const edge = new GraphEdge(vertexA, vertexB, 10); 31 | 32 | expect(edge.startVertex).toEqual(vertexA); 33 | expect(edge.endVertex).toEqual(vertexB); 34 | expect(edge.weight).toEqual(10); 35 | 36 | edge.reverse(); 37 | 38 | expect(edge.startVertex).toEqual(vertexB); 39 | expect(edge.endVertex).toEqual(vertexA); 40 | expect(edge.weight).toEqual(10); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /src/data-structures/hash-table/README.md: -------------------------------------------------------------------------------- 1 | # Bảng Băm 2 | 3 | Trong máy tính, **bảng băm** (hash map) là một cấu trúc dữ liệu có thể thực hiện kiểu dữ liệu trừu tượng là *mảng kết hợp*, cấu trúc có thể *ánh xạ các khoá thành giá trị*. Bảng băm sử dụng *hàm băm* để tính chỉ mục thành một mảng các bucket hoặc slot, từ đó có thể tìm thấy giá trị mong muốn. 4 | 5 | Ý tưởng cho hàm băm là sẽ gán mỗi khóa cho một bucket duy nhất, nhưng hầu hết các thiết kế bảng băm sử dụng một hàm băm không hoàn hảo, nên có thể gây ra xung đột băm trong đó hàm băm tạo ra cùng một chỉ mục cho nhiều hơn một khóa. Những va chạm như vậy phải được giải quyết theo một cách nào đó. 6 | 7 | ![Hash Table](https://upload.wikimedia.org/wikipedia/commons/7/7d/Hash_table_3_1_1_0_1_0_0_SP.svg) 8 | 9 | Xung đột băm được giải quyết bằng các chuỗi riêng biệt. 10 | 11 | ![Hash Collision](https://upload.wikimedia.org/wikipedia/commons/d/d0/Hash_table_5_0_1_1_1_1_1_LL.svg) 12 | 13 | ## Liên kết 14 | 15 | - [Wikipedia](https://en.wikipedia.org/wiki/Hash_table) 16 | - [YouTube](https://www.youtube.com/watch?v=shs0KM3wKv8&index=4&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) -------------------------------------------------------------------------------- /src/data-structures/heap/MaxHeap.js: -------------------------------------------------------------------------------- 1 | import Heap from './Heap'; 2 | 3 | export default class MaxHeap extends Heap { 4 | /** 5 | * Kiểm tra xem các phần tử trong heap có theo đúng thứ tự 6 | * Với MinHeap phần tử đầu tiên phải là nhỏ nhất hoặc bằng. 7 | * Với MaxHeap phần tử đầu tiên phải là lớn nhất hoặc bằng. 8 | * 9 | * @param {*} firstElement 10 | * @param {*} secondElement 11 | * @return {boolean} 12 | */ 13 | pairIsInCorrectOrder(firstElement, secondElement) { 14 | return this.compare.greaterThanOrEqual(firstElement, secondElement); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/data-structures/heap/MinHeap.js: -------------------------------------------------------------------------------- 1 | import Heap from './Heap'; 2 | 3 | export default class MinHeap extends Heap { 4 | /** 5 | * Kiểm tra xem các phần tử trong heap có theo đúng thứ tự 6 | * Với MinHeap phần tử đầu tiên phải là nhỏ nhất hoặc bằng. 7 | * Với MaxHeap phần tử đầu tiên phải là lớn nhất hoặc bằng. 8 | * 9 | * @param {*} firstElement 10 | * @param {*} secondElement 11 | * @return {boolean} 12 | */ 13 | pairIsInCorrectOrder(firstElement, secondElement) { 14 | return this.compare.lessThanOrEqual(firstElement, secondElement); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/data-structures/heap/README.md: -------------------------------------------------------------------------------- 1 | # Đống (Heap) 2 | 3 | Trong khoa học máy tính, **đống (heap)** là một dạng cây chuyên biệt dựa trên cấu trúc dữ liệu thỏa mãn thuộc tính heap được mô tả phía dưới. 4 | 5 | Ở *min heap*, nếu `P` là cha của nút `C` thì giá trị (key) của `P` phải nhỏ hơn hoặc bằng giá trị của `C`. 6 | 7 | ![MinHeap](https://upload.wikimedia.org/wikipedia/commons/6/69/Min-heap.png) 8 | 9 | Ở *max heap*, giá trị của `P` phải lớn hơn hoặc bằng giá trị của `C`. 10 | 11 | ![Heap](https://upload.wikimedia.org/wikipedia/commons/3/38/Max-Heap.svg) 12 | 13 | Nút ở trên cùng heap không có nút cha được gọi là nút gốc (root node). 14 | 15 | ## Liên kết 16 | 17 | - [Wikipedia](https://en.wikipedia.org/wiki/Heap_(data_structure)) 18 | - [YouTube](https://www.youtube.com/watch?v=t0Cq6tVNRBA&index=5&t=0s&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) -------------------------------------------------------------------------------- /src/data-structures/heap/__test__/Heap.test.js: -------------------------------------------------------------------------------- 1 | import Heap from '../Heap'; 2 | 3 | describe('Heap', () => { 4 | it('should not allow to create instance of the Heap directly', () => { 5 | const instantiateHeap = () => { 6 | const heap = new Heap(); 7 | heap.add(5); 8 | }; 9 | 10 | expect(instantiateHeap).toThrow(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/data-structures/linked-list/LinkedListNode.js: -------------------------------------------------------------------------------- 1 | export default class LinkedListNode { 2 | constructor(value, next = null) { 3 | this.value = value; 4 | this.next = next; 5 | } 6 | 7 | toString(callback) { 8 | return callback ? callback(this.value) : `${this.value}`; 9 | } 10 | } -------------------------------------------------------------------------------- /src/data-structures/linked-list/__test__/LinkedListNode.test.js: -------------------------------------------------------------------------------- 1 | import LinkedListNode from '../LinkedListNode'; 2 | 3 | describe('LinkedListNode', () => { 4 | it('should create list node with value', () => { 5 | const node = new LinkedListNode(1); 6 | 7 | expect(node.value).toBe(1); 8 | expect(node.next).toBeNull(); 9 | }); 10 | 11 | it('should create list node with object as a value', () => { 12 | const nodeValue = { value: 1, key: 'test' }; 13 | const node = new LinkedListNode(nodeValue); 14 | 15 | expect(node.value.value).toBe(1); 16 | expect(node.value.key).toBe('test'); 17 | expect(node.next).toBeNull(); 18 | }); 19 | 20 | it('should link nodes together', () => { 21 | const node2 = new LinkedListNode(2); 22 | const node1 = new LinkedListNode(1, node2); 23 | 24 | expect(node1.next).toBeDefined(); 25 | expect(node2.next).toBeNull(); 26 | expect(node1.value).toBe(1); 27 | expect(node1.next.value).toBe(2); 28 | }); 29 | 30 | it('should convert node to string', () => { 31 | const node = new LinkedListNode(1); 32 | 33 | expect(node.toString()).toBe('1'); 34 | 35 | node.value = 'string value'; 36 | expect(node.toString()).toBe('string value'); 37 | }); 38 | 39 | it('should convert node to string with custom stringifier', () => { 40 | const nodeValue = { value: 1, key: 'test' }; 41 | const node = new LinkedListNode(nodeValue); 42 | const toStringCallback = (value) => `value: ${value.value}, key: ${value.key}`; 43 | 44 | expect(node.toString(toStringCallback)).toBe('value: 1, key: test'); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /src/data-structures/priority-queue/README.md: -------------------------------------------------------------------------------- 1 | # Hàng đợi ưu tiên 2 | 3 | Trong khoa học máy tính, **hàng đợi ưu tiên** là một kiểu dữ liệu trừu tượng nó có cấu trúc dữ liệu tương tự như một hàng đợi thông thường. Nhưng khi ta thêm một phần tử cần phải có độ "ưu tiên" đi kèm. Trong hàng đợi ưu tiên, những phần tử có độ ưu tiên cao sẽ được phục vụ trước những phần tử có độ ưu tiên thấp. Hai phần tử có cùng độ ưu tiên sẽ dựa vào thứ tự trong hàng đợi để thực hiện. 4 | 5 | Hàng đợi ưu tiên thường được triển khai với heap, tuy vậy chúng vẫn khác biệt về mặt khái niệm với heap. Hàng đợi ưu tiên là một khái niệm trừu tượng như "list" hoặc "map"; giống như một danh sách có thể được thực hiện với một danh sách được liên kết hoặc một mảng, một hàng đợi ưu tiên có thể được thực hiện với một heap hoặc nhiều phương thức khác như một mảng không có thứ tự. 6 | 7 | ## Liên kết 8 | 9 | - [Wikipedia](https://en.wikipedia.org/wiki/Priority_queue) 10 | - [YouTube](https://www.youtube.com/watch?v=wptevk0bshY&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8&index=6) -------------------------------------------------------------------------------- /src/data-structures/queue/Queue.js: -------------------------------------------------------------------------------- 1 | import LinkedList from '../linked-list/LinkedList'; 2 | 3 | export default class Queue { 4 | constructor() { 5 | // Chúng ta sẽ triển khai Hàng đợi dựa trên danh sách liên kết vì 6 | // cấu trúc của chúng khá tương đồng. Cụ thể, chúng đều hoạt động dựa trên 7 | // phần tử đầu và cuối. So sánh thao tác enqueue/dequeue của hàng đợi 8 | // với append/deleteHead của danh sách liên kết. 9 | this.linkedList = new LinkedList(); 10 | } 11 | 12 | /** 13 | * @return {boolean} 14 | */ 15 | isEmpty() { 16 | return !this.linkedList.head; 17 | } 18 | 19 | /** 20 | * Đọc phần tử đầu tiên của hàng đợi mà không xoá nó. 21 | * @return {*} 22 | */ 23 | peek() { 24 | if (!this.linkedList.head) { 25 | return null; 26 | } 27 | 28 | return this.linkedList.head.value; 29 | } 30 | 31 | /** 32 | * Thêm một phần tử mới vào hàng đợi (tail của danh sách liên kết) 33 | * Phần tử đó sẽ được xử lý sau tất cả các phần tử ở trước nó. 34 | * @param {*} value 35 | */ 36 | enqueue(value) { 37 | this.linkedList.append(value); 38 | } 39 | 40 | /** 41 | * Xoá phần tử ở vị trí đầu tiên khỏi hàng đợi (head ở danh sách liên kết). 42 | * Nếu hàng đợi trống trả về null. 43 | * @return {*} 44 | */ 45 | dequeue() { 46 | const removedHead = this.linkedList.deleteHead(); 47 | return removedHead ? removedHead.value : null; 48 | } 49 | 50 | /** 51 | * @param [callback] 52 | * @return {string} 53 | */ 54 | toString(callback) { 55 | // Trả về biểu diễn string của hàng đợi. 56 | return this.linkedList.toString(callback); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/data-structures/queue/README.md: -------------------------------------------------------------------------------- 1 | # Hàng đợi 2 | 3 | Trong khoa học máy tính, **hàng đợi** là một kiểu dữ liệu trừu trường hoặc một tập hợp mà ở đó các thực thể trong tập hợp được giữ theo thứ tự và nguyên tắc hoạt động của tập hợp đó. Như việc thêm các thực thể vào hàng đợi được gọi là enqueue. Còn việc xoá các thực thể khỏi hàng đợi được gọi là dequeue. Cơ chế này gọi là First-In-First-Out (FIFO). Cấu trúc dữ liệu dạng FIFO, phần tử thêm vào hàng đợi trước sẽ được xoá ra trước. Điều này tương đương với yêu cầu khi một phần tử mới được thêm vào, tất cả các phần tử đã được thêm vào trước đó phải được loại bỏ trước khi phần tử mới có thể được loại bỏ. Giống như ngăn xếp, một hành động peek cũng trả về giá trị của phần tử đầu tiên mà không cần phải dequeue nó. 4 | Hàng đợi là một ví dụ về cấu trúc dữ liệu tuyến tính, hoặc trừu tượng hơn là một bộ tập hợp tuần tự. 5 | 6 | Hình ảnh mình hoạ về hàng đợi. 7 | 8 | ![Queue](https://upload.wikimedia.org/wikipedia/commons/5/52/Data_Queue.svg) 9 | 10 | ## Liên kết 11 | 12 | - [Wikipedia](https://en.wikipedia.org/wiki/Queue_(abstract_data_type)) 13 | - [YouTube](https://www.youtube.com/watch?v=wjI1WNcIntg&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8&index=3&) 14 | -------------------------------------------------------------------------------- /src/data-structures/stack/README.md: -------------------------------------------------------------------------------- 1 | # Ngăn Xếp 2 | 3 | Trong khoa học máy tính, **ngăn xếp** là một kiểu dữ liệu trừu tượng phục vụ như một tập hợp các yếu tố cho hai hoạt động chính sau: 4 | * **push** , thêm phần tử vào ngăn xếp và 5 | * **pop**, xoá phần tử được thêm vào gần nhất khỏi ngăn xếp nếu nó vẫn chưa bị xoá. 6 | 7 | Trật tự các phần tử được thêm vào và lấy ra ngăn xếp còn được gọi dưới cái tên là LIFO (last in, first out) tức vào cuối ra đầu. Ngoài ra ta còn có phương thức peek() để lấy phần tử trên cùng ngăn xếp mà không cần xoá nó. Cái tên "ngăn xếp" dựa trên các hình ảnh thực tế từ các vật phẩm vật lý xếp chồng lên nhau, rất dễ dàng để lấy mục trên cùng khỏi ngăn xếp, nhưng với các mục nằm sâu bên dưới ta sẽ phải cần lấy rât nhiều mục khác trước. 8 | 9 | Biểu diễn đơn giản về hoạt động của ngăn xếp các thao tác push và pop. 10 | 11 | ![Stack](https://upload.wikimedia.org/wikipedia/commons/b/b4/Lifo_stack.png) 12 | 13 | ## Liên kết 14 | 15 | - [Wikipedia](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)) 16 | - [YouTube](https://www.youtube.com/watch?v=wjI1WNcIntg&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8&index=3&) -------------------------------------------------------------------------------- /src/data-structures/tree/README.md: -------------------------------------------------------------------------------- 1 | # Cây 2 | 3 | Trong khoa học máy tính, **cây** là một kiểu dữ liệu trừu tượng (ADT) được sử dụng rộng rãi - hoặc cấu trúc dữ liệu triển khai kiểu ADT - mô phỏng cấu trúc cây phân cấp, với giá trị gốc và các cây con có nút cha, được biểu diễn dưới dạng một tập hợp các nút được liên kết. 4 | 5 | Cấu trúc dữ liệu dạng cây có thể được định nghĩa đệ quy (cục bộ) như một tập hợp các nút (bắt đầu từ nút gốc), trong đó mỗi nút là một cấu trúc dữ liệu bao gồm một giá trị, và một danh sách các tham chiếu đến các nút ("con") , với ràng buộc là không có tham chiếu nào bị trùng lặp và không có tham chiếu nào trỏ đến nút gốc. 6 | 7 | Một cây đơn giản sẽ không có thứ tự; trong sơ đồ này, nút số 7 có 8 | hai nút con, mang số 2 và 6, và một nút cha, mang số 2. Nút gốc ở trên cùng, không có cha. 9 | 10 | ![Tree](https://upload.wikimedia.org/wikipedia/commons/f/f7/Binary_tree.svg) 11 | 12 | ## Liên kết 13 | 14 | - [Wikipedia](https://en.wikipedia.org/wiki/Tree_(data_structure)) 15 | - [YouTube](https://www.youtube.com/watch?v=oSWTXtMglKE&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8&index=8) -------------------------------------------------------------------------------- /src/data-structures/tree/binary-search-tree/BinarySearchTree.js: -------------------------------------------------------------------------------- 1 | import BinarySearchTreeNode from './BinarySearchTreeNode'; 2 | 3 | export default class BinarySearchTree { 4 | /** 5 | * @param {function} [nodeValueCompareFunction] 6 | */ 7 | constructor(nodeValueCompareFunction) { 8 | this.root = new BinarySearchTreeNode(null, nodeValueCompareFunction); 9 | 10 | // Lấy bộ so sánh từ nút gốc. 11 | this.nodeComparator = this.root.nodeComparator; 12 | } 13 | 14 | /** 15 | * @param {*} value 16 | * @return {BinarySearchTreeNode} 17 | */ 18 | insert(value) { 19 | return this.root.insert(value); 20 | } 21 | 22 | /** 23 | * @param {*} value 24 | * @return {boolean} 25 | */ 26 | contains(value) { 27 | return this.root.contains(value); 28 | } 29 | 30 | /** 31 | * @param {*} value 32 | * @return {boolean} 33 | */ 34 | remove(value) { 35 | return this.root.remove(value); 36 | } 37 | 38 | /** 39 | * @return {string} 40 | */ 41 | toString() { 42 | return this.root.toString(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/data-structures/tree/fenwick-tree/README.md: -------------------------------------------------------------------------------- 1 | # Cây chỉ số nhị phân / Cây Fenwick 2 | 3 | **Cây Fenwick** hoặc **cây chỉ số nhị phân** là cấu trúc dữ liệu có thể hoạt động hiệu quả việc cập nhật phần tử và tính tổng tiền tố trong một bảng số. 4 | 5 | Khi so sánh với một mảng số phẳng, thì cây Fenwick cân bằng hiệu quả hơn các thao tác: cập nhật phần tử hay tính tổng tiền tố. Trong mảng phẳng bao gồm các số `n`, để có thể tính toán tổng tiền tố yêu cầu thời gian tuyến tính; trong trường hợp cập nhật các phần tử của mảng cũng yêu cầu thời gian tuyến tính (trong cả hai trường hợp, hoạt động khác có thể được thực hiện trong thời gian không đổi). Cây Fenwick cho phép thực hiện cả hai phép toán trong thời gian `O (log n)`. Điều này đạt được bằng cách biểu diễn các số dưới dạng cây, trong đó giá trị của mỗi nút là tổng các số trong cây con đó. Cấu trúc cây cho phép các hoạt động được thực hiện chỉ bằng cách sử dụng các truy cập nút `O (log n)`. 6 | 7 | ## Triển khai 8 | 9 | Cây chỉ số nhị phân được biểu diễn dưới dạng mảng. Mỗi nút trong cây lưu trữ tổng các phần tử trong mảng được cho. Kích thước của cây sẽ bằng `n` với `n` là kích thước của mảng đầu vào. Trong triển khai hiện tại ta dùng kích thước `n+1` để dễ thực hiện. Tất cả được đánh số từ 1. 10 | 11 | ![Binary Indexed Tree](https://www.geeksforgeeks.org/wp-content/uploads/BITSum.png) 12 | 13 | Ảnh động bên dưới minh hoạ quá trình tạo cây Fenwick từ mảng `[1, 2, 3, 4, 5]` bằng cách chèn từng phần tử. 14 | 15 | ![Fenwick Tree](https://upload.wikimedia.org/wikipedia/commons/d/dc/BITDemo.gif) 16 | 17 | 18 | ## Liên kết 19 | 20 | - [Wikipedia](https://en.wikipedia.org/wiki/Fenwick_tree) 21 | - [GeeksForGeeks](https://www.geeksforgeeks.org/binary-indexed-tree-or-fenwick-tree-2/) 22 | - [YouTube](https://www.youtube.com/watch?v=CWDQJGaN1gY&index=18&t=0s&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) -------------------------------------------------------------------------------- /src/data-structures/trie/README.md: -------------------------------------------------------------------------------- 1 | # Trie 2 | 3 | Trong khoa học máy tính, **trie** còn được gọi là cây kỹ thuật số đôi khi là cây cơ số hoặc cây tiền tố (vì chúng có thể được tìm kiếm bằng các tiền tố), là một loại cây tìm kiếm — một cấu trúc dữ liệu cây có thứ tự được sử dụng để lưu trữ tập động hoặc mảng liên kết trong đó các khóa thường là chuỗi. Không giống như cây nhị phân, mỗi nút trong cây không liên kết với một khóa trong mảng; thay vào đó mỗi nút liên kết với một chuỗi ký tự sao cho các chuỗi ký tự của tất cả các nút con của một nút đều có chung một tiền tố, chính là chuỗi ký tự của nút đó. Nút gốc tương ứng với chuỗi ký tự rỗng. 4 | Các giá trị không nhất thiết phải được liên kết với mọi nút, thay vào đó chúng chỉ cần liên kết với một vài nút có liên quan trong cây. Biểu diễn tối ưu hóa không gian của cây tiền tố : 5 | 6 | ![Trie](https://upload.wikimedia.org/wikipedia/commons/b/be/Trie_example.svg) 7 | 8 | ## Liên kết 9 | 10 | - [Wikipedia](https://en.wikipedia.org/wiki/Trie) 11 | - [YouTube](https://www.youtube.com/watch?v=zIjfhVPRZCg&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8&index=7&t=0s) --------------------------------------------------------------------------------