├── algorithms ├── algorithm-tasks │ ├── countConstruct │ │ ├── README.md │ │ ├── countConstruct.mjs │ │ └── countConstructExample.mjs │ ├── fibanacchi │ │ ├── fibanacсhi.mjs │ │ ├── fibanacchiExample.mjs │ │ ├── memoizedFibbonachiExample.mjs │ │ ├── memoizedFibanaсchi.mjs │ │ └── README.md │ ├── bestSum │ │ ├── bestSumExample.mjs │ │ ├── bestSum.mjs │ │ └── README.md │ ├── howSum │ │ ├── howSumExample.mjs │ │ ├── howSum.mjs │ │ └── README.md │ ├── canSum │ │ ├── canSumExample.mjs │ │ ├── canSum.mjs │ │ └── README.md │ ├── twoSum │ │ ├── twoSumExample.mjs │ │ ├── twoSum.mjs │ │ └── README.md │ ├── gridTraveler │ │ ├── gridTravelerExample.mjs │ │ ├── gridTraveler.mjs │ │ ├── memoizedGridTravelerExample.mjs │ │ ├── memoizedGridTraveler.mjs │ │ └── README.md │ ├── tableGridTraveler │ │ ├── tableGridTravelerExample.mjs │ │ ├── tableGridTraveler.mjs │ │ └── README.md │ ├── canSumWIthTable │ │ ├── canSumWithTableExample.mjs │ │ ├── canSumWithTable.mjs │ │ └── README.md │ ├── fibanacciWithTable │ │ ├── fibanacchiWithTable.mjs │ │ ├── fibanacchiWithTableExample.mjs │ │ └── README.md │ ├── canConstruct │ │ ├── canConstructExample.mjs │ │ ├── canConstruct.mjs │ │ └── README.md │ ├── allConstructWithTable │ │ ├── allConstructWithTableExample.mjs │ │ ├── allConstructWithTable.mjs │ │ └── README.md │ ├── howSumWithTable │ │ ├── howSumWithTableExample.mjs │ │ ├── howSumWithTable.mjs │ │ └── README.md │ ├── countConstructWithTable │ │ ├── countConstructWithTable.mjs │ │ ├── countConstructWithTableExample.mjs │ │ └── README.md │ ├── allConstruct │ │ ├── allContructExample.mjs │ │ ├── allConstruct.mjs │ │ └── README.md │ ├── canConstructWithTabulation │ │ ├── canConstructWIthTabulation.mjs │ │ ├── canCounstructWithTabulationExample.mjs │ │ └── README.md │ ├── bestSumWithTabulation │ │ ├── bestSumWithTabulation.mjs │ │ ├── bestSumWithTabulationExample.mjs │ │ └── README.md │ ├── package.json │ └── README.md ├── data-structures │ ├── 2-3-binary-tree │ │ ├── twoThreeTreeExample.mjs │ │ ├── README.md │ │ └── TwoThreeTree.mjs │ ├── binary-heap │ │ ├── priorityQueueExample.mjs │ │ ├── PriorityQueue.mjs │ │ └── README.md │ ├── hash-table │ │ ├── hashTableExample.mjs │ │ ├── HashTable.mjs │ │ └── README.md │ ├── 2-3-4-binary-tree │ │ ├── twoThreeFourBinaryTreeExample.mjs │ │ ├── README.md │ │ └── TwoTheeFourBinaryTree.mjs │ ├── package.json │ ├── black-red-tree │ │ ├── blackRedTreeExample.mjs │ │ ├── README.md │ │ └── BlackRedTree.mjs │ ├── binary-tree │ │ ├── binaryTreeExample.mjs │ │ ├── BinaryTree.mjs │ │ └── README.md │ ├── min-indexed-binary-heap │ │ ├── indexMinPqExample.mjs │ │ ├── README.md │ │ └── IndexMinPQ.mjs │ └── table │ │ ├── symbolTableExample.mjs │ │ ├── SymbolTable.mjs │ │ └── README.md └── sort-playground │ ├── shell-sort │ ├── shellSort.mjs │ └── README.md │ ├── visualization.mjs │ ├── ascending-merge-sort │ ├── ascendingMergeSort.mjs │ └── README.md │ ├── insert-sort │ ├── insertSort.mjs │ └── README.md │ ├── heap-sort │ ├── HeapSort.mjs │ └── README.md │ ├── select-sort │ ├── selectSort.mjs │ └── README.md │ ├── quick-three-way │ ├── threeWayQuickSort.mjs │ └── README.md │ ├── quick-sort │ ├── quickSort.mjs │ └── README.md │ ├── README.md │ ├── descending-merge-sort │ ├── descendingMergeSort.mjs │ └── README.md │ ├── package.json │ ├── sortPlayground.mjs │ └── utils.mjs ├── .gitignore └── README.md /algorithms/algorithm-tasks/countConstruct/README.md: -------------------------------------------------------------------------------- 1 | [countConstruct memoization time code](https://www.youtube.com/watch?v=oBt53YbR9Kk&t=9516s) 2 | 3 | [Назад к содержанию](../README.md) -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/fibanacchi/fibanacсhi.mjs: -------------------------------------------------------------------------------- 1 | export function fibanaсchi(number) { 2 | if (number <= 2) { 3 | return 1; 4 | } 5 | return fibanaсchi(number - 1) + fibanaсchi(number - 2) 6 | } -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/fibanacchi/fibanacchiExample.mjs: -------------------------------------------------------------------------------- 1 | import {fibanaсchi} from './fibanacсhi.mjs'; 2 | 3 | console.log(fibanaсchi(4)) 4 | console.log(fibanaсchi(12)) 5 | console.log(fibanaсchi(22)) 6 | console.log(fibanaсchi(50)) -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/bestSum/bestSumExample.mjs: -------------------------------------------------------------------------------- 1 | import {bestSum} from './bestSum.mjs'; 2 | 3 | console.log(bestSum(7, [5,4,3,7])); 4 | console.log(bestSum(8, [2,3,5])); 5 | console.log(bestSum(8, [1,4,5])); 6 | console.log(bestSum(100, [1,2,5,25])); -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/howSum/howSumExample.mjs: -------------------------------------------------------------------------------- 1 | import {howSum} from './howSum.mjs'; 2 | 3 | console.log(howSum(300, [202, 3, 14])) 4 | console.log(howSum(5, [2, 1])) 5 | console.log(howSum(5, [6, 3])) 6 | console.log(howSum(33, [2, 1, 5, 8])) 7 | console.log(howSum(14, [2, 8])) -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/canSum/canSumExample.mjs: -------------------------------------------------------------------------------- 1 | 2 | import {canSum} from './canSum.mjs'; 3 | 4 | console.log(canSum(300, [202, 3, 14])) 5 | console.log(canSum(5, [2, 1])) 6 | console.log(canSum(5, [6, 3])) 7 | console.log(canSum(33, [2, 1, 5, 8])) 8 | console.log(canSum(14, [2, 8])) -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/fibanacchi/memoizedFibbonachiExample.mjs: -------------------------------------------------------------------------------- 1 | import {memoizedFibanaссhi} from './memoizedFibanaсchi.mjs'; 2 | 3 | console.log(memoizedFibanaссhi(4)) 4 | console.log(memoizedFibanaссhi(12)) 5 | console.log(memoizedFibanaссhi(22)) 6 | console.log(memoizedFibanaссhi(50)) -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/twoSum/twoSumExample.mjs: -------------------------------------------------------------------------------- 1 | import {twoSum} from './twoSum.mjs'; 2 | 3 | console.log(twoSum([2, 7, 11, 15], 9)); 4 | console.log(twoSum([3, 2, 4], 6)); 5 | console.log(twoSum([3, 3], 6)); 6 | console.log(twoSum([3, 2, 4], 6)); 7 | console.log(twoSum([0, 4, 3, 0], 0)); -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/gridTraveler/gridTravelerExample.mjs: -------------------------------------------------------------------------------- 1 | import {gridTraveler} from './gridTraveler.mjs'; 2 | 3 | console.log(gridTraveler(3, 4)) 4 | console.log(gridTraveler(5, 6)) 5 | console.log(gridTraveler(12, 12)) 6 | console.log(gridTraveler(6, 9)) 7 | console.log(gridTraveler(18, 18)) -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/gridTraveler/gridTraveler.mjs: -------------------------------------------------------------------------------- 1 | export function gridTraveler(m, n) { 2 | if (m === 1 && n === 1) { 3 | return 1; 4 | } 5 | if (m === 0 || n === 0) { 6 | return 0; 7 | } 8 | 9 | 10 | return gridTraveler(m - 1, n) + gridTraveler(m, n - 1); 11 | } -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/tableGridTraveler/tableGridTravelerExample.mjs: -------------------------------------------------------------------------------- 1 | import {tableGridTraveler} from './tableGridTraveler.mjs'; 2 | 3 | console.log(tableGridTraveler(1, 1)) 4 | console.log(tableGridTraveler(2, 3)) 5 | console.log(tableGridTraveler(3, 2)) 6 | console.log(tableGridTraveler(3, 3)) 7 | console.log(tableGridTraveler(100, 100)) -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/canSumWIthTable/canSumWithTableExample.mjs: -------------------------------------------------------------------------------- 1 | import {canSumWithTable} from './canSumWithTable.mjs'; 2 | 3 | console.log(canSumWithTable(7, [5, 3, 4, 7])) 4 | console.log(canSumWithTable(7, [2, 4])) 5 | console.log(canSumWithTable(7, [2, 3])) 6 | console.log(canSumWithTable(7, [2, 3, 5])) 7 | console.log(canSumWithTable(3000, [2, 3, 5, 7])) -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/fibanacchi/memoizedFibanaсchi.mjs: -------------------------------------------------------------------------------- 1 | export function memoizedFibanaссhi(n, memo = {}) { 2 | if (n in memo) { 3 | return memo[n]; 4 | } 5 | 6 | if (n <= 2) { 7 | return 1; 8 | } 9 | 10 | memo[n] = memoizedFibanaссhi(n - 1, memo) + memoizedFibanaссhi(n - 2, memo); 11 | return memo[n]; 12 | } -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/gridTraveler/memoizedGridTravelerExample.mjs: -------------------------------------------------------------------------------- 1 | import {memoizedGridTraveler} from './memoizedGridTraveler.mjs'; 2 | 3 | console.log(memoizedGridTraveler(3, 4)) 4 | console.log(memoizedGridTraveler(5, 6)) 5 | console.log(memoizedGridTraveler(12, 12)) 6 | console.log(memoizedGridTraveler(6, 9)) 7 | console.log(memoizedGridTraveler(18, 18)) -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/fibanacciWithTable/fibanacchiWithTable.mjs: -------------------------------------------------------------------------------- 1 | export function fibanacchiWithTable(num) { 2 | const table = Array(num + 1).fill(0); 3 | table[1] = 1; 4 | 5 | for (let i = 0; i <= num; i++) { 6 | table[i + 1] += table[i]; 7 | table[i + 2] += table[i]; 8 | } 9 | 10 | return table[num]; 11 | } 12 | 13 | 14 | -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/twoSum/twoSum.mjs: -------------------------------------------------------------------------------- 1 | export function twoSum(nums, target) { 2 | const map = {}; 3 | 4 | for (let i = 0; i < nums.length; i++) { 5 | const diff = target - nums[i]; 6 | 7 | if (map[diff] !== undefined) { 8 | return [map[diff], i]; 9 | } 10 | map[nums[i]] = i; 11 | } 12 | return []; 13 | } -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/fibanacciWithTable/fibanacchiWithTableExample.mjs: -------------------------------------------------------------------------------- 1 | import {fibanacchiWithTable} from './fibanacchiWithTable.mjs'; 2 | 3 | console.log(fibanacchiWithTable(6)); 4 | console.log(fibanacchiWithTable(7)); 5 | console.log(fibanacchiWithTable(8)); 6 | console.log(fibanacchiWithTable(50)); 7 | console.log(fibanacchiWithTable(150)); 8 | console.log(fibanacchiWithTable(1500)); -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/canSumWIthTable/canSumWithTable.mjs: -------------------------------------------------------------------------------- 1 | export function canSumWithTable(targetSum, numbers) { 2 | const table = Array(targetSum + 1).fill(false); 3 | table[0] = true; 4 | 5 | for (let i = 0; i <= targetSum; i++) { 6 | if (table[i]) { 7 | for(const num of numbers) { 8 | table[i + num] = true; 9 | } 10 | } 11 | } 12 | return table[targetSum]; 13 | } -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/canConstruct/canConstructExample.mjs: -------------------------------------------------------------------------------- 1 | import {canConstruct} from './canConstruct.mjs'; 2 | 3 | console.log(canConstruct("abcdef", ["ab", "abc", "cd", "def", "abcd"])); 4 | console.log(canConstruct("skateboard", ["bo", "rd", "ate", "t", "ska", "sk", "boar"])); 5 | console.log(canConstruct("enterapotentpot", ["a", "p", "ent", "enter", "ot", "o", "t"])); 6 | console.log(canConstruct("eeeeeeeeeeeeeeeeeeeeeeeeeef", ["e", "ee", "eee", "eeee", "eeeee", "eeeeee"])); -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/gridTraveler/memoizedGridTraveler.mjs: -------------------------------------------------------------------------------- 1 | export function memoizedGridTraveler(m, n, memo = {}) { 2 | const key = `${m},${n}` 3 | if (m === 1 && n === 1) { 4 | return 1; 5 | } 6 | if (m === 0 || n === 0) { 7 | return 0; 8 | } 9 | 10 | if (key in memo) { 11 | return memo[key]; 12 | } 13 | 14 | memo[key] = memoizedGridTraveler(m - 1, n, memo) + memoizedGridTraveler(m, n - 1, memo); 15 | return memo[key]; 16 | } -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/allConstructWithTable/allConstructWithTableExample.mjs: -------------------------------------------------------------------------------- 1 | import {allConstructWithTable} from './allConstructWithTable.mjs'; 2 | 3 | console.log(allConstructWithTable('abcdef', ['ab', 'abc', 'cd', 'def', 'abcd'])); 4 | console.log(allConstructWithTable('purple', ['purp', 'p', 'ur', 'le', 'purpl'])); 5 | console.log(allConstructWithTable('skateboard', ['bo', 'rd', 'ate', 't', 'ka', 'k', 'boar'])); 6 | console.log(allConstructWithTable('enterapotentpot', ['a', 'p', 'ent', 'enter', 'ot', 'o', 't'])); -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/howSumWithTable/howSumWithTableExample.mjs: -------------------------------------------------------------------------------- 1 | import {howSumWithTable} from './howSumWithTable.mjs'; 2 | 3 | console.log(howSumWithTable(300, [202, 3, 14])) 4 | console.log(howSumWithTable(300, [7, 14])) 5 | console.log(howSumWithTable(5, [2, 1])) 6 | console.log(howSumWithTable(5, [6, 3])) 7 | console.log(howSumWithTable(8, [2, 3, 5])) 8 | console.log(howSumWithTable(33, [2, 1, 5, 8])) 9 | console.log(howSumWithTable(14, [2, 8])) 10 | console.log(howSumWithTable(7, [5, 3, 4, 7])) 11 | -------------------------------------------------------------------------------- /algorithms/data-structures/2-3-binary-tree/twoThreeTreeExample.mjs: -------------------------------------------------------------------------------- 1 | import {TwoThreeTree} from './TwoThreeTree.mjs'; 2 | 3 | function twoThreeBinaryThreeShowCase() { 4 | const tree = new TwoThreeTree(); 5 | tree.insert(10); 6 | tree.insert(20); 7 | tree.insert(30); 8 | tree.insert(40); 9 | tree.insert(50); 10 | 11 | console.log(tree.find(10)); // true 12 | console.log(tree.find(25)); // false 13 | console.log(tree.find(40)); // true 14 | } 15 | twoThreeBinaryThreeShowCase(); -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/howSumWithTable/howSumWithTable.mjs: -------------------------------------------------------------------------------- 1 | export function howSumWithTable(target, nums) { 2 | const table = Array(target + 1).fill(null); 3 | table[0] = []; 4 | 5 | for (let i = 0; i <= target; i++) { 6 | if (table[i] !== null) { 7 | for (const num of nums) { 8 | if (table[i]) { 9 | table[i + num] = [...table[i], num]; 10 | } 11 | } 12 | } 13 | } 14 | 15 | return table[target] 16 | } 17 | -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/countConstruct/countConstruct.mjs: -------------------------------------------------------------------------------- 1 | export function countConstruct(target, wordBank, memo = {}) { 2 | if (target in memo) { 3 | return memo[target]; 4 | } 5 | 6 | if (target === '') { 7 | return 1; 8 | } 9 | 10 | let count = 0; 11 | 12 | for (const word of wordBank) { 13 | if (target.startsWith(word)) { 14 | count += countConstruct(target.slice(word.length), wordBank, memo); 15 | } 16 | } 17 | 18 | memo[target] = count; 19 | return count; 20 | } -------------------------------------------------------------------------------- /algorithms/sort-playground/shell-sort/shellSort.mjs: -------------------------------------------------------------------------------- 1 | import {insertSortBody} from '../insert-sort/insertSort.mjs'; 2 | 3 | export function shellSort(data, frameMaker) { 4 | const size = data.length; 5 | let gap = 1; 6 | const PARTS = 3; 7 | 8 | while (gap < size / PARTS) { 9 | gap = PARTS * gap + 1; 10 | } 11 | 12 | while (gap >= 1) { 13 | data = insertSortBody(data, size, gap); 14 | gap = Math.floor(gap / PARTS); 15 | } 16 | 17 | frameMaker && frameMaker(data); 18 | return {data, frameMaker}; 19 | } 20 | -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/countConstruct/countConstructExample.mjs: -------------------------------------------------------------------------------- 1 | import {countConstruct} from './countConstruct.mjs'; 2 | 3 | console.log(countConstruct('abcdef', ['ab', 'abc', 'cd', 'def', 'abcd'])); 4 | console.log(countConstruct('purple', ['purp', 'p', 'ur', 'le', 'purpl'])); 5 | console.log(countConstruct('skateboard', ['bo', 'rd', 'ate', 't', 'ka', 'k', 'boar'])); 6 | console.log(countConstruct('enterapotentpot', ['a', 'p', 'ent', 'enter', 'ot', 'o', 't'])); 7 | console.log(countConstruct('eeeeeeeeeeeeeeeeeeeeeeeeeef', ['e', 'ee', 'eee', 'eeee', 'eeeee', 'c'])); 8 | 9 | -------------------------------------------------------------------------------- /algorithms/data-structures/binary-heap/priorityQueueExample.mjs: -------------------------------------------------------------------------------- 1 | import {PriorityQueue} from './PriorityQueue.mjs'; 2 | 3 | function runPriorityQueueShowcase() { 4 | const pq = new PriorityQueue(); 5 | pq.insert(10); 6 | pq.insert(20); 7 | pq.insert(30); 8 | pq.insert(5); 9 | 10 | 11 | console.assert(pq.delMax() === 30, "Куча должна вернуть 30!"); 12 | console.assert(pq.delMax() === 20, "Куча должна вернуть 20!"); 13 | 14 | console.log("Если нет записей об ошибках в консоле, то тест пройден!"); 15 | } 16 | 17 | runPriorityQueueShowcase(); -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/countConstructWithTable/countConstructWithTable.mjs: -------------------------------------------------------------------------------- 1 | export function countConstructWithTable(target, parts) { 2 | const table = Array(target.length + 1).fill(0); 3 | table[0] = 1; 4 | 5 | for (let i = 0; i < target.length; i++) { 6 | if (table[i]) { 7 | for (const part of parts) { 8 | if (target.slice(i, i + part.length) === part) { 9 | table[i + part.length] += table[i]; 10 | } 11 | } 12 | } 13 | } 14 | 15 | return table[target.length]; 16 | } -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/allConstruct/allContructExample.mjs: -------------------------------------------------------------------------------- 1 | import {allConstruct} from './allConstruct.mjs'; 2 | 3 | console.log(allConstruct('abcdef', ['ab', 'abc', 'cd', 'def', 'abcd'])); 4 | console.log(allConstruct('purple', ['purp', 'p', 'ur', 'le', 'purpl'])); 5 | console.log(allConstruct('eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef', ['e', 'ee', 'eee', 'eeee', 'eeeee', 'eeeeee'])); 6 | console.log(allConstruct('skateboard', ['bo', 'rd', 'ate', 't', 'ka', 'k', 'boar'])); 7 | console.log(allConstruct('enterapotentpot', ['a', 'p', 'ent', 'enter', 'ot', 'o', 't'])); -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/canConstructWithTabulation/canConstructWIthTabulation.mjs: -------------------------------------------------------------------------------- 1 | export function canConstructWIthTabulation(target, parts) { 2 | const table = Array(target.length + 1).fill(false); 3 | table[0] = true; 4 | 5 | for (let i = 0; i <= target.length; i++) { 6 | if (table[i]) { 7 | for (const part of parts) { 8 | if (target.slice(i, i + part.length) === part) { 9 | table[i + part.length] = true; 10 | } 11 | } 12 | } 13 | } 14 | return table[target.length]; 15 | } -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/canConstructWithTabulation/canCounstructWithTabulationExample.mjs: -------------------------------------------------------------------------------- 1 | import {canConstructWIthTabulation} from './canConstructWIthTabulation.mjs'; 2 | 3 | console.log(canConstructWIthTabulation("abcdef", ["ab", "abc", "cd", "def", "abcd"])); 4 | console.log(canConstructWIthTabulation("skateboard", ["bo", "rd", "ate", "t", "ska", "sk", "boar"])); 5 | console.log(canConstructWIthTabulation("enterapotentpot", ["a", "p", "ent", "enter", "ot", "o", "t"])); 6 | console.log(canConstructWIthTabulation("eeeeeeeeeeeeeeeeeeeeeeeeeef", ["e", "ee", "eee", "eeee", "eeeee", "eeeeee"])); -------------------------------------------------------------------------------- /algorithms/sort-playground/visualization.mjs: -------------------------------------------------------------------------------- 1 | import { 2 | ALGORITHMS_DIRECTORIES, 3 | chooseVisualizationType, 4 | getJsonFiles, 5 | promptUserToSelectFile, STRING_TO_SYMBOL 6 | } from './utils.mjs'; 7 | 8 | const [, , type] = process.argv; 9 | 10 | export function visualizationByType(type) { 11 | if (!type in ALGORITHMS_DIRECTORIES) { 12 | console.log('Недопустимый тип для воспроизведения') 13 | } 14 | promptUserToSelectFile(getJsonFiles(ALGORITHMS_DIRECTORIES[STRING_TO_SYMBOL[type]]), chooseVisualizationType) 15 | } 16 | 17 | visualizationByType(type); -------------------------------------------------------------------------------- /algorithms/data-structures/hash-table/hashTableExample.mjs: -------------------------------------------------------------------------------- 1 | import {HashTable} from './HashTable.mjs'; 2 | 3 | function hashTableShowCase() { 4 | const myHashTable = new HashTable(); 5 | 6 | myHashTable.set('name', 'John'); 7 | myHashTable.set('age', '30'); 8 | myHashTable.set('country', 'USA'); 9 | 10 | console.log(myHashTable.get('name')); // вывод: John 11 | console.log(myHashTable.get('age')); // вывод: 30 12 | console.log(myHashTable.hasKey('country')); // вывод: true 13 | console.log(myHashTable.hasKey('email')); // вывод: false 14 | } 15 | 16 | hashTableShowCase(); -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/canSum/canSum.mjs: -------------------------------------------------------------------------------- 1 | export function canSum(targetSum, numbers, memo = {}) { 2 | if (targetSum in memo) { 3 | return memo[targetSum]; 4 | } 5 | 6 | if (targetSum === 0) { 7 | return true; 8 | } 9 | 10 | if (targetSum < 0) { 11 | return false; 12 | } 13 | 14 | for (const num of numbers) { 15 | const remainder = targetSum - num; 16 | if(canSum(remainder, numbers, memo) === true) { 17 | memo[targetSum] = true; 18 | return true; 19 | } 20 | } 21 | 22 | memo[targetSum] = false; 23 | return false; 24 | } -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/bestSumWithTabulation/bestSumWithTabulation.mjs: -------------------------------------------------------------------------------- 1 | export function bestSumWithTabulation(target, numbers) { 2 | const table = Array(target + 1).fill(null); 3 | table[0] = []; 4 | 5 | for (let i = 0; i <= target; i++) { 6 | if (table[i]) { 7 | for (const num of numbers) { 8 | const combination = [...table[i], num]; 9 | 10 | if (!table[i + num] || table[i + num].length > combination.length) { 11 | table[i + num] = combination; 12 | } 13 | } 14 | } 15 | } 16 | 17 | return table[target]; 18 | } -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/countConstructWithTable/countConstructWithTableExample.mjs: -------------------------------------------------------------------------------- 1 | import {countConstructWithTable} from './countConstructWithTable.mjs'; 2 | 3 | console.log(countConstructWithTable('abcdef', ['ab', 'abc', 'cd', 'def', 'abcd'])); 4 | console.log(countConstructWithTable('purple', ['purp', 'p', 'ur', 'le', 'purpl'])); 5 | console.log(countConstructWithTable('skateboard', ['bo', 'rd', 'ate', 't', 'ka', 'k', 'boar'])); 6 | console.log(countConstructWithTable('enterapotentpot', ['a', 'p', 'ent', 'enter', 'ot', 'o', 't'])); 7 | console.log(countConstructWithTable('eeeeeeeeeeeeeeeeeeeeeeeeeef', ['e', 'ee', 'eee', 'eeee', 'eeeee', 'c'])); 8 | 9 | -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/tableGridTraveler/tableGridTraveler.mjs: -------------------------------------------------------------------------------- 1 | export function tableGridTraveler(m, n) { 2 | const table = Array(m + 1) 3 | .fill() 4 | .map(() => Array(n + 1).fill(0)); 5 | 6 | table[1][1] = 1; 7 | 8 | for (let i = 0; i <= m; i++) { 9 | for(let j = 0; j <= n; j++) { 10 | const current = table[i][j]; 11 | 12 | if (j + 1 <= n) { 13 | table[i][j + 1] += current; 14 | } 15 | 16 | if (i + 1 <= m) { 17 | table[i + 1][j] += current; 18 | } 19 | } 20 | } 21 | 22 | return table[m][n] 23 | } -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/allConstructWithTable/allConstructWithTable.mjs: -------------------------------------------------------------------------------- 1 | export function allConstructWithTable(target, parts) { 2 | const table = Array(target.length + 1) 3 | .fill() 4 | .map(() => []); 5 | 6 | table[0] = [[]]; 7 | 8 | for (let i = 0; i <= target.length; i++) { 9 | for (const part of parts) { 10 | if (target.slice(i, i + part.length) === part) { 11 | const newCombinations = table[i].map(subarray => [...subarray, part]); 12 | table[i + part.length].push(...newCombinations); 13 | } 14 | } 15 | } 16 | 17 | return table[target.length] 18 | } -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/howSum/howSum.mjs: -------------------------------------------------------------------------------- 1 | export function howSum(targetSum, numbers, memo = {}) { 2 | if (targetSum in memo) return memo[targetSum]; 3 | if (targetSum === 0) { 4 | return []; 5 | } 6 | if (targetSum < 0) { 7 | return null; 8 | } 9 | 10 | for (const num of numbers) { 11 | const remainder = targetSum - num; 12 | const remainderResult = howSum(remainder, numbers, memo); 13 | 14 | if (remainderResult !== null) { 15 | memo[targetSum] = [...remainderResult, num]; 16 | return memo[targetSum] 17 | } 18 | } 19 | 20 | memo[targetSum] = null; 21 | return null; 22 | } 23 | -------------------------------------------------------------------------------- /algorithms/sort-playground/ascending-merge-sort/ascendingMergeSort.mjs: -------------------------------------------------------------------------------- 1 | import {merge} from '../descending-merge-sort/descendingMergeSort.mjs'; 2 | 3 | export function ascendingMergeSort(data, frameMaker) { 4 | const index = data.length; 5 | const result = new Array(index); 6 | 7 | for (let sliceIndex = 1; sliceIndex < index; sliceIndex *= 2) { 8 | for (let lowIndex = 0; lowIndex < index - sliceIndex; lowIndex += sliceIndex * 2) { 9 | frameMaker && frameMaker(data); 10 | merge(data, lowIndex, lowIndex + sliceIndex - 1, Math.min(lowIndex + sliceIndex * 2 - 1, index - 1), result); 11 | } 12 | } 13 | return {data, frameMaker}; 14 | } -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/bestSumWithTabulation/bestSumWithTabulationExample.mjs: -------------------------------------------------------------------------------- 1 | import {bestSumWithTabulation} from './bestSumWithTabulation.mjs'; 2 | 3 | console.log(bestSumWithTabulation(300, [202, 3, 14])) 4 | console.log(bestSumWithTabulation(300, [7, 14])) 5 | console.log(bestSumWithTabulation(5, [2, 1])) 6 | console.log(bestSumWithTabulation(5, [6, 3])) 7 | console.log(bestSumWithTabulation(8, [2, 3, 5])) 8 | console.log(bestSumWithTabulation(33, [2, 1, 5, 8])) 9 | console.log(bestSumWithTabulation(14, [2, 8])) 10 | console.log(bestSumWithTabulation(7, [5, 3, 4, 7])) 11 | console.log(bestSumWithTabulation(100, [1, 2, 4, 25])) 12 | console.log(bestSumWithTabulation(100, [25, 1, 5, 2])) 13 | -------------------------------------------------------------------------------- /algorithms/sort-playground/insert-sort/insertSort.mjs: -------------------------------------------------------------------------------- 1 | import {less, swap} from '../utils.mjs'; 2 | 3 | export function insertSortBody(data, size, gap = 1, frameMaker) { 4 | for (let counter = gap; counter < size; counter++) { 5 | for (let insertIndex = counter; insertIndex >= gap && less(data[insertIndex], data[insertIndex - gap]); insertIndex -= gap) { 6 | frameMaker && frameMaker(data); 7 | swap(data, insertIndex, insertIndex - gap); 8 | } 9 | } 10 | return data; 11 | } 12 | 13 | export function insertSort(data, frameMaker) { 14 | let size = data.length; 15 | insertSortBody(data, size, 1, frameMaker) 16 | return {data, frameMaker}; 17 | } 18 | -------------------------------------------------------------------------------- /algorithms/sort-playground/heap-sort/HeapSort.mjs: -------------------------------------------------------------------------------- 1 | import {IndexMinPQ} from '../../data-structures/min-indexed-binary-heap/IndexMinPQ.mjs'; 2 | 3 | export function heapSort(array, frameMaker) { 4 | const pq = new IndexMinPQ(array.length); 5 | 6 | // Заполнение очереди с приоритетами элементами массива 7 | for (let i = 0; i < array.length; i++) { 8 | pq.insert(i, array[i]); 9 | } 10 | 11 | // Удаление элементов из очереди с приоритетами 12 | // и добавление их к результату в отсортированном порядке 13 | const data = []; 14 | while (!pq.isEmpty()) { 15 | data.push(pq.minItem()); 16 | pq.deleteMin(); 17 | } 18 | 19 | return {data, frameMaker}; 20 | } -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/allConstruct/allConstruct.mjs: -------------------------------------------------------------------------------- 1 | export function allConstruct(target, wordBank, memo = {}) { 2 | if (target in memo) { 3 | return memo[target]; 4 | } 5 | 6 | if (target === '') { 7 | return [[]]; 8 | } 9 | 10 | const result = []; 11 | 12 | for (const word of wordBank) { 13 | if (target.startsWith(word)) { 14 | const suffix = target.slice(word.length); 15 | const suffixWays = allConstruct(suffix, wordBank, memo); 16 | const targetWays = suffixWays.map(way => [word,...way]); 17 | result.push(...targetWays); 18 | } 19 | } 20 | 21 | memo[target] = result; 22 | return result; 23 | } -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/canConstruct/canConstruct.mjs: -------------------------------------------------------------------------------- 1 | export function canConstruct(target, wordBank, memo = {}) { 2 | if (target in memo) { 3 | return memo[target]; 4 | } 5 | 6 | if (target === '') { 7 | return true; 8 | } 9 | 10 | if (wordBank.length === 0) { 11 | return false; 12 | } 13 | for (let word of wordBank) { 14 | if (target.indexOf(word) === 0) { 15 | const suffix = target.slice(word.length); 16 | if (canConstruct(suffix, wordBank, memo)) { 17 | memo[target] = true; 18 | return true; 19 | } 20 | } 21 | } 22 | memo[target] = false; 23 | return memo[target]; 24 | } -------------------------------------------------------------------------------- /algorithms/sort-playground/select-sort/selectSort.mjs: -------------------------------------------------------------------------------- 1 | import {less, swap} from '../utils.mjs'; 2 | 3 | export function selectSort(data, frameMaker) { 4 | return { 5 | data: data.reduce((baseArray, currentElement, index) => { 6 | let currentMinIndex = index; 7 | for(let innerIndex = index + 1; innerIndex <= baseArray.length; innerIndex++) { 8 | if (less(baseArray[innerIndex], baseArray[currentMinIndex])) { 9 | currentMinIndex = innerIndex; 10 | } 11 | } 12 | frameMaker && frameMaker(data); 13 | swap(baseArray, index, currentMinIndex); 14 | return baseArray; 15 | }, data), 16 | frameMaker 17 | } 18 | } -------------------------------------------------------------------------------- /algorithms/data-structures/2-3-4-binary-tree/twoThreeFourBinaryTreeExample.mjs: -------------------------------------------------------------------------------- 1 | import {TwoThreeFourTree} from './TwoTheeFourBinaryTree.mjs'; 2 | 3 | function TwoThreeFourTreeShowCase() { 4 | const tree = new TwoThreeFourTree(); 5 | 6 | // Вставка элементов в дерево 7 | tree.insert(10); 8 | tree.insert(20); 9 | tree.insert(30); 10 | tree.insert(40); 11 | 12 | // Поиск элементов в дерево 13 | console.log(tree.find(10)); // Вывод: 0 14 | console.log(tree.find(20)); // Вывод: 0 или 1 в зависимости от того, где располагается элемент 15 | console.log(tree.find(30)); // Вывод: 0, 1 или 2 в зависимости от того, где располагается элемент 16 | console.log(tree.find(100)); // Вывод: -1, так как такого элемента нет в дереве 17 | } 18 | 19 | TwoThreeFourTreeShowCase(); -------------------------------------------------------------------------------- /algorithms/data-structures/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "data-structures", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "table-test": "node table/symbolTableExample.mjs", 8 | "min-heap-test": "node min-indexed-binary-heap/indexMinPqExample.mjs", 9 | "hash-table-test": "node hash-table/hashTableExample.mjs", 10 | "black-red-tree-test": "node black-red-tree/blackRedTreeExample.mjs", 11 | "binary-search-tree-test": "node binary-tree/binaryTreeExample.mjs", 12 | "binary-heap-test": "node binary-heap/priorityQueueExample.mjs", 13 | "two-three-tree-test": "node 2-3-binary-tree/twoThreeTreeExample.mjs", 14 | "two-three-four-tree-test": "node 2-3-4-binary-tree/twoThreeFourBinaryTreeExample.mjs" 15 | }, 16 | "author": "", 17 | "license": "ISC" 18 | } 19 | -------------------------------------------------------------------------------- /algorithms/data-structures/black-red-tree/blackRedTreeExample.mjs: -------------------------------------------------------------------------------- 1 | import {RedBlackTree} from './BlackRedTree.mjs'; 2 | 3 | function RebBlackThreeShowCase() { 4 | let rbTree = new RedBlackTree(); 5 | 6 | // Вставка значений 7 | rbTree.insert(5); 8 | rbTree.insert(3); 9 | rbTree.insert(7); 10 | rbTree.insert(2); 11 | rbTree.insert(4); 12 | rbTree.insert(1); 13 | rbTree.insert(9); 14 | 15 | // Например, можно написать функцию для вывода всех значений в дереве (используя обход в глубину) 16 | 17 | function printTree(node) { 18 | if(node !== null) { 19 | printTree(node.left); 20 | console.log("Data:" + node.data + ", Color: " + node.color); 21 | printTree(node.right); 22 | } 23 | } 24 | 25 | printTree(rbTree.root); 26 | } 27 | 28 | RebBlackThreeShowCase(); -------------------------------------------------------------------------------- /algorithms/data-structures/binary-tree/binaryTreeExample.mjs: -------------------------------------------------------------------------------- 1 | import {BinarySearchTree} from './BinaryTree.mjs'; 2 | 3 | function binaryThreeShowCase() { 4 | // Создаем новое бинарное дерево поиска 5 | const BST = new BinarySearchTree(); 6 | 7 | // Вставляем узлы в дерево 8 | BST.insert(15); 9 | BST.insert(25); 10 | BST.insert(10); 11 | BST.insert(7); 12 | BST.insert(22); 13 | BST.insert(17); 14 | BST.insert(13); 15 | BST.insert(5); 16 | BST.insert(9); 17 | BST.insert(27); 18 | 19 | const root = BST.getRootNode(); 20 | 21 | // Ищем узел со значением 22 22 | const findNode = BST.search(root, 22); 23 | 24 | if (findNode !== null) { 25 | console.log("Узел найден!"); 26 | console.log(findNode); 27 | } else { 28 | console.log("Узел не найден."); 29 | } 30 | } 31 | 32 | binaryThreeShowCase(); -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/bestSum/bestSum.mjs: -------------------------------------------------------------------------------- 1 | export function bestSum(targetSum, numbers, memo = {}) { 2 | if (targetSum === 0) { 3 | return []; 4 | } 5 | if (targetSum < 0) { 6 | return null; 7 | } 8 | if (targetSum in memo) { 9 | return memo[targetSum]; 10 | } 11 | 12 | let shortestCombination = null; 13 | 14 | for (const num of numbers) { 15 | const remainder = targetSum - num; 16 | const remainderCombination = bestSum(remainder, numbers, memo); 17 | 18 | if (remainderCombination !== null) { 19 | const combination = [...remainderCombination, num]; 20 | 21 | 22 | if (shortestCombination === null || combination.length < shortestCombination.length) { 23 | shortestCombination = combination; 24 | } 25 | } 26 | } 27 | memo[targetSum] = shortestCombination; 28 | return shortestCombination; 29 | } -------------------------------------------------------------------------------- /algorithms/sort-playground/quick-three-way/threeWayQuickSort.mjs: -------------------------------------------------------------------------------- 1 | import {compare, swap} from '../utils.mjs'; 2 | 3 | export function threeWayQuickSort(data, frameMaker) { 4 | sort(data, 0, data.length - 1, frameMaker); 5 | return {data, frameMaker}; 6 | } 7 | 8 | function sort(data, lowIndex, heightIndex, frameMaker) { 9 | if (heightIndex <= lowIndex) { 10 | return; 11 | } 12 | 13 | let leftPointer = lowIndex; 14 | let next = lowIndex + 1; 15 | let rightPointer = heightIndex; 16 | const midData = data[lowIndex]; 17 | 18 | while (next <= rightPointer) { 19 | const compareResult = compare(data[next], midData); 20 | 21 | frameMaker && frameMaker(data); 22 | if (compareResult < 0) { 23 | swap(data, leftPointer++, next++); 24 | } else if (compareResult > 0) { 25 | swap(data, next, rightPointer--); 26 | } else { 27 | next++; 28 | } 29 | } 30 | sort(data, lowIndex, leftPointer - 1); 31 | sort(data, rightPointer + 1, heightIndex); 32 | } 33 | -------------------------------------------------------------------------------- /algorithms/data-structures/min-indexed-binary-heap/indexMinPqExample.mjs: -------------------------------------------------------------------------------- 1 | import {IndexMinPQ} from './IndexMinPQ.mjs'; 2 | 3 | function runMinPriorityQueueShowcase() { 4 | const priorityQueue1 = new IndexMinPQ(10); 5 | const priorityQueue2 = new IndexMinPQ(10); 6 | 7 | priorityQueue1.insert(2, "Alice"); 8 | priorityQueue1.insert(1, "Bob"); 9 | 10 | priorityQueue2.insert(3, "Charlie"); 11 | priorityQueue2.insert(4, "David"); 12 | 13 | const mergeMinPQs = (pq1, pq2) => { 14 | let merged = new IndexMinPQ(Math.max(pq1.maximumSize, pq2.maximumSize)); 15 | 16 | for (let i = 0; i < pq1.size(); i++) { 17 | merged.insert(pq1.priorityQueue[i+1], pq1.keyValues[pq1.priorityQueue[i+1]]); 18 | } 19 | for (let i = 0; i < pq2.size(); i++) { 20 | merged.insert(pq2.priorityQueue[i+1], pq2.keyValues[pq2.priorityQueue[i+1]]); 21 | } 22 | return merged; 23 | } 24 | 25 | let mergedQueue = mergeMinPQs(priorityQueue1, priorityQueue2); 26 | 27 | console.log(mergedQueue.minItem()); // "Bob" 28 | mergedQueue.deleteMin(); 29 | console.log(mergedQueue.minItem()); // "Alice" 30 | } 31 | 32 | runMinPriorityQueueShowcase(); -------------------------------------------------------------------------------- /algorithms/sort-playground/quick-sort/quickSort.mjs: -------------------------------------------------------------------------------- 1 | import {less, swap} from '../utils.mjs'; 2 | 3 | export function quickSort(data, frameMaker) { 4 | sort(data, 0, data.length - 1, frameMaker); 5 | return {data, frameMaker}; 6 | } 7 | 8 | function sort(data, lowIndex, heightIndex, frameMaker) { 9 | if (heightIndex <= lowIndex) { 10 | return; 11 | } 12 | frameMaker && frameMaker(data) 13 | 14 | const pivot = partition(data, lowIndex, heightIndex); 15 | sort(data, lowIndex, pivot - 1, frameMaker); 16 | sort(data, pivot + 1, heightIndex, frameMaker); 17 | } 18 | 19 | function partition(data, lowIndex, heightIndex) { 20 | let leftPoint = lowIndex; 21 | let rightPoint = heightIndex + 1; 22 | const mid = data[lowIndex]; 23 | 24 | while (true) { 25 | while (less(data[++leftPoint], mid)) { 26 | if (leftPoint === heightIndex) { 27 | break; 28 | } 29 | } 30 | while (less(mid, data[--rightPoint])) { 31 | if (rightPoint === lowIndex) { 32 | break; 33 | } 34 | } 35 | 36 | if (leftPoint >= rightPoint) { 37 | break; 38 | } 39 | 40 | swap(data, leftPoint, rightPoint); 41 | } 42 | swap(data, lowIndex, rightPoint); 43 | return rightPoint; 44 | } -------------------------------------------------------------------------------- /algorithms/data-structures/hash-table/HashTable.mjs: -------------------------------------------------------------------------------- 1 | export class HashTable { 2 | constructor(size = 50) { 3 | this.size = size; 4 | this.buckets = new Array(size).fill(null).map(() => []); 5 | } 6 | 7 | hash(key) { 8 | let hash = 0; 9 | for (let i = 0; i < key.length; i++) { 10 | hash += key.charCodeAt(i); 11 | } 12 | return hash % this.size; 13 | } 14 | 15 | set(key, value) { 16 | const keyHash = this.hash(key); 17 | const bucketArray = this.buckets[keyHash]; 18 | const storedElement = bucketArray.find((el) => el.key === key); 19 | 20 | if (storedElement) { 21 | storedElement.val = value; 22 | } else { 23 | bucketArray.push({ key: key, val: value }); 24 | } 25 | } 26 | 27 | get(key) { 28 | const keyHash = this.hash(key); 29 | const bucketArray = this.buckets[keyHash]; 30 | const storedElement = bucketArray.find((el) => el.key === key); 31 | 32 | return storedElement ? storedElement.val : undefined; 33 | } 34 | 35 | hasKey(key) { 36 | const keyHash = this.hash(key); 37 | const bucketArray = this.buckets[keyHash]; 38 | const storedElement = bucketArray.find((el) => el.key === key); 39 | 40 | return !!storedElement; 41 | } 42 | } -------------------------------------------------------------------------------- /algorithms/sort-playground/README.md: -------------------------------------------------------------------------------- 1 | # Алгоритмы Сортировки 2 | 3 | В этом проекте представлены разные алгоритмы сортировки с упором на обучение и визуализацию. Каждый алгоритм сортировки имеет свою собственную папку с readme-файлом, который подробно объясняет его работу. 4 | 5 | ## Обзор Алгоритмов Сортировки 6 | 7 | 1. [Shell Sort](shell-sort/README.md): алгоритм сортировки, который обобщает сортировку вставками, улучшая ее производительность на больших массивах. 8 | 9 | 2. [Insertion Sort](insert-sort/README.md): простой алгоритм сортировки, работающий путём сравнения каждого элемента массива с предыдущими. Подходит для небольших объёмов данных. 10 | 11 | 3. [Selection Sort](select-sort/README.md): еще один простой метод сортировки, который работает путем выбора наименьшего элемента из списка и обмена его с первым элементом. 12 | 13 | 4. [Descending Merge Sort](descending-merge-sort/README.md): вариация обычного слияния сортировки, которая сортирует в нисходящем порядке. 14 | 15 | 5. [Ascending Merge Sort](ascending-merge-sort/README.md): привычный алгоритм сортировки слиянием, позволяющий сортировать в больших списках по возрастанию. 16 | 17 | 6. [Quick Sort](quick-sort/README.md): один из самых известных и широко используемых алгоритмов сортировки, который работает на основе принципа "разделяй и властвуй". 18 | 19 | 7. [Three Way Quick Sort](quick-three-way/README.md): вариация алгоритма быстрой сортировки, которая эффективно обрабатывает дублирующиеся ключи. -------------------------------------------------------------------------------- /algorithms/sort-playground/descending-merge-sort/descendingMergeSort.mjs: -------------------------------------------------------------------------------- 1 | import {less} from '../utils.mjs'; 2 | 3 | export function merge(data, lowIndex, mid, heightIndex, result){ 4 | let index = lowIndex; 5 | let temp = mid + 1; 6 | 7 | for (let mergeIndex = lowIndex; mergeIndex <= heightIndex; mergeIndex++) { 8 | result[mergeIndex] = data[mergeIndex]; 9 | } 10 | 11 | for (let mergeIndex = lowIndex; mergeIndex <= heightIndex; mergeIndex++) { 12 | if (index > mid) { 13 | data[mergeIndex] = result[temp++]; 14 | } else if (temp > heightIndex){ 15 | data[mergeIndex] = result[index++]; 16 | } else if (less(result[temp], result[index])) { 17 | data[mergeIndex] = result[temp++]; 18 | } else { 19 | data[mergeIndex] = result[index++]; 20 | } 21 | } 22 | } 23 | 24 | export function descendingMergeSort(data, frameMaker) { 25 | const result = []; 26 | sort(data, 0, data.length - 1, result, frameMaker); 27 | return {data, frameMaker}; 28 | } 29 | 30 | function sort(data, lowIndex, heightIndex, result, frameMaker) { 31 | if (heightIndex <= lowIndex) { 32 | return; 33 | } 34 | 35 | frameMaker && frameMaker(data); 36 | 37 | const mid = lowIndex + (heightIndex - lowIndex) / 2; 38 | sort(data, lowIndex, mid, result, frameMaker); 39 | sort(data, mid + 1, heightIndex, result, frameMaker); 40 | merge(data, lowIndex, mid, heightIndex, result, frameMaker); 41 | } -------------------------------------------------------------------------------- /algorithms/sort-playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "algoritms-in-js", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "run-three-way-quick-sort": "node sortPlayground.mjs 10000 threeWayQuickSort", 6 | "run-three-way-quick-sort-frame": "node sortPlayground.mjs 10000 threeWayQuickSort true", 7 | "ascending-merge-sort": "node sortPlayground.mjs 10000000 ascendingMergeSort", 8 | "ascending-merge-sort-frame": "node sortPlayground.mjs 100000 ascendingMergeSort true", 9 | "descending-merge-sort": "node sortPlayground.mjs 10000 descendingMergeSort", 10 | "descending-merge-sort-frame": "node sortPlayground.mjs 10000 descendingMergeSort true", 11 | "shell-sort": "node sortPlayground.mjs 10000 shell", 12 | "shell-sort-frame": "node sortPlayground.mjs 10000 shell true", 13 | "insert-sort": "node sortPlayground.mjs 10000 insert", 14 | "insert-sort-frame": "node sortPlayground.mjs 10000 insert true", 15 | "select-sort": "node sortPlayground.mjs 10000 select", 16 | "select-sort-frame": "node sortPlayground.mjs 10000 select true", 17 | "quickSort-sort": "node sortPlayground.mjs 10000 quickSort", 18 | "quickSort-sort-frame": "node sortPlayground.mjs 10000 quickSort true", 19 | "heapSort-sort": "node sortPlayground.mjs 10000000 heapSort", 20 | "heapSort-sort-frame": "node sortPlayground.mjs 10000 heapSort true", 21 | "visualizate-quickSort-sort": "node visualization.mjs quickSort", 22 | "visualizate-ascending-merge-sort": "node visualization.mjs ascendingMergeSort" 23 | } 24 | } -------------------------------------------------------------------------------- /algorithms/data-structures/table/symbolTableExample.mjs: -------------------------------------------------------------------------------- 1 | import {SymbolTable} from './SymbolTable.mjs'; 2 | 3 | function symbolTableShowcase() { 4 | const table = new SymbolTable(); 5 | 6 | table.insertKeyValue(1, 'apple'); 7 | table.insertKeyValue(2, 'banana'); 8 | table.insertKeyValue(3, 'pear'); 9 | 10 | console.log(table.getValue(1)); // Выводит 'apple' 11 | console.log(table.hasKey(2)); // Выводит true 12 | console.log(table.hasKey(4)); // Выводит false (такого ключа нет в таблице) 13 | 14 | console.log(table.isTableEmpty()); // Выводит false (таблица не пуста) 15 | 16 | table.deleteKey(1); // Удаляет пару ключ-значение с ключом 1 17 | console.log(table.getValue(1)); // Выводит undefined (такого ключа больше нет) 18 | 19 | console.log(table.minimumKey()); // Выводит 2 (это наименьший ключ в таблице) 20 | console.log(table.maximumKey()); // Выводит 3 (это наибольший ключ в таблице) 21 | 22 | table.insertKeyValue(4, 'orange'); 23 | console.log(table.highestKeyUnder(4)); // Выводит 3 24 | console.log(table.lowestKeyAbove(2)); // Выводит 3 25 | 26 | console.log(table.rankOfKey(3)); // Выводит 1 27 | console.log(table.selectKey(1)); // Выводит 3 28 | 29 | table.deleteMinimumKey(); 30 | console.log(table.minimumKey()); // Выводит 3 31 | 32 | table.deleteMaximumKey(); 33 | console.log(table.maximumKey()); // Выводит 3 34 | 35 | console.log(table.subTableSize(1, 6)); // Выводит 1 36 | console.log(table.keysWithin(1, 6)); // Выводит [3] 37 | console.log(table.getAllKeys()); // Выводит [3] 38 | } 39 | 40 | symbolTableShowcase(); -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "algorithm-tasks", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "fibonacci": "node fibanacchi/fibanacchiExample.mjs", 8 | "memoized-fibonacci": "node fibanacchi/memoizedFibbonachiExample.mjs", 9 | "grid-traveler": "node gridTraveler/gridTravelerExample.mjs", 10 | "memoized-grid-traveler": "node gridTraveler/memoizedGridTravelerExample.mjs", 11 | "can-sum": "node canSum/canSumExample.mjs", 12 | "how-sum": "node howSum/howSumExample.mjs", 13 | "best-sum": "node bestSum/bestSumExample.mjs", 14 | "can-construct": "node canConstruct/canConstructExample.mjs", 15 | "count-construct": "node countConstruct/countConstructExample.mjs", 16 | "all-construct": "node allConstruct/allContructExample.mjs", 17 | "fibanacci-with-table": "node fibanacciWithTable/fibanacchiWithTableExample.mjs", 18 | "grid-tavaler-with-table": "node tableGridTraveler/tableGridTravelerExample.mjs", 19 | "can-sum-with-table": "node canSumWithTable/canSumWithTableExample.mjs", 20 | "two-sum": "node twoSum/twoSumExample.mjs", 21 | "how-sum-with-table": "node howSumWithTable/howSumWithTableExample.mjs", 22 | "best-sum-with-table": "node bestSumWithTabulation/bestSumWithTabulationExample.mjs", 23 | "can-construct-with-table": "node canConstructWithTabulation/canCounstructWithTabulationExample.mjs", 24 | "count-construct-with-table": "node countConstructWithTable/countConstructWithTableExample.mjs", 25 | "all-construct-with-table": "node allConstructWithTable/allConstructWithTableExample.mjs" 26 | }, 27 | "author": "", 28 | "license": "ISC" 29 | } 30 | -------------------------------------------------------------------------------- /algorithms/sort-playground/sortPlayground.mjs: -------------------------------------------------------------------------------- 1 | import { 2 | carry, 3 | compose, 4 | EXAMPLE, 5 | generateData, 6 | logAlgorithmResult, 7 | saveFrames, 8 | SORT_TYPE, 9 | STRING_TO_SYMBOL, 10 | timeTaken 11 | } from './utils.mjs'; 12 | import {shellSort} from './shell-sort/shellSort.mjs'; 13 | import {selectSort} from './select-sort/selectSort.mjs'; 14 | import {insertSort} from './insert-sort/insertSort.mjs'; 15 | import {descendingMergeSort} from './descending-merge-sort/descendingMergeSort.mjs'; 16 | import {ascendingMergeSort} from './ascending-merge-sort/ascendingMergeSort.mjs'; 17 | import {quickSort} from './quick-sort/quickSort.mjs'; 18 | import {threeWayQuickSort} from './quick-three-way/threeWayQuickSort.mjs'; 19 | import {heapSort} from './heap-sort/HeapSort.mjs'; 20 | 21 | 22 | const SORT_FUNCTION = new Map([ 23 | [SORT_TYPE.insert, insertSort], 24 | [SORT_TYPE.select, selectSort], 25 | [SORT_TYPE.shell, shellSort], 26 | [SORT_TYPE.descendingMergeSort, descendingMergeSort], 27 | [SORT_TYPE.ascendingMergeSort, ascendingMergeSort], 28 | [SORT_TYPE.quickSort, quickSort], 29 | [SORT_TYPE.threeWayQuickSort, threeWayQuickSort], 30 | [SORT_TYPE.heapSort, heapSort], 31 | ]); 32 | 33 | function run(arg = EXAMPLE, sortType = SORT_TYPE.shell, makeFrames = false){ 34 | const curriedSort = carry(SORT_FUNCTION.get(sortType), sortType, makeFrames) 35 | compose( 36 | timeTaken(curriedSort), 37 | saveFrames, 38 | logAlgorithmResult(sortType), 39 | )(arg) 40 | 41 | } 42 | 43 | const [, , size, type, makeFrameShot = false] = process.argv; 44 | 45 | run(generateData(Number(size)), STRING_TO_SYMBOL[type], makeFrameShot); -------------------------------------------------------------------------------- /algorithms/data-structures/binary-heap/PriorityQueue.mjs: -------------------------------------------------------------------------------- 1 | export class PriorityQueue { 2 | constructor() { 3 | this.queue = [null]; 4 | } 5 | 6 | insert(value) { 7 | const newQueue = [...this.queue, value]; 8 | this.queue = this.swim(newQueue, newQueue.length - 1); 9 | } 10 | 11 | delMax() { 12 | let max = this.queue[1]; 13 | this.queue = this.sink(this.exchange(this.queue, 1, this.queue.length - 1), 1); 14 | this.queue.pop(); 15 | return max; 16 | } 17 | 18 | swim(queue, index) { 19 | let newIndex = index; 20 | while (newIndex > 1 && this.less(queue, Math.floor(newIndex / 2), newIndex)) { 21 | queue = this.exchange(queue, newIndex, Math.floor(newIndex / 2)); 22 | newIndex = Math.floor(newIndex / 2); 23 | } 24 | return queue; 25 | } 26 | 27 | sink(queue, index) { 28 | let newIndex = index; 29 | while (2 * newIndex <= queue.length - 1) { 30 | let j = 2 * newIndex; 31 | if (j < queue.length - 1 && this.less(queue, j, j + 1)) { 32 | j++; 33 | } 34 | if (!this.less(queue, newIndex, j)) { 35 | break; 36 | } 37 | queue = this.exchange(queue, newIndex, j); 38 | newIndex = j; 39 | } 40 | return queue; 41 | } 42 | 43 | less(queue, index1, index2) { 44 | return queue[index1] < queue[index2]; 45 | } 46 | 47 | exchange(queue, index1, index2) { 48 | return queue.map((value, index) => { 49 | if (index === index1) return queue[index2]; 50 | if (index === index2) return queue[index1]; 51 | return value; 52 | }); 53 | } 54 | 55 | isEmpty() { 56 | return this.queue.length === 1; 57 | } 58 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | *.iml 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # TypeScript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # next.js build output 63 | .next 64 | /out 65 | 66 | # Nuxt.js build / generate output 67 | .nuxt 68 | dist 69 | 70 | # Vuepress build output 71 | .vuepress/dist 72 | 73 | # Serverless directories 74 | .serverless/ 75 | 76 | # Miscellaneous 77 | *.tmp 78 | *.tmp.* 79 | *.swp 80 | .DS_Store 81 | .idea 82 | .vscode 83 | .history 84 | 85 | # It's better to unpack these files and commit the raw source 86 | # git has its own built in compression methods 87 | *.7z 88 | *.dmg 89 | *.gz 90 | *.iso 91 | *.jar 92 | *.rar 93 | *.zip 94 | 95 | # Logs 96 | logs 97 | *.log 98 | npm-debug.log* 99 | yarn-debug.log* 100 | yarn-error.log* 101 | lerna-debug.log* -------------------------------------------------------------------------------- /algorithms/sort-playground/shell-sort/README.md: -------------------------------------------------------------------------------- 1 | # Сортировка Шелла: 2 | 3 | ## Описание алгоритма 4 | 5 | Сортировка Шелла - это усовершенствование сортировки вставками, которое первоначально старается отсортировать элементы, находящиеся на большом расстоянии друг от друга, и постепенно сокращает это расстояние. Её эффективность связана с возможностью перемещать элементы на большие расстояния за единицу времени. 6 | 7 | ## Преимущества и недостатки 8 | 9 | **Преимущества**: 10 | - Очень быстрые для средних наборов данных. 11 | - Эффективна для данных, которые уже частично отсортированы. 12 | 13 | **Недостатки**: 14 | - Сложнее в понимании и реализации, чем более простые сортировки. 15 | - Эффективность зависит от выбора последовательности промежутков. 16 | 17 | ## Имплементация 18 | 19 | Функция `shellSort` инициирует `gap` как 1 и увеличивает его до меньше трети от длины полного массива. Далее, пока `gap` больше или равен 1, она вызывает `insertSortBody` чтобы отсортировать элементы на расстоянии `gap` друг от друга и затем уменьшить `gap`. 20 | 21 | # Документация функции 22 | 23 | ```javascript 24 | export function shellSort(data) { 25 | 26 | /** 27 | * Сортирует массив данных "data" сортировкой Шелла. 28 | * 29 | * Параметры: 30 | * - data: Массив чисел для сортировки. 31 | * 32 | * Возвращает: 33 | * - Отсортированный по возрастанию массив. 34 | */ 35 | const size = data.length; 36 | let gap = 1; 37 | const PARTS = 3; 38 | 39 | // Увеличивает "gap", пока он не превышает треть от длины списка 40 | while (gap < size / PARTS) { 41 | gap = PARTS * gap + 1; 42 | } 43 | 44 | // Пока "gap" больше или равен 1, вызывает сортировку вставками для "gap"-а 45 | while (gap >= 1) { 46 | data = insertSortBody(data, size, gap); 47 | gap = Math.floor(gap / PARTS); 48 | } 49 | 50 | // Возвращает отсортированный массив 51 | return data; 52 | } 53 | ``` -------------------------------------------------------------------------------- /algorithms/data-structures/hash-table/README.md: -------------------------------------------------------------------------------- 1 | # Хеш-таблица на JavaScript 2 | 3 | Этот код реализует структуру данных - хеш-таблицу, которая основана на массиве и позволяет быстро получить значение по уникальному ключу. 4 | 5 | ## Описание кода 6 | 7 | ### Конструктор класса 8 | Принимает в качестве параметра размер хеш-таблицы и инициализирует ее пустыми "ведрами" (в виде массивов). 9 | 10 | ### Метод hash 11 | Получает строковый ключ и преобразует его в числовое значение - хеш, который служит индексом для массива "buckets". 12 | 13 | ### Метод set 14 | Принимает ключ и значение, генерирует хеш от ключа, добавляет пару ключ-значение в соответствующий "бакет". 15 | 16 | ### Метод get 17 | Принимает ключ, генерирует хеш от ключа, ищет в соответствующем "бакете" нужное значение и возвращает его. 18 | 19 | ### Метод hasKey 20 | Принимает ключ, генерирует хеш от ключа, ищет в соответствующем "бакете" ключ и возвращает true, если такой ключ присутствует, и false в противном случае. 21 | 22 | 23 | ## Зачем использовать хеш-таблицу 24 | 25 | Хеш-таблицы обычно используются, когда нужно быстро получать данные по уникальному ключу. В простом массиве или связном списке нам нужно пройти по всем элементам, чтобы найти нужный. Это может быть проблематично, когда данных очень много. Хеш-таблицы решают эту проблему, позволяя нам быстро находить элементы. 26 | 27 | ## Когда использовать хеш-таблицу 28 | 29 | Хеш-таблицы идеально подходят для хранения и поиска данных на основе уникальных ключей. Они могут использоваться, когда: 30 | - Требуется быстрый доступ к данным по ключу (например, в базах данных, кэшировании и т. д.) 31 | - Требуется высокая производительность для чтения и записи данных 32 | - Данные уникальны и могут быть упорядочены в виде пар ключ-значение 33 | 34 | Пожалуйста, обратите внимание: хеш-таблицы могут стать неэффективными, если коллизий хешей слишком много, так что хеш-функция и способ решения коллизий очень важны для поддержания производительности. -------------------------------------------------------------------------------- /algorithms/data-structures/2-3-4-binary-tree/README.md: -------------------------------------------------------------------------------- 1 | # 2–3–4 Дерево на JavaScript 2 | 3 | ## Описание 4 | 5 | Данный код представляет собой реализацию структуры данных, известной как 2–3–4 дерево. Это одна из версий B-дерева, где узел может иметь от 2 до 4 детей. 6 | 7 | 2–3–4 дерево идеально сбалансированное, что обеспечивает быструю операцию поиска (в худшем случае O(logN)), вставку и удаление элементов. 8 | 9 | ## Задачи, решаемые этой структурой данных 10 | 11 | 2–3–4 дерево используются в тех случаях, когда необходимо эффективно работать с большими массивами данных. Он версионирует достоинства бинарных деревьев поиска, но при этом избегает проблему их дисбаланса благодаря большей гибкости по количеству детей у каждого узла. 12 | 13 | Применимость: 14 | 15 | - Поиск, вставка и удаление элементы в больших массивах данных. 16 | - для реализации файловых систем и баз данных, где необходимо эффективное выполнение операций с большим набором данных. 17 | 18 | ## Описание кода 19 | 20 | Код состоит из двух основных классов: `Node` и `Tree234`. 21 | 22 | - Класс `Node` представляет собой узел в 2–3–4 дереве. В нем есть массив элементов и массив указателей на дочерние узлы. 23 | 24 | - Класс `Tree234` представляет собой непосредственно 2–3–4 дерево. В нем содержатся функции для операций поиска, вставки и деления узла. 25 | 26 | - Метод `find()` позволяет найти указанный элемент в дереве. 27 | 28 | - Метод `findItem()` применяется внутри метода `find()` и возвращает индекс искомого элемента в узле, если таковой есть. 29 | 30 | - Метод `getNextChild()` используется в процессе поиска и вставки для определения, в каком дочернем узле следует транзитивно продолжить выполнение соответствующей операции. 31 | 32 | - Метод `insert()` служит для вставки новых элементов в дерево. 33 | 34 | - Метод `split()` используется в процессе вставки для разделения узла, если он заполнен. 35 | 36 | *Примечание:* Реализация метода `split()` остается на усмотрение соответствующего программиста в соответствии с конкретной задачей. -------------------------------------------------------------------------------- /algorithms/data-structures/table/SymbolTable.mjs: -------------------------------------------------------------------------------- 1 | export class SymbolTable { 2 | constructor() { 3 | this.keyValuePairs = {}; 4 | } 5 | 6 | insertKeyValue(key, value) { 7 | this.keyValuePairs[key] = value; 8 | } 9 | 10 | getValue(key) { 11 | return this.keyValuePairs[key]; 12 | } 13 | 14 | deleteKey(key) { 15 | if(this.hasKey(key)) delete this.keyValuePairs[key]; 16 | } 17 | 18 | hasKey(key) { 19 | return this.keyValuePairs.hasOwnProperty(key); 20 | } 21 | 22 | isTableEmpty() { 23 | return this.numberOfPairs() === 0; 24 | } 25 | 26 | numberOfPairs() { 27 | return Object.keys(this.keyValuePairs).length; 28 | } 29 | 30 | minimumKey() { 31 | let keys = Object.keys(this.keyValuePairs); 32 | return Math.min.apply(null, keys); 33 | } 34 | 35 | maximumKey() { 36 | let keys = Object.keys(this.keyValuePairs); 37 | return Math.max.apply(null, keys); 38 | } 39 | 40 | highestKeyUnder(key) { 41 | let keys = this.getAllKeys().filter(k => k <= key); 42 | return Math.max.apply(null, keys); 43 | } 44 | 45 | lowestKeyAbove(key) { 46 | let keys = this.getAllKeys().filter(k => k >= key); 47 | return Math.min.apply(null, keys); 48 | } 49 | 50 | rankOfKey(key) { 51 | return this.getAllKeys().filter(k => k < key).length; 52 | } 53 | 54 | selectKey(k) { 55 | let keys = this.getAllKeys(); 56 | keys.sort((a, b) => a - b); 57 | return keys[k]; 58 | } 59 | 60 | deleteMinimumKey() { 61 | this.deleteKey(this.minimumKey()); 62 | } 63 | 64 | deleteMaximumKey() { 65 | this.deleteKey(this.maximumKey()); 66 | } 67 | 68 | subTableSize(low, high) { 69 | let keys = this.getAllKeys().filter(k => k >= low && k <= high); 70 | return keys.length; 71 | } 72 | 73 | keysWithin(low, high) { 74 | return this.getAllKeys().filter(k => k >= low && k <= high); 75 | } 76 | 77 | getAllKeys() { 78 | return Object.keys(this.keyValuePairs); 79 | } 80 | } -------------------------------------------------------------------------------- /algorithms/data-structures/binary-tree/BinaryTree.mjs: -------------------------------------------------------------------------------- 1 | export class Node { 2 | constructor(data) { 3 | this.data = data; 4 | this.left = null; 5 | this.right = null; 6 | } 7 | } 8 | 9 | export class BinarySearchTree { 10 | constructor() { 11 | this.root = null; 12 | } 13 | 14 | insert(data) { 15 | const newNode = new Node(data); 16 | 17 | if (this.root === null) { 18 | this.root = newNode; 19 | } else { 20 | this.insertNode(this.root, newNode); 21 | } 22 | } 23 | 24 | insertNode(node, newNode) { 25 | if (newNode.data < node.data) { 26 | if (node.left === null) { 27 | node.left = newNode; 28 | } else { 29 | this.insertNode(node.left, newNode); 30 | } 31 | } else { 32 | if (node.right === null) { 33 | node.right = newNode; 34 | } else { 35 | this.insertNode(node.right, newNode); 36 | } 37 | } 38 | } 39 | 40 | inOrderTraverse(node, callback) { 41 | if(node !== null) { 42 | this.inOrderTraverse(node.left, callback); 43 | callback(node.data); 44 | this.inOrderTraverse(node.right, callback) 45 | } 46 | } 47 | 48 | preOrderTraverse(node, callback) { 49 | if (node !== null) { 50 | callback(node.data); 51 | this.preOrderTraverse(node.left, callback); 52 | this.preOrderTraverse(node.right, callback); 53 | } 54 | } 55 | 56 | postOrderTraverse(node, callback) { 57 | if (node !== null) { 58 | this.postOrderTraverse(node.left, callback); 59 | this.postOrderTraverse(node.right, callback); 60 | callback(node.data); 61 | } 62 | } 63 | 64 | search(node, data) { 65 | if (node === null) { 66 | return null; 67 | } else if (data < node.data) { 68 | return this.search(node.left, data); 69 | } else if (data > node.data) { 70 | return this.search(node.right, data); 71 | } else { 72 | return node; 73 | } 74 | } 75 | 76 | getRootNode() { 77 | return this.root; 78 | } 79 | } -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/canConstructWithTabulation/README.md: -------------------------------------------------------------------------------- 1 | [canConstruct tabulation time code](https://www.youtube.com/watch?v=oBt53YbR9Kk&t=15650s) 2 | 3 | [Назад к содержанию](../README.md) 4 | 5 | # canConstructWithTabulation 6 | 7 | Функция `canConstructWithTabulation` предоставляет решение задачи проверки возможности построения целевой строки (`target`) из заданного набора подстрок (`parts`). 8 | 9 | ## Принцип работы 10 | 11 | Функция `canConstructWithTabulation` использует подход с табулированием (Tabulation) для заполнения и использования таблицы, которая представляет собой одномерный массив. Она работает следующим образом: 12 | 13 | 1. Создается таблица (`table`) размером `target.length + 1`. 14 | 2. Все элементы в таблице инициализируются значением `false`, кроме первого элемента (`table[0]`), который инициализируется значением `true`. 15 | 3. Для каждого индекса `i` от `0` до `target.length`: 16 | - Если значение `table[i]` равно `true`, то проверяются все подстроки из набора `parts`. 17 | - Если подстрока `target.slice(i, i + part.length)` равна подстроке `part`, то значение следующего элемента таблицы (`table[i + part.length]`) устанавливается в `true`. 18 | 4. Возвращается значение последнего элемента таблицы (`table[target.length]`), которое определяет возможность построения цели. 19 | 20 | ## Особенности реализации 21 | 22 | - Функция `canConstructWithTabulation` использует подход с табулированием для заполнения и использования таблицы. 23 | - Одномерный массив `table` представляет собой таблицу, в которой хранятся булевы значения, определяющие возможность построения цели. 24 | - Создается и инициализируется только одна таблица `table`, в которой сохраняются все промежуточные значения. 25 | - Вместо рекурсии, используется цикл для итерации по индексам и заполнения таблицы `table`. 26 | - Используется метод `slice` для получения подстроки цели `target` и сравнения с подстроками из набора `parts`. 27 | 28 | ## Анализ концепций 29 | 30 | - **Табулирование**: Функция `canConstructWithTabulation` использует подход с табулированием для создания и заполнения таблицы, чтобы определить возможность построения цели из набора подстрок. 31 | - **Динамическое программирование**: Принципы динамического программирования применяются в функции `canConstructWithTabulation`. Оптимальное решение задачи строится из оптимальных решений ее подзадач. -------------------------------------------------------------------------------- /algorithms/sort-playground/select-sort/README.md: -------------------------------------------------------------------------------- 1 | # Сортировка выбором: 2 | 3 | ## Описание алгоритма 4 | 5 | Сортировка выбором - это простой алгоритм, который исправляет одну позицию в списке за одну итерацию. В каждой итерации он ищет наименьший (или наибольший, в зависимости от порядка сортировки) элемент в неотсортированной части списка и заменяет его на правильное место. 6 | 7 | ## Преимущества и недостатки 8 | 9 | **Преимущества**: 10 | - Простота реализации. 11 | - Эффективен для небольших наборов данных. 12 | 13 | **Недостатки**: 14 | - Медленно работает на больших наборах данных: время выполнения в худшем случае составляет O(n^2). 15 | - Не адаптивен и не сохраняет порядка элементов, которые равны, поэтому не является стабильным. 16 | 17 | ## Имплементация 18 | 19 | Функция `selectSort` использует функцию `reduce` для перебора каждого элемента в массиве `data`. 20 | 21 | Для каждого элемента она инициирует `currentMinIndex` значением текущего индекса, а затем перебирает остальные элементы массива в поиске наименьшего значения. Если находит значение меньше текущего минимального, то она обновляет `currentMinIndex`. 22 | 23 | В конце каждого перебора `selectSort` использует функцию `swap`, чтобы обменять позицией текущий элемент и элемент с наименьшим значением. 24 | 25 | # Документация функции 26 | 27 | ```javascript 28 | export function selectSort(data) { 29 | /** 30 | * Отсортировывает массив данных "data" сортировкой выбором. 31 | * 32 | * Параметры: 33 | * - data: Массив чисел для сортировки. 34 | * 35 | * Возвращает: 36 | * - Отсортированный по возрастанию массив. 37 | */ 38 | 39 | return data.reduce((baseArray, currentElement, index) => { 40 | // Инициализация индекса минимального элемента текущим индексом 41 | let currentMinIndex = index; 42 | 43 | // Поиск индекса минимального элемента в остальной части массива 44 | for(let innerIndex = index + 1; innerIndex <= baseArray.length; innerIndex++) { 45 | if (less(baseArray[innerIndex], baseArray[currentMinIndex])) { 46 | currentMinIndex = innerIndex; 47 | } 48 | } 49 | 50 | // Обмен значений текущего и минимального элементов 51 | swap(baseArray, index, currentMinIndex); 52 | 53 | // Возвращение обновленного массива 54 | return baseArray; 55 | }, data) 56 | } 57 | ``` -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/bestSumWithTabulation/README.md: -------------------------------------------------------------------------------- 1 | [bestSum tabulation time code](https://www.youtube.com/watch?v=oBt53YbR9Kk&t=14841s) 2 | 3 | [Назад к содержанию](../README.md) 4 | 5 | # bestSumWithTabulation 6 | 7 | Функция `bestSumWithTabulation` предоставляет решение задачи по поиску наименьшей комбинации чисел из заданного набора (`numbers`), которая суммируется до целевого числа (`target`). 8 | 9 | ## Принцип работы 10 | 11 | Функция `bestSumWithTabulation` использует подход с табулированием (Tabulation) для построения и заполнения таблицы, которая представляет собой двумерный массив. Она работает следующим образом: 12 | 13 | 1. Создается таблица (`table`) размером `target + 1` (от `0` до `target`). 14 | 2. Все ячейки в таблице инициализируются значением `null`, кроме ячейки `table[0]`, которая инициализируется пустым массивом (`[]`). 15 | 3. Для каждого числа `i` от `0` до `target`: 16 | - Если комбинация чисел для значения `i` существует в таблице (`table[i]` не равно `null`), то для каждого числа `num` в наборе `numbers`: 17 | - Создается новая комбинация путем объединения текущей комбинации `table[i]` и числа `num` (`combination = [...table[i], num]`). 18 | - Если для ячейки `table[i + num]` нет комбинации или новая комбинация `combination` короче текущей комбинации таблицы, то обновляется ячейка `table[i + num]` значением `combination`. 19 | 4. Возвращается значение ячейки таблицы `table[target]`, представляющее наименьшую комбинацию чисел, суммирующихся до целевого числа. 20 | 21 | ## Особенности реализации 22 | 23 | - Функция `bestSumWithTabulation` использует подход с табулированием для заполнения таблицы и нахождения наименьшей комбинации чисел. 24 | - Двумерный массив `table` представляет собой таблицу, где каждая ячейка хранит комбинацию чисел для соответствующего значения `i`. 25 | - Создается и инициализируется только одна таблица `table`, в которой сохраняются все промежуточные комбинации чисел. 26 | - Вместо рекурсии, используется цикл для итерации по значениям и заполнения таблицы `table`. 27 | 28 | ## Анализ концепций 29 | 30 | - **Табулирование**: Функция `bestSumWithTabulation` использует подход с табулированием для создания таблицы и заполнения ее значениями, представляющими комбинации чисел. 31 | - **Динамическое программирование**: Принципы динамического программирования применяются в функции `bestSumWithTabulation`. Оптимальное решение задачи строится из оптимальных решений ее подзадач. -------------------------------------------------------------------------------- /algorithms/sort-playground/heap-sort/README.md: -------------------------------------------------------------------------------- 1 | # Пирамидальная сортировка с использованием IndexMinPQ 2 | 3 | Пирамидальная сортировка - это эффективный алгоритм сортировки, который использует бинарную пирамиду. В данном случае мы реализуем пирамидальную сортировку с использованием индексированной минимальной очереди с приоритетами (IndexMinPQ). 4 | 5 | ## Как это работает 6 | 7 | 1. Функция `heapSort` принимает на вход массив, который нужно отсортировать. 8 | 9 | ```javascript 10 | const array = [7, 2, 8, 4, 1, 6]; 11 | ``` 12 | 13 | 2. Создается объект `pq` класса `IndexMinPQ`, количество элементов которого будет равно размеру входного массива. 14 | 15 | ```javascript 16 | let pq = new IndexMinPQ(array.length); 17 | ``` 18 | 19 | 3. Все элементы входного массива вставляются в очередь с приоритетами. 20 | 21 | ```javascript 22 | for (let i = 0; i < array.length; i++) { 23 | pq.insert(i, array[i]); 24 | } 25 | ``` 26 | 27 | В результате этих операций в очереди формируется бинарная пирамида. 28 | 29 | 4. Затем происходит удаление элементов из очереди с приоритетами до тех пор, пока она не станет пустой. Каждый удаленный (минимальный) элемент добавляется в результирующий массив. 30 | 31 | ```javascript 32 | let sorted = []; 33 | while (!pq.isEmpty()) { 34 | sorted.push(pq.minItem()); 35 | pq.deleteMin(); 36 | } 37 | ``` 38 | 39 | В результате, элементы вставляются в результирующий массив в отсортированном порядке. 40 | 41 | 5. В итоге функция возвращает отсортированный массив. 42 | 43 | ```javascript 44 | return sorted; 45 | ``` 46 | 47 | ## Полный код алгоритма 48 | 49 | ```javascript 50 | function heapSort(array) { 51 | let pq = new IndexMinPQ(array.length); 52 | 53 | // Заполнение очереди с приоритетами элементами массива 54 | for (let i = 0; i < array.length; i++) { 55 | pq.insert(i, array[i]); 56 | } 57 | 58 | // Удаление элементов из очереди с приоритетами 59 | // и добавление их к результату в отсортированном порядке 60 | let sorted = []; 61 | while (!pq.isEmpty()) { 62 | sorted.push(pq.minItem()); 63 | pq.deleteMin(); 64 | } 65 | 66 | return sorted; 67 | } 68 | 69 | // Пример использования алгоритма 70 | const array = [7, 2, 8, 4, 1, 6]; 71 | const sortedArray = heapSort(array); // [1, 2, 4, 6, 7, 8] 72 | console.log(sortedArray); 73 | ``` 74 | 75 | В заключение, пирамидальная сортировка с использованием очереди с приоритетами представляет собой эффективный алгоритм, который обеспечивает упорядочивание элементов в общем случае за O(n log n) времени. -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/canConstruct/README.md: -------------------------------------------------------------------------------- 1 | [canConstruct memoization time code](https://www.youtube.com/watch?v=oBt53YbR9Kk&t=7965s) 2 | 3 | [Назад к содержанию](../README.md) 4 | 5 | # canConstruct 6 | 7 | Функция `canConstruct` предоставляет решение задачи проверки возможности построения целевой строки (`target`) из заданного массива слов (`wordBank`). 8 | 9 | ## Принцип работы 10 | 11 | Функция `canConstruct` использует рекурсивный подход с мемоизацией для уменьшения количества повторных вычислений и оптимизации работы. Она работает следующим образом: 12 | 13 | 1. Если цель (`target`) уже есть в мемо (`memo`), то возвращается сохраненное значение, чтобы избежать повторных вычислений. 14 | 2. Если цель пустая строка (`''`), значит, можно построить цель из слов, поэтому возвращается значение `true`. 15 | 3. Если массив слов (`wordBank`) пуст, значит, больше нет слов для проверки, поэтому возвращается значение `false`. 16 | 4. Для каждого слова (`word`) в массиве слов (`wordBank`): 17 | - Если слово (`word`) является префиксом цели (`target`), значит, можно удалить это слово из цели и проверить остаток. 18 | - Вычисляется остаток от удаления префикса (`suffix = target.slice(word.length)`). 19 | - Рекурсивно вызывается функция `canConstruct` для остатка (`suffix`) и того же массива слов (`wordBank`). 20 | - Если для остатка возможно построение цели (`canConstruct(suffix, wordBank, memo)` возвращает `true`), то возвращается значение `true` и сохраняется в мемо для текущей цели. 21 | 5. Если ни одно слово из массива слов не может быть использовано для построения цели, то возвращается значение `false` и сохраняется в мемо для текущей цели. 22 | 23 | ## Особенности реализации 24 | 25 | - Функция `canConstruct` использует рекурсию для поиска возможности построения цели из слов. 26 | - Мемоизация (использование объекта `memo`) позволяет сохранять результаты ранее вычисленных целей и избегать повторных вычислений для этих целей. 27 | - Функция `indexOf` используется для проверки, является ли слово префиксом цели. 28 | - Остаток (`suffix`) вычисляется путем удаления префикса (слова) из цели. 29 | - Полученные результаты сохраняются в мемо для каждой проверяемой цели. 30 | 31 | ## Анализ концепций 32 | 33 | - **Рекурсия**: Функция `canConstruct` использует рекурсию для разбиения задачи на более простые подзадачи и последовательного решения каждой из них. 34 | - **Мемоизация**: Использование объекта `memo` позволяет сохранять результаты ранее вычисленных целей и избегать повторных вычислений для этих целей. Это значительно улучшает производительность функции. 35 | - **Динамическое программирование**: Принципы динамического программирования применяются в функции `canConstruct`. Оптимальное решение за -------------------------------------------------------------------------------- /algorithms/data-structures/binary-heap/README.md: -------------------------------------------------------------------------------- 1 | # Binary Heap Implementation 2 | 3 | Данный репозиторий содержит реализацию бинарной кучи на JavaScript, которая основывается на реализации приоритетной очереди. 4 | 5 | ## Методы 6 | 7 | ### swim(queue, index) 8 | 9 | Этот метод (или "подниматель") перемещает элементы внутреннего представления кучи (`queue`), пока не соблюдается свойства кучи. Метод принимает саму кучу и индекс элемента, который необходимо "поднять". 10 | 11 | ```javascript 12 | function swim(queue, index) { 13 | while (index > 1 && less(queue, index/2, index)) { 14 | exchange(queue, index, index/2); 15 | index = index/2; 16 | } 17 | } 18 | ``` 19 | 20 | ### sink(queue, index) 21 | 22 | Этот метод (или "погружатель") удаляет элемент из кучи и затем выполняет необходимые перемещения для сохранения свойства кучи. Метод принимает кучу и индекс элемента, который нужно "погрузить". 23 | 24 | ```javascript 25 | function sink(queue, index) { 26 | while (2*index <= N) { 27 | let j = 2*index; 28 | if (j < N && less(queue, j, j+1)) j++; 29 | if (!less(queue, index, j)) break; 30 | exchange(queue, index, j); 31 | index = j; 32 | } 33 | } 34 | ``` 35 | 36 | ### less(queue, index1, index2) 37 | 38 | Метод для сравнения двух элементов кучи. Метод принимает саму кучи и индексы элементов для сравнения. Возвращает `true`, если первый элемент меньше второго. 39 | 40 | ```javascript 41 | function less(queue, index1, index2) { 42 | return queue[index1] < queue[index2]; 43 | } 44 | ``` 45 | 46 | ### exchange(queue, index1, index2) 47 | 48 | Метод для обмена двумя элементами на основе их индексов в куче. Метод принимает кучу и индексы элементов для обмена. 49 | 50 | ```javascript 51 | function exchange(queue, index1, index2) { 52 | let temp = queue[index1]; 53 | queue[index1] = queue[index2]; 54 | queue[index2] = temp; 55 | } 56 | ``` 57 | Примечание: В приведенных выше примерах использования методов N - это размер бинарной кучи. 58 | 59 | ## Пример использования 60 | 61 | ```javascript 62 | const pq = new PriorityQueue(); 63 | pq.insert(10); 64 | pq.insert(20); 65 | pq.insert(30); 66 | pq.insert(5); 67 | 68 | 69 | console.assert(pq.delMax() === 30, "Куча должна вернуть 30!"); 70 | console.assert(pq.delMax() === 20, "Куча должна вернуть 20!"); 71 | 72 | ``` 73 | В этом примере мы создаем новую очередь, добавляем в нее несколько элементов и затем удаляем самые большие элементы. 74 | 75 | Напоминаем, что в бинарной куче, реализованной в виде массива, индексы начинаются с 1, а не с 0. Поэтому первый элемент в массиве оставлен неизменным. Максимальный элемент всегда находится в queue[1]. Это упрощает вычисление индексов для родительского элемента и потомков. -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/README.md: -------------------------------------------------------------------------------- 1 | # Код по видеоуроку от [freeCodeCamp](https://www.youtube.com/watch?v=oBt53YbR9Kk&t=0s) 2 | 3 | # Руководство по динамическому программированию 4 | 5 | В этом репозитории представлены примеры задач и решений, связанных с динамическим программированием. Каждая задача имеет свой собственный ридми-файл с описанием задачи и примером решения. 6 | 7 | ## Что такое динамическое программирование? 8 | 9 | Динамическое программирование (Dynamic Programming) - это метод решения сложных задач путем разделения их на более простые подзадачи и повторного использования результатов подзадач для нахождения решения всей задачи. 10 | 11 | Преимущества и принципы динамического программирования: 12 | - Оптимальное решение задачи строится из оптимальных решений ее подзадач. 13 | - Избегает повторных вычислений путем сохранения результатов для переиспользования. 14 | - Используется для решения задач с "наложением подзадач", где оптимальное решение задачи содержит оптимальные решения подзадач. 15 | 16 | ## Как использовать примеры 17 | 18 | Каждая папка в этом репозитории содержит ридми-файл с описанием задачи и примером решения на динамическое программирование. Вам нужно выполнить команду, указанную в `package.json`, чтобы запустить примеры. 19 | 20 | Пример: 21 | ``` 22 | npm run fibonacci 23 | ``` 24 | 25 | ## Полезные ресурсы 26 | 27 | - [Статья на Wikipedia о динамическом программировании](https://en.wikipedia.org/wiki/Dynamic_programming) 28 | - [Статья по динамическому программированию](https://www.geeksforgeeks.org/dynamic-programming/) 29 | - [Рекомендации по решению алгоритмических задач с использованием таблиц](https://www.topcoder.com/community/competitive-programming/tutorials/dynamic-programming-from-novice-to-advanced/) 30 | 31 | ## Навигация по примерам 32 | 33 | - [Fibonacci](fibanacchi/README.md) 34 | - [Grid Traveler](gridTraveler/README.md) 35 | - [Can Sum](canSum/README.md) 36 | - [How Sum](howSum/README.md) 37 | - [Best Sum](bestSum/README.md) 38 | - [Can Construct](canConstruct/README.md) 39 | - [Count Construct](countConstruct/README.md) 40 | - [All Construct](allConstruct/README.md) 41 | - [Fibonacci with Table](fibanacciWithTable/README.md) 42 | - [Grid Traveler with Table](tableGridTraveler/README.md) 43 | - [Can Sum with Table](canSumWIthTable/README.md) 44 | - [Two Sum](twoSum/README.md) 45 | - [How Sum with Table](howSumWithTable/README.md) 46 | - [Best Sum with Table](bestSumWithTabulation/README.md) 47 | - [Can Construct with Table](canConstructWithTabulation/README.md) 48 | - [Count Construct with Table](countConstructWithTable/README.md) 49 | - [All Construct with Table](allConstructWithTable/README.md) 50 | 51 | Для получения подробных описаний и решений каждой задачи, перейдите по указанным ссылкам. -------------------------------------------------------------------------------- /algorithms/data-structures/2-3-binary-tree/README.md: -------------------------------------------------------------------------------- 1 | ## Реализация 2-3 дерева на JavaScript 2 | 3 | Представленный код - это базовая реализация структуры данных, известной как 2-3 дерево, написанная на JavaScript. 4 | 5 | ### Что такое 2-3 дерево? 6 | 7 | 2-3 дерево - это вид сбалансированного дерева поиска. В отличие от бинарного дерева поиска, узлы в 2-3 дереве могут хранить 1-2 значения и, соответственно, иметь 2 или 3 дочерних узла. Это делает 2-3 деревья более эффективными в поддержании баланса, что в свою очередь гарантирует эффективные операции поиска. 8 | 9 | ### Когда использовать 2-3 деревья? 10 | 11 | 2-3 деревья хорошо сбалансированы и могут гарантировать эффективные операции поиска. Они идеально подходят для случаев, когда требуются частые поиски и важно поддержание баланса в деревьях поиска. Примеры включают определенные виды индексации баз данных и файловых систем. 12 | 13 | ### Как работает данный код? 14 | 15 | Код включает в себя два основных класса: `Node` и `TwoThreeTree`. 16 | 17 | #### Класс `Node`: 18 | 19 | Класс `Node` представляет собой шаблон для узлов 2-3 дерева. Каждый экземпляр узла имеет свойство `data`, которое является массивом, способным хранить 1-2 значения, и массив `children`, в котором хранятся ссылки на дочерние узлы. 20 | 21 | В классе Node также есть метод `isLeaf()`, который проверяет, является ли текущий узел листовым. 22 | 23 | #### Класс `TwoThreeTree`: 24 | 25 | Класс `TwoThreeTree` предоставляет методы для работы с 2-3 деревьями. 26 | 27 | - `constructor`: Инициализирует новое 2-3 дерево. Дерево изначально пустое с свойством `root`, установленным как `null`. 28 | 29 | - `insert(value)`: Вставляет новое значение в дерево. 30 | 31 | - `_insert(value, root)`: Рекурсивный вспомогательный метод для вставки. Он определяет правильную позицию нового значения и при необходимости обрабатывает реструктуризацию дерева. 32 | 33 | - `_split(root)`: Вспомогательный метод, который обрабатывает разделение узлов, которые получают более 2 значений из-за вставок. 34 | 35 | ### Пример использования кода 36 | 37 | ```javascript 38 | const tree = new TwoThreeTree(); 39 | tree.insert(1); 40 | tree.insert(2); 41 | tree.insert(3); 42 | console.log(tree.root); 43 | ``` 44 | 45 | Этот код создает новое 2-3 дерево и вставляет три значения: 1, 2 и 3. После этого он выводит корневой узел дерева в консоль. Вывод представляет состояние дерева после этих вставок. 46 | 47 | Учтите, что это простая реализация. Для более надежного кода рассмотрите обработку крайних случаев и добавление дополнительных методов при необходимости, таких как `delete` или `find`. 48 | 49 | ## Заключение 50 | 51 | 2-3 деревья - мощные структуры данных для упорядочивания данных и обеспечения эффективных операций поиска. Эта реализация на JavaScript предоставляет полезную отправную точку для понимания работы этих деревьев. -------------------------------------------------------------------------------- /algorithms/sort-playground/insert-sort/README.md: -------------------------------------------------------------------------------- 1 | # Сортировка вставками: 2 | 3 | ## Описание алгоритма 4 | 5 | Сортировка вставками - это простой алгоритм сортировки, в котором каждый следующий неотсортированной элемент последовательно сравнивается с отсортированными элементами и помещается на свое соответствующее место. 6 | 7 | ## Преимущества и недостатки 8 | 9 | **Преимущества**: 10 | - Простота реализации. 11 | - Эффективный для небольших наборов данных. 12 | - Отлично работает с частично отсортированными массивами. 13 | 14 | **Недостатки**: 15 | - Не эффективны для больших наборов данных: в худшем случае время выполнения составляет O(n^2). 16 | - Не стабильна: может изменить относительный порядок равных элементов. 17 | 18 | ## Имплементация 19 | 20 | Функция `insertSort` принимает в качестве параметра массив `data`, который нужно отсортировать. Функция `insertSortBody` выполняет реальную работу, выполняя сортировку вставками. 21 | 22 | Эта функция генерирует подмассивы для каждого индекса `counter`, начиная с `gap` и заканчивая размером массива `size`. Затем, для каждого подмассива, цикл выполняет сравнение элементов и, если нужно, меняет их местами с использованием функции `swap`. 23 | 24 | # Документация функции 25 | 26 | ```javascript 27 | export function insertSort(data) { 28 | /** 29 | * Сортирует массив данных "data" сортировкой вставками. 30 | * 31 | * Параметры: 32 | * - data: Массив чисел для сортировки. 33 | * 34 | * Возвращает: 35 | * - Отсортированный по возрастанию массив. 36 | */ 37 | 38 | // Вычисляем размер массива 39 | let size = data.length; 40 | 41 | // Вызывает функцию, выполняющую сортировку вставками 42 | insertSortBody(data, size) 43 | 44 | // Возвращает упорядоченный массив 45 | return data; 46 | } 47 | 48 | export function insertSortBody(data, size, gap = 1) { 49 | /** 50 | * Вспомогательная функция для сортировки вставками. 51 | * 52 | * Параметры: 53 | * - data: Массив данных для сортировки. 54 | * - size: Размер массива data. 55 | * - gap: Расстояние между сравниваемыми элементами, по умолчанию 1. 56 | * 57 | * Возвращает: 58 | * - Отсортированный по возрастанию массив. 59 | */ 60 | 61 | // Внешний цикл перемещает границу отсортированной и неотсортированной части 62 | for (let counter = gap; counter < size; counter++) { 63 | 64 | /* Внутренний цикл сортирует отсортированные подмассивы, перемещая текущий элемент на правильное место */ 65 | for (let insertIndex = counter; insertIndex >= gap && less(data[insertIndex], data[insertIndex - gap]); insertIndex -= gap) { 66 | swap(data, insertIndex, insertIndex - gap); 67 | } 68 | } 69 | 70 | // Возвращает отсортированный массив 71 | return data; 72 | } 73 | ``` -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/twoSum/README.md: -------------------------------------------------------------------------------- 1 | [Назад к содержанию](../README.md) 2 | 3 | # `twoSum` 4 | 5 | ## Описание 6 | 7 | Функция `twoSum` предоставляет решение задачи "Two Sum" в рамках алгоритмического подхода с использованием хэш-таблицы. Она находит два числа в массиве `nums`, которые в сумме дают целевое число `target`, и возвращает их индексы. 8 | 9 | ## Принцип работы 10 | 11 | 1. Создание хэш-таблицы `map`. Хэш-таблица будет использоваться для хранения чисел из массива `nums` и их индексов в виде ключ-значение. 12 | 13 | 2. Проход по массиву `nums` с помощью цикла `for`. 14 | - Для каждого числа `nums[i]` в массиве: 15 | - Рассчитываем разницу `diff` между целевым числом `target` и текущим числом `nums[i]`. 16 | 17 | 3. Проверка, если значение `diff` (т.е. `diff` присутствует в хэш-таблице `map`) уже существует. 18 | - Если такое значение существует, это означает, что мы уже встречали `diff` ранее в массиве. Исходя из задачи, такое число должно быть одним из искомых чисел, а значит, мы нашли решение. 19 | - Возврат массива, содержащего индексы `map[diff]` и текущего индекса `i`, так как эти два числа суммируются до `target`. 20 | 21 | 4. Если значение `diff` еще не существует в хэш-таблице `map`, то сохраняем текущее число `nums[i]` и его индекс `i` в хэш-таблице `map`. 22 | - Это помогает нам отслеживать уже обработанные числа и их индексы, чтобы использовать их для проверки будущих чисел. 23 | 24 | 5. Возврат пустого массива, если не удалось найти пару чисел, суммирующуюся до `target`. 25 | 26 | ## Особенности реализации 27 | 28 | - Функция `twoSum` решает задачу "Two Sum" с использованием подхода хэш-таблицы. 29 | - В процессе выполнения функции: 30 | - Создается хэш-таблица `map`, где ключами являются числа из массива `nums`, а значениями — их индексы в массиве. 31 | - Происходит проход по массиву `nums` с помощью цикла `for`. 32 | - Для каждого числа рассчитывается разница относительно целевого числа `target`. 33 | - Если такая разница уже присутствует в хэш-таблице `map`, возвращается массив, содержащий индексы двух чисел, суммирующихся до `target`. 34 | - Если разница не найдена в хэш-таблице `map`, текущее число и его индекс сохраняются в хэш-таблице. 35 | - Если решение не найдено, возвращается пустой массив. 36 | 37 | ## Примеры использования 38 | 39 | ```javascript 40 | console.log(twoSum([2, 7, 11, 15], 9)); // [0, 1] 41 | console.log(twoSum([3, 2, 4], 6)); // [1, 2] 42 | console.log(twoSum([3, 3], 6)); // [0, 1] 43 | console.log(twoSum([1, 2, 3, 4, 5], 10)); // [] 44 | ``` 45 | 46 | ## Анализ использованных концепций 47 | 48 | - Хэш-таблица: Функция `twoSum` использует хэш-таблицу для хранения чисел из массива `nums` и соответствующих им индексов. Это позволяет быстро определить, существует ли уже значение `diff` (разница) в массиве и, как результат, найти пару чисел, суммирующихся до `target`. -------------------------------------------------------------------------------- /algorithms/data-structures/2-3-binary-tree/TwoThreeTree.mjs: -------------------------------------------------------------------------------- 1 | class Node { 2 | constructor(data, left = null, middle = null, right = null) { 3 | this.data = Array.isArray(data) ? data : [data]; 4 | this.children = [left, middle, right].filter(x => x); 5 | } 6 | 7 | isLeaf() { 8 | return !this.children.length; 9 | } 10 | } 11 | 12 | export class TwoThreeTree { 13 | constructor() { 14 | this.root = null; 15 | } 16 | 17 | insert(value) { 18 | if (!this.root) { 19 | this.root = new Node(value); 20 | return; 21 | } 22 | const { newChild, newRoot } = this._insert(value, this.root); 23 | if (newRoot) { 24 | this.root = newRoot; 25 | } else if (newChild) { 26 | this.root = new Node(newChild.data[0], this.root, newChild); 27 | } 28 | } 29 | 30 | _insert(value, root) { 31 | const index = root.data.findIndex(x => value < x); 32 | const foundIndex = index >= 0 ? index : root.data.length; 33 | 34 | if (root.isLeaf()) { 35 | root.data.splice(foundIndex, 0, value); 36 | if (root.data.length > 2) { 37 | return this._split(root); 38 | } 39 | return {}; 40 | } 41 | 42 | const { newChild, newRoot } = this._insert(value, root.children[foundIndex]); 43 | if (newRoot) { 44 | return { newRoot }; 45 | } else if (newChild) { 46 | root.data.splice(foundIndex, 0, newChild.data[0]); 47 | root.children[foundIndex] = newChild.children[0]; 48 | root.children.splice(foundIndex + 1, 0, newChild.children[1]); 49 | if (root.data.length > 2) { 50 | return { newChild: this._split(root) }; 51 | } 52 | } 53 | return {}; 54 | } 55 | 56 | find(value, node = this.root) { 57 | if (node == null) { 58 | return false; 59 | } 60 | 61 | for (let i = 0; i < node.data.length; i++) { 62 | if (value < node.data[i]) { 63 | if (node.children.length !== 0) { 64 | return this.find(value, node.children[i]); 65 | } 66 | return false; 67 | } 68 | else if (value === node.data[i]) { 69 | return true; 70 | } 71 | } 72 | 73 | if (node.children.length !== 0) { 74 | return this.find(value, node.children[node.data.length]); 75 | } 76 | return false; 77 | } 78 | 79 | _split(root) { 80 | const newRight = new Node(root.data.splice(2, 1), root.children.splice(2, 2)); 81 | const newLeft = new Node(root.data.splice(0, 1), root.children.splice(0, 2)); 82 | return new Node(root.data, newLeft, newRight); 83 | } 84 | } -------------------------------------------------------------------------------- /algorithms/sort-playground/ascending-merge-sort/README.md: -------------------------------------------------------------------------------- 1 | # Сортировка слиянием: 2 | 3 | ## Описание алгоритма 4 | 5 | Сортировка слиянием - это алгоритм сортировки, который на основе принципа "Разделяй и властвуй" разбивает список на отдельные элементы, а затем сливает их в порядке упорядочивания. 6 | 7 | Цель восходящей сортировки слиянием - сортировка, начиная с наименьших подмассивов и постепенно увеличивая их размер, пока весь массив не будет отсортирован. 8 | 9 | В отличие от обычной версии алгоритма сортировки слиянием, в восходящей сортировке слиянием не используется рекурсия. 10 | 11 | ## Преимущества и недостатки 12 | 13 | **Преимущества**: 14 | - Эффективный для больших наборов данных: в среднем время выполнения алгоритма сортировки слиянием составляет O(n log n). 15 | - Не зависит от исходного расположения данных. 16 | - Он работает стабильно, что означает, что он сохраняет относительный порядок равных элементов в отсортированном массиве. 17 | 18 | **Недостатки**: 19 | - Использует дополнительное пространство, равное размеру исходного массива, для слияния подмассивов. 20 | - Большее количество операций сравнения по сравнению с другими алгоритмами, например, QuickSort. 21 | - Рекурсивная реализация может вызвать переполнение стека для больших наборов данных. 22 | 23 | ## Имплементация 24 | 25 | Функция `ascendingMergeSort` принимает в качестве параметра массив `data`, который необходимо отсортировать. 26 | 27 | Сначала она создает вспомогательный массив `result`, затем инициализирует переменную `sliceIndex` значением 1. 28 | 29 | Внешний цикл `for` удваивает значение `sliceIndex` на каждой итерации начиная от 1, пока `sliceIndex` не превысит длину массива. 30 | 31 | Внутренний цикл `for` проходит по массиву `data`, разбивая его на подмассивы размером `sliceIndex` и сливая их в отсортированном порядке. 32 | 33 | Операция слияния выполняется с использованием внешней функции `merge()`. 34 | 35 | # Документация функции 36 | 37 | ```javascript 38 | export function ascendingMergeSort(data) { 39 | /** 40 | * Сортирует массив данных "data" восходящей сортировкой слиянием. 41 | * 42 | * Параметры: 43 | * - data: Массив чисел для сортировки. 44 | * 45 | * Возвращает: 46 | * - Отсортированный по восходящему методу слияния массив. 47 | */ 48 | 49 | // Инициализируем вспомогательные переменные 50 | const index = data.length; 51 | const result = new Array(index); 52 | 53 | // Установим диапазон для каждого разделения массива 54 | for (let sliceIndex = 1; sliceIndex < index; sliceIndex *= 2) { 55 | // Создаем подмассивы и производим слияние в отсортированном порядке 56 | for (let lowIndex = 0; lowIndex < index - sliceIndex; lowIndex += sliceIndex * 2) { 57 | // Вызываем функцию слияния для каждого подмассива 58 | merge(data, lowIndex, lowIndex + sliceIndex - 1, Math.min(lowIndex + sliceIndex * 2 - 1, index - 1), result); 59 | } 60 | } 61 | // Возвращаем отсортированный массив 62 | return data; 63 | } 64 | ``` -------------------------------------------------------------------------------- /algorithms/data-structures/min-indexed-binary-heap/README.md: -------------------------------------------------------------------------------- 1 | # IndexMinPQ - Очередь приоритетов по минимальному значению 2 | 3 | `IndexMinPQ` является классом, используемым для создания индексированной очереди с приоритетами на минимум. Очередь может содержать до `maxN` элементов, индексы используемые для вставки находятся в диапазоне от 0 до `maxN-1`. 4 | 5 | 6 | ## Методы 7 | 8 | ### constructor(maxN) 9 | Создает новый экземпляр `IndexMinPQ`. 10 | ```javascript 11 | const priorityQueue = new IndexMinPQ(10); 12 | ``` 13 | 14 | ### isEmpty() 15 | Проверяет, является ли очередь пустой. 16 | ```javascript 17 | priorityQueue.isEmpty(); // true 18 | priorityQueue.insert(1, 2); 19 | priorityQueue.isEmpty(); // false 20 | ``` 21 | 22 | ### contains(index) 23 | Проверяет, содержит ли очередь заданный индекс. 24 | ```javascript 25 | priorityQueue.contains(1); // true 26 | priorityQueue.contains(2); // false 27 | ``` 28 | 29 | ### insert(index, item) 30 | Вставляет элемент с указанной приоритетной очередью. 31 | ```javascript 32 | priorityQueue.insert(2, 3); 33 | priorityQueue.insert(1, 1); 34 | ``` 35 | 36 | ### minItem() 37 | Возвращает наименьший элемент в очереди. 38 | ```javascript 39 | priorityQueue.minItem(); // 1 40 | ``` 41 | 42 | ### minIndex() 43 | Возвращает индекс наименьшего элемента в очереди. 44 | ```javascript 45 | priorityQueue.minIndex(); // 1 46 | ``` 47 | 48 | ### deleteMin() 49 | Удаляет и возвращает наименьший элемент в очереди. 50 | ```javascript 51 | priorityQueue.deleteMin(); // 1 52 | ``` 53 | 54 | ### size() 55 | Возвращает количество элементов в очереди. 56 | ```javascript 57 | priorityQueue.size(); // 1 58 | ``` 59 | 60 | ## Пример работы 61 | 62 | ```javascript 63 | const priorityQueue = new IndexMinPQ(10); 64 | 65 | priorityQueue.insert(2, "Alice"); 66 | priorityQueue.insert(1, "Bob"); 67 | priorityQueue.insert(3, "Charlie"); 68 | 69 | console.log(priorityQueue.minItem()); // "Bob" 70 | 71 | priorityQueue.deleteMin(); 72 | 73 | console.log(priorityQueue.minItem()); // "Alice" 74 | ``` 75 | 76 | ## Пример мерджа двух коллекций 77 | 78 | ```javascript 79 | const priorityQueue1 = new IndexMinPQ(10); 80 | const priorityQueue2 = new IndexMinPQ(10); 81 | 82 | priorityQueue1.insert(2, "Alice"); 83 | priorityQueue1.insert(1, "Bob"); 84 | 85 | priorityQueue2.insert(3, "Charlie"); 86 | priorityQueue2.insert(4, "David"); 87 | 88 | const mergeMinPQs = (pq1, pq2) => { 89 | let merged = new IndexMinPQ(Math.max(pq1.maximumSize, pq2.maximumSize)); 90 | 91 | for (let i = 0; i < pq1.size(); i++) { 92 | merged.insert(pq1.priorityQueue[i+1], pq1.keyValues[pq1.priorityQueue[i+1]]); 93 | } 94 | for (let i = 0; i < pq2.size(); i++) { 95 | merged.insert(pq2.priorityQueue[i+1], pq2.keyValues[pq2.priorityQueue[i+1]]); 96 | } 97 | return merged; 98 | } 99 | 100 | let mergedQueue = mergeMinPQs(priorityQueue1, priorityQueue2); 101 | 102 | console.log(mergedQueue.minItem()); // "Bob" 103 | mergedQueue.deleteMin(); 104 | console.log(mergedQueue.minItem()); // "Alice" -------------------------------------------------------------------------------- /algorithms/data-structures/min-indexed-binary-heap/IndexMinPQ.mjs: -------------------------------------------------------------------------------- 1 | export class IndexMinPQ { 2 | constructor(maxN) { 3 | this.priorityQueue = []; 4 | this.reversePQ = []; 5 | this.keyValues = []; 6 | this.totalValues = 0; 7 | this.maximumSize = maxN; 8 | 9 | for (let index = 0; index <= maxN; index++) { 10 | this.reversePQ[index] = -1; 11 | } 12 | 13 | } 14 | 15 | isEmpty() { 16 | return this.totalValues === 0; 17 | } 18 | 19 | contains(index) { 20 | if (index < 0 || index >= this.maximumSize) throw new Error('index out of bounds'); 21 | return this.reversePQ[index] !== -1; 22 | } 23 | 24 | insert(index, item) { 25 | if (index < 0 || index >= this.maximumSize) throw new Error('index out of bounds'); 26 | if (this.contains(index)) throw new Error('index is already in the priority queue'); 27 | this.totalValues++; 28 | this.reversePQ[index] = this.totalValues; 29 | this.priorityQueue[this.totalValues] = index; 30 | this.keyValues[index] = item; 31 | this.swim(this.totalValues); 32 | } 33 | 34 | minItem() { 35 | if (this.totalValues === 0) throw new Error('Priority queue underflow'); 36 | return this.keyValues[this.priorityQueue[1]]; 37 | } 38 | 39 | minIndex() { 40 | if (this.totalValues === 0) throw new Error('Priority queue underflow'); 41 | return this.priorityQueue[1]; 42 | } 43 | 44 | deleteMin() { 45 | if (this.totalValues == 0) throw new Error('Priority queue underflow'); 46 | let minimum = this.priorityQueue[1]; 47 | this.exchange(1, this.totalValues--); 48 | this.sink(1); 49 | this.reversePQ[minimum] = -1; 50 | this.keyValues[minimum] = null; 51 | this.priorityQueue[this.totalValues + 1] = -1; 52 | return minimum; 53 | } 54 | 55 | size() { 56 | return this.totalValues; 57 | } 58 | 59 | swim(index) { 60 | while (index > 1 && this.more(Math.floor(index / 2), index)) { 61 | this.exchange(index, Math.floor(index / 2)); 62 | index = Math.floor(index / 2); 63 | } 64 | } 65 | 66 | sink(index) { 67 | while (2 * index <= this.totalValues) { 68 | let indexSubvalue = 2 * index; 69 | if (indexSubvalue < this.totalValues && this.more(indexSubvalue, indexSubvalue + 1)) indexSubvalue++; 70 | if (!this.more(index, indexSubvalue)) break; 71 | this.exchange(index, indexSubvalue); 72 | index = indexSubvalue; 73 | } 74 | } 75 | 76 | more(index1, index2) { 77 | return this.keyValues[this.priorityQueue[index1]] > this.keyValues[this.priorityQueue[index2]]; 78 | } 79 | 80 | exchange(index1, index2) { 81 | let tempIndex = this.priorityQueue[index1]; 82 | this.priorityQueue[index1] = this.priorityQueue[index2]; 83 | this.priorityQueue[index2] = tempIndex; 84 | this.reversePQ[this.priorityQueue[index1]] = index1; 85 | this.reversePQ[this.priorityQueue[index2]] = index2; 86 | } 87 | } -------------------------------------------------------------------------------- /algorithms/data-structures/binary-tree/README.md: -------------------------------------------------------------------------------- 1 | # Binary Search Tree 2 | 3 | Бинарное дерево поиска (Binary Search Tree, BST) - это древовидная структура данных, которая обладает следующими свойствами: 4 | 5 | - Узел содержит ключ и две связи : левую и правую. 6 | - Левая связь направлена на узел, ключ которого меньше ключа родителя. 7 | - Правая связь направлена на узел, ключ которого больше ключа родителя. 8 | - Левое и правое поддеревья также являются бинарными деревьями поиска. 9 | 10 | ## Класс Node 11 | 12 | Вершина дерева определяется классом Node, который включает в себя поля для хранения данных и ссылок на левый и правый дочерние узлы. 13 | 14 | ```javascript 15 | class Node { 16 | constructor(data) { 17 | this.data = data; 18 | this.left = null; 19 | this.right = null; 20 | } 21 | } 22 | ``` 23 | 24 | ## Класс BinarySearchTree 25 | 26 | ```javascript 27 | class BinarySearchTree { 28 | constructor() { 29 | this.root = null; 30 | } 31 | 32 | insert(data) { 33 | const newNode = new Node(data); 34 | 35 | if (this.root === null) { 36 | this.root = newNode; 37 | } else { 38 | this.insertNode(this.root, newNode); 39 | } 40 | } 41 | 42 | insertNode(node, newNode) { 43 | if (newNode.data < node.data) { 44 | if (node.left === null) { 45 | node.left = newNode; 46 | } else { 47 | this.insertNode(node.left, newNode); 48 | } 49 | } else { 50 | if (node.right === null) { 51 | node.right = newNode; 52 | } else { 53 | this.insertNode(node.right, newNode); 54 | } 55 | } 56 | } 57 | 58 | inOrderTraverse(node, callback) { 59 | if(node !== null) { 60 | this.inOrderTraverse(node.left, callback); 61 | callback(node.data); 62 | this.inOrderTraverse(node.right, callback) 63 | } 64 | } 65 | 66 | preOrderTraverse(node, callback) { 67 | if (node !== null) { 68 | callback(node.data); 69 | this.preOrderTraverse(node.left, callback); 70 | this.preOrderTraverse(node.right, callback); 71 | } 72 | } 73 | 74 | postOrderTraverse(node, callback) { 75 | if (node !== null) { 76 | this.postOrderTraverse(node.left, callback); 77 | this.postOrderTraverse(node.right, callback); 78 | callback(node.data); 79 | } 80 | } 81 | 82 | getRootNode() { 83 | return this.root; 84 | } 85 | } 86 | ``` 87 | 88 | ## Когда использовать дерево бинарного поиска 89 | 90 | Бинарное дерево поиска используется в случаях, когда требуется быстрый поиск, вставка и удаление данных. Используется в таких алгоритмах и структурах данных как: 91 | 92 | - Построение и анализ выражений 93 | - Базы данных 94 | - Маршрутизаторы в интернете 95 | - Графические алгоритмы 96 | 97 | Особенностью бинарных деревьев поиска является то, что они позволяют эффективно производить все базовые операции (поиск, вставка, удаление) в среднем за время O(log n), где n — количество узлов в дереве. Это приводит к значительной экономии времени обработки больших объемов данных. 98 | 99 | Обратите внимание, что это среднее время. В худшем случае (если дерево вырождается в список) эти операции займут время O(n). -------------------------------------------------------------------------------- /algorithms/sort-playground/quick-sort/README.md: -------------------------------------------------------------------------------- 1 | # Быстрая сортировка 2 | 3 | Быстрая сортировка - это эффективный алгоритм сортировки, изобретенный английским информатиком Чарльзом Хоаром, с основной идеей "разделяй и властвуй". Быстрая сортировка работает, разбивая массив на две части, вокруг "опорного" элемента. 4 | 5 | ## Плюсы и минусы 6 | Плюсы: 7 | 1. Это одна из самых быстрых известных универсальных сортировок. В среднем она работает в _O(n log n)_ раз. 8 | 2. Быстрая сортировка работает "на месте", что означает, что ей никогда не требуется больше _O(log n)_ оперативной памяти. 9 | 10 | Минусы: 11 | 1. В худшем случае быстрая сортировка занимает _O(n^2)_ раз, хотя это крайне редкое явление для случайно отсортированных массивов. Типичное число сравнений - это 39% больше _n log n_. 12 | 2. Быстрая сортировка не является стабильной сортировкой, что означает, что сортировка двух объектов с равными ключами может изменить их относительный порядок в разных отсортированных массивах. 13 | 14 | ## Имплементация с использованием JavaScript 15 | 16 | ```javascript 17 | import {less, swap} from '../utils.mjs'; 18 | 19 | /** 20 | * Экспортируемая функция `quickSort` принимает массив `data` 21 | * как аргумент, она вызывает вспомогательную функцию `sort`, 22 | * чтобы отсортировать весь массив, и возвращает отсортированный массив. 23 | * 24 | * @param {Array} data - Массив данных для сортировки. 25 | * @return {Array} - Отсортированный массив. 26 | */ 27 | export function quickSort(data) { 28 | sort(data, 0, data.length - 1); 29 | return data; 30 | } 31 | 32 | /** 33 | * `Sort` - это вспомогательная функция, которая вызывается рекурсивно. 34 | * Она принимает массив данных и два индекса как ввод, и сортирует 35 | * подмассив между этими двумя индексами. 36 | * 37 | * @param {Array} data - Массив данных для сортировки. 38 | * @param {Number} lowIndex - Нижний индекс для сортировки. 39 | * @param {Number} heightIndex - Верхний индекс для сортировки. 40 | */ 41 | function sort(data, lowIndex, heightIndex) { 42 | if (heightIndex <= lowIndex) { 43 | return; 44 | } 45 | 46 | const pivot = partition(data, lowIndex, heightIndex); 47 | sort(data, lowIndex, pivot - 1); 48 | sort(data, pivot + 1, heightIndex); 49 | } 50 | 51 | /** 52 | * `Partition` - это вспомогательная функция, которая взаимодействует 53 | * с подмассивом между двумя указанными индексами. Она выбирает опорный 54 | * элемент и переупорядочивает подмассив так, что элементы меньше опорного 55 | * элемента находятся слева от него, а большие - справа. Затем возвращает 56 | * индекс опорного элемента. 57 | * 58 | * @param {Array} data - Массив данных для сортировки. 59 | * @param {Number} lowIndex - Нижний индекс для сортировки. 60 | * @param {Number} heightIndex - Верхний индекс для сортировки. 61 | * @return {Number} - Индекс опорного элемента. 62 | */ 63 | function partition(data, lowIndex, heightIndex) { 64 | let leftPoint = lowIndex; 65 | let rightPoint = heightIndex + 1; 66 | const mid = data[lowIndex]; 67 | 68 | while (true) { 69 | while (less(data[++leftPoint], mid)) { 70 | if (leftPoint === heightIndex) { 71 | break; 72 | } 73 | } 74 | while (less(mid, data[--rightPoint])) { 75 | if (rightPoint === lowIndex) { 76 | break; 77 | } 78 | } 79 | 80 | if (leftPoint >= rightPoint) { 81 | break; 82 | } 83 | 84 | swap(data, leftPoint, rightPoint); 85 | } 86 | swap(data, lowIndex, rightPoint); 87 | return rightPoint; 88 | } 89 | ``` -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/tableGridTraveler/README.md: -------------------------------------------------------------------------------- 1 | [gridTraveler tabulation time code](https://www.youtube.com/watch?v=oBt53YbR9Kk&t=12137s) 2 | 3 | [Назад к содержанию](../README.md) 4 | 5 | # `tableGridTraveler` 6 | 7 | ## Описание 8 | 9 | Функция `tableGridTraveler` решает алгоритмическую задачу `gridTraveler` с помощью табуляции (или использования таблицы) в динамическом программировании. Она определяет количество возможных путей для перемещения из верхнего левого угла сетки размером `m x n` в правый нижний угол сетки, двигаясь только вниз или вправо. 10 | 11 | ## Принцип работы 12 | 13 | 1. Создание и инициализация таблицы `table`, имеющей размер `(m + 1) x (n + 1)`. 14 | - Длина и ширина таблицы равны `m + 1` и `n + 1` соответственно. 15 | - Использование дополнительного столбца и строки в таблице упрощает обработку граничных случаев и предотвращает выход за границы таблицы. 16 | 17 | 2. Инициализация начального значения в левом верхнем углу таблицы (`table[0][0]`) равным 1. 18 | - Это означает, что изначально существует один путь для перемещения в верхний левый угол сетки – не перемещаться ни вправо, ни вниз. 19 | 20 | 3. Заполнение таблицы `table` построчно и постолбцово. 21 | - Начиная с ячейки `table[0][1]` и продвигаясь вправо по первой строке, вычисляем количество путей до каждой ячейки на основе предыдущих значений в таблице. 22 | - Затем, начиная с ячейки `table[1][0]` и продвигаясь вниз по первому столбцу, вычисляем количество путей до каждой ячейки на основе предыдущих значений в таблице. 23 | - Значение в каждой ячейке `table[i][j]` равно сумме значений ячеек `table[i-1][j]` (предыдущая ячейка сверху) и `table[i][j-1]` (предыдущая ячейка слева). 24 | - Продолжаем этот процесс заполнения таблицы до ячейки `table[m][n]`, которая содержит итоговое количество путей. 25 | 26 | 4. Возвращение значения в правом нижнем углу таблицы (`table[m][n]`), которое представляет количество путей для достижения правого нижнего угла сетки. 27 | 28 | ## Особенности реализации 29 | 30 | - Функция `tableGridTraveler` использует подход табуляции в динамическом программировании для решения задачи `gridTraveler`. 31 | - Создается таблица `table` с размером `(m + 1) x (n + 1)`, где значения в каждой ячейке представляют количество путей для достижения соответствующей ячейки в сетке. 32 | - Используется один дополнительный столбец и одна дополнительная строка в таблице для упрощения обработки граничных случаев. 33 | - Заполнение таблицы происходит построчно и постолбцово, начиная со значения `table[0][0]` в верхнем левом углу. 34 | - Значение в каждой ячейке `table[i][j]` вычисляется путем суммирования значений ячеек `table[i-1][j]` (предыдущая ячейка сверху) и `table[i][j-1]` (предыдущая ячейка слева), что представляет собой накопление количества путей. 35 | - Возвращается значение в правом нижнем углу таблицы `table[m][n]`, которое представляет количество путей для достижения правого нижнего угла сетки размером `m x n`. 36 | 37 | ## Примеры использования 38 | 39 | ```javascript 40 | console.log(tableGridTraveler(2, 3)); // 3 41 | console.log(tableGridTraveler(3, 2)); // 3 42 | console.log(tableGridTraveler(3, 3)); // 6 43 | console.log(tableGridTraveler(4, 4)); // 20 44 | console.log(tableGridTraveler(5, 5)); // 70 45 | ``` 46 | 47 | ## Анализ использованных концепций 48 | 49 | - Динамическое программирование: Функция `tableGridTraveler` применяет подход динамического программирования с использованием табуляции для эффективного решения задачи `gridTraveler`. Процесс вычисления количества путей строится на основе предыдущих значений в таблице, чтобы избежать повторных вычислений и улучшить производительность алгоритма. 50 | -------------------------------------------------------------------------------- /algorithms/sort-playground/descending-merge-sort/README.md: -------------------------------------------------------------------------------- 1 | # Сортировка слиянием: 2 | 3 | ## Описание алгоритма 4 | 5 | Нисходящая сортировка слиянием - это разновидность алгоритма сортировки слиянием, которая начинает сразу разделять массив на две части и сортирует их перед тем, как выполнить слияние. 6 | 7 | Цель восходящей сортировки слиянием - создать отсортированный массив, начиная с наибольших подмассивов и постепенно уменьшая их размер. 8 | 9 | Основное различие между нисходящей и восходящей сортировкой слиянием заключается в моменте сортировки отдельных подмассивов. Нисходящая сортировка слиянием сначала разделяет массив, а потом сортирует, в то время как восходящая сортировка слиянием сначала сортирует маленькие подмассивы и только потом разделяет на большие подмассивы. 10 | 11 | ## Преимущества и недостатки 12 | 13 | **Преимущества**: 14 | - Массивы большего размера сортируются быстрее. 15 | - Эффективный для больших наборов данных: в среднем время выполнения алгоритма сортировки слиянием составляет O(n log n). 16 | - Не зависит от исходного расположения данных. 17 | 18 | **Недостатки**: 19 | - Использует дополнительное пространство, равное размеру исходного массива, для слияния подмассивов. 20 | - Может быть более медленным для меньших массивов по сравнению с другими алгоритмами, например, QuickSort. 21 | - Рекурсивная реализация может вызвать переполнение стека для больших наборов данных. 22 | 23 | ## Имплементация 24 | 25 | Функция `descendingMergeSort` принимает в качестве параметра массив `data`, который необходимо отсортировать. 26 | 27 | Она пользуется вспомогательной функцией `sort`, вызывая её для каждого подмассива - сначала для левой, затем для правой половинки. 28 | 29 | Эта вспомогательная функция вызывает себя рекурсивно до тех пор, пока подмассив имеет размер больше 1, а затем производит слияние двух отсортированных половинок в один отсортированный массив. 30 | 31 | # Документация функции 32 | 33 | ```javascript 34 | export function descendingMergeSort(data) { 35 | /** 36 | * Сортирует массив данных "data" нисходящей сортировкой слиянием. 37 | * 38 | * Параметры: 39 | * - data: Массив чисел для сортировки. 40 | * 41 | * Возвращает: 42 | * - Отсортированный по нисходящему методу слияния массив. 43 | */ 44 | 45 | // Инициализируем вспомогательный массив 46 | const result = []; 47 | 48 | // Вызывает рекурсивную функцию сортировки на массиве данных 49 | sort(data, 0, data.length - 1, result); 50 | 51 | // Возвращает упорядоченный массив 52 | return data; 53 | } 54 | 55 | function sort(data, lowIndex, heightIndex, result) { 56 | /** 57 | * Вспомогательная функция для разделения исходного массива и сортировки его частей. 58 | * 59 | * Параметры: 60 | * - data: Массив данных для сортировки. 61 | * - lowIndex: Нижний индекс подмассива для сортировки. 62 | * - heightIndex: Верхний индекс подмассива для сортировки. 63 | * - result: Вспомогательный массив для временного хранения данных. 64 | */ 65 | 66 | // Если нижний индекс равен или больше верхнего, возвращаемуправление - дальнейшее разделение массива невозможно 67 | if (heightIndex <= lowIndex) { 68 | return; 69 | } 70 | 71 | // Вычисляем индекс, который будет серединой массива 72 | const mid = lowIndex + (heightIndex - lowIndex) / 2; 73 | 74 | // Рекурсивно вызываем функцию сортировки для двух половинок массива 75 | sort(data, lowIndex, mid, result); 76 | sort(data, mid + 1, heightIndex, result); 77 | 78 | // Выполняем слияние двух подмассивов 79 | merge(data, lowIndex, mid, heightIndex, result); 80 | } 81 | ``` -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/canSumWIthTable/README.md: -------------------------------------------------------------------------------- 1 | [canSum tabulation time code](https://www.youtube.com/watch?v=oBt53YbR9Kk&t=13079s) 2 | 3 | [Назад к содержанию](../README.md) 4 | 5 | # Документация для функции canSumWithTable 6 | 7 | ## Описание 8 | 9 | Функция `canSumWithTable` предоставляет решение задачи проверки возможности получения целевой суммы (`targetSum`) путем сложения чисел из заданного массива (`numbers`). 10 | 11 | ```javascript 12 | export function canSumWithTable(targetSum, numbers) { 13 | const table = Array(targetSum + 1).fill(false); 14 | table[0] = true; 15 | 16 | for (let i = 0; i <= targetSum; i++) { 17 | if (table[i]) { 18 | for(const num of numbers) { 19 | table[i + num] = true; 20 | } 21 | } 22 | } 23 | return table[targetSum]; 24 | } 25 | ``` 26 | 27 | ## Принцип работы 28 | 29 | 1. Создание и инициализация таблицы `table` с размером `targetSum + 1`, заполненной значениями `false`. Первый элемент `table[0]` устанавливается в `true`, так как сумма 0 может быть получена без чисел. 30 | 31 | 2. Цикл `for`, проходящий от 0 до `targetSum`: 32 | 33 | - Проверка, является ли текущий элемент `table[i]` равным `true`, что означает, что сумма `i` может быть получена из предыдущих чисел. 34 | 35 | - Цикл `for-of`, проходящий по каждому числу `num` из массива `numbers`: 36 | 37 | - Установка значения `true` для элемента `table[i + num]`, что означает, что сумма `i + num` может быть получена. 38 | 39 | 3. Возврат значения элемента `table[targetSum]`, которое указывает, можно ли получить целевую сумму `targetSum` путем сложения чисел из массива. 40 | 41 | ## Особенности реализации 42 | 43 | - Функция `canSumWithTable` использует подход с использованием таблицы (`table`) для отслеживания возможности получения каждой целевой суммы. 44 | 45 | - Вместо рекурсии или мемоизации, используется таблица для хранения промежуточных результатов и отслеживания возможных сумм. 46 | 47 | - Таблица `table` представлена в виде одномерного массива, где индексы соответствуют суммам, а значения определяют, можно ли получить эту сумму. 48 | 49 | - Каждый элемент `table[i]` представляет возможность получения суммы `i`. При проверке наличия `table[i] === true`, происходит обновление значений элементов следующих сумм, которые могут быть получены добавлением числа `num`. 50 | 51 | ## Анализ концепций 52 | 53 | - **Таблица (Tabulation)**: Функция `canSumWithTable` использует подход с использованием таблицы (`table`), где значения в таблице отслеживают возможность получения каждой целевой суммы. Это позволяет избежать повторных вычислений и улучшает производительность алгоритма. 54 | 55 | - **Динамическое программирование**: Принципы динамического программирования применяются в функции `canSumWithTable`. Оптимальное решение задачи строится из оптимальных решений подпроблем. 56 | 57 | ## Примеры использования 58 | 59 | ```javascript 60 | console.log(canSumWithTable(7, [2, 3])); // true 61 | console.log(canSumWithTable(7, [5, 3, 4, 7])); // true 62 | console.log(canSumWithTable(7, [2, 4])); // false 63 | console.log(canSumWithTable(8, [2, 3, 5])); // true 64 | console.log(canSumWithTable(300, [7, 14])); // false 65 | ``` 66 | 67 | ## Временная сложность 68 | 69 | Временная сложность функции `canSumWithTable` составляет O(m * n), где `m` - значение `targetSum`, а `n` - количество чисел в массиве `numbers`. Поскольку алгоритм выполняет два вложенных цикла, в худшем случае он может выполнить `m * n` итераций. Однако, благодаря использованию таблицы (`table`), повторные вычисления избегаются, и каждое значение вычисляется только один раз, что улучшает производительность алгоритма. -------------------------------------------------------------------------------- /algorithms/data-structures/2-3-4-binary-tree/TwoTheeFourBinaryTree.mjs: -------------------------------------------------------------------------------- 1 | class DataItem { 2 | constructor(item) { 3 | this.item = item; 4 | } 5 | } 6 | 7 | class Node { 8 | constructor() { 9 | this.parent; 10 | this.dataItems = []; 11 | this.childArray = []; 12 | } 13 | 14 | isLeaf() { 15 | return !this.childArray[0]; 16 | } 17 | 18 | isFull() { 19 | return this.dataItems.length === 3; 20 | } 21 | 22 | getParent() { 23 | return this.parent; 24 | } 25 | 26 | insertItem(newItem) { 27 | this.dataItems.push(newItem); 28 | this.dataItems.sort((a, b) => a.item - b.item); 29 | } 30 | 31 | split() { 32 | if (this.dataItems.length === 3) { 33 | let dataItemCount = this.dataItems.length; 34 | let midItem = this.dataItems[dataItemCount / 2 | 0]; 35 | 36 | let newRight = new Node(); 37 | newRight.dataItems.push(this.dataItems.pop()); 38 | newRight.childArray[0] = midItem.rightChild; 39 | if (newRight.childArray[0]) newRight.childArray[0].parent = newRight; 40 | 41 | midItem.rightChild = newRight; 42 | if (this.parent) { 43 | this.parent.insertItem(midItem); 44 | this.parent.attachChild(this.parent.findInsertionPoint(midItem.item), newRight); 45 | } else { 46 | this.items = [this.dataItems[0]]; 47 | this.childArray = [this, midItem.rightChild]; 48 | } 49 | } 50 | } 51 | } 52 | 53 | export class TwoThreeFourTree { 54 | constructor() { 55 | this.root = new Node(); 56 | } 57 | 58 | find(key) { 59 | let curNode = this.root; 60 | let childNumber; 61 | 62 | while (true) { 63 | if ((childNumber = this.findItem(curNode, key)) !== -1) 64 | return childNumber; 65 | else if (curNode == null || curNode.childArray === undefined || curNode.childArray[0] == null) 66 | return -1; 67 | else 68 | curNode = this.getNextChild(curNode, key); 69 | } 70 | } 71 | 72 | findItem(node, key) { 73 | if (!node) { 74 | return -1; 75 | } 76 | 77 | for(let j = 0; j < node.dataItems.length; j++) { 78 | if(node.dataItems[j].item === key) 79 | return j; 80 | } 81 | 82 | return -1; 83 | } 84 | 85 | getNextChild(node, key) { 86 | if (!node) { 87 | return null; 88 | } 89 | 90 | let j; 91 | 92 | for(j = 0; j < node.dataItems.length; j++) { 93 | if(key < node.dataItems[j].item) 94 | return node.childArray[j]; 95 | } 96 | 97 | return node.childArray[j]; 98 | } 99 | 100 | insert(dValue) { 101 | let curNode = this.root; 102 | let newItem = new DataItem(dValue); 103 | 104 | while(true) { 105 | if(curNode == null) { 106 | console.error('Error: Node is null.'); 107 | return; 108 | } 109 | 110 | if(curNode.isFull()) { 111 | curNode.split(); 112 | curNode = curNode.getParent(); 113 | 114 | curNode = this.getNextChild(curNode, dValue); 115 | } else if(curNode.isLeaf()) { 116 | break; 117 | } else { 118 | curNode = this.getNextChild(curNode, dValue); 119 | } 120 | } 121 | 122 | curNode.insertItem(newItem); 123 | } 124 | } -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/howSum/README.md: -------------------------------------------------------------------------------- 1 | [howSum memoization time code](https://www.youtube.com/watch?v=oBt53YbR9Kk&t=5369s) 2 | 3 | [Назад к содержанию](../README.md) 4 | 5 | # Описание функции `howSum` 6 | 7 | Функция `howSum` представляет собой рекурсивный алгоритм для решения задачи нахождения комбинации чисел из массива `numbers`, которая в сумме дает значение `targetSum`. Она использует концепцию динамического программирования с техникой мемоизации для улучшения производительности при больших входных данных. 8 | 9 | ## Принцип работы 10 | 11 | Задача заключается в том, чтобы установить, существует ли набор чисел из заданного массива `numbers`, который суммируется в число `targetSum`. Если такой набор существует, функция возвращает его; если нет — возвращает `null`. 12 | 13 | ```javascript 14 | export function howSum(targetSum, numbers, memo = {}) { 15 | // Если целевая сумма уже была рассчитана, возвращаем результат из мемоизации 16 | if (targetSum in memo) return memo[targetSum]; 17 | 18 | // Если целевая сумма равна 0, возвращаем пустой массив (базовый случай) 19 | if (targetSum === 0) { 20 | return []; 21 | } 22 | 23 | // Если целевая сумма меньше 0, возвращаем null (нет решения) 24 | if (targetSum < 0) { 25 | return null; 26 | } 27 | 28 | // Перебираем все числа в массиве numbers 29 | for (const num of numbers) { 30 | // Вычисляем остаток от целевой суммы после вычитания текущего числа 31 | const remainder = targetSum - num; 32 | 33 | // Делаем рекурсивный вызов функции с новой целевой суммой (остаток) 34 | const remainderResult = howSum(remainder, numbers, memo); 35 | 36 | // Если получили действительный результат (можем получить целевую сумму) 37 | // добавляем текущее число в массив и возвращаем его 38 | if (remainderResult !== null) { 39 | memo[targetSum] = [...remainderResult, num]; 40 | return memo[targetSum] 41 | } 42 | } 43 | 44 | memo[targetSum] = null; 45 | return null; 46 | } 47 | ``` 48 | 49 | ### Объяснение кода и его работы: 50 | 51 | - **Мемоизация**: Словарь `memo` используется для хранения уже вычисленных значений `targetSum`. Это позволяет избегать повторных вычислений и значительно ускоряет работу алгоритма при больших входных данных. 52 | 53 | ```javascript 54 | if (targetSum in memo) return memo[targetSum]; 55 | ``` 56 | 57 | - **Базовые случаи**: 58 | - Если `targetSum` равна `0`, функция возвращает пустой массив, что означает, что сумма чисел достигнута без дополнительных элементов. 59 | 60 | ```javascript 61 | if (targetSum === 0) return []; 62 | ``` 63 | 64 | - Если `targetSum` меньше `### Описание функции `howSum` 65 | 66 | Функция `howSum` реализована в виде алгоритма поиска комбинации чисел из массива `numbers`, которые в сумме дают целевое число `targetSum`. Функция использует динамическое программирование с помощью мемоизации для предотвращения повторных одинаковых вычислений, что делает ее более эффективной по сравнению с наивным рекурсивным подходом. 67 | 68 | #### Принцип работы: 69 | 70 | 1. **Мемоизация:** На каждом шаге алгоритма проверяется, не было ли уже вычислено решение для текущей целевой суммы `targetSum`. Если было, результат возвращается немедленно. 71 | 72 | ```javascript 73 | if (targetSum in memo) return memo[targetSum]; 74 | ``` 75 | 76 | 2. **Базовый случай:** Если `targetSum` равна нулю, это означает, что комбинация для получения такой суммы найдена (пустой массив). 77 | 78 | ```javascript 79 | if (targetSum === 0) { 80 | return []; 81 | } 82 | ``` 83 | 84 | 3. **Отсечение ветви:** Если `targetSum` оказывается меньше нуля, это означает, что текущий путь не приведет к решению задачи. 85 | 86 | ```javascript 87 | if (targetSum < 0) { 88 | return null; 89 | } 90 | ``` 91 | -------------------------------------------------------------------------------- /algorithms/data-structures/table/README.md: -------------------------------------------------------------------------------- 1 | # SymbolTable 2 | 3 | Symbol Table является важной структурой данных, используемой во многих областях компьютерной науки, включая интерпретаторы и компиляторы, базы данных и т.д. 4 | 5 | ## Обзор 6 | 7 | Symbol Table представляет собой структуру данных для хранения пар ключ-значение. Ключами обычно являются идентификаторы, а значениями - информация, связанная с этими идентификаторами. 8 | 9 | Например, в компиляторе ключами будут имена переменных, а значениями - информация о типах этих переменных, их местоположении в памяти и так далее. 10 | 11 | ## Когда использовать Symbol Table 12 | 13 | - **Компиляция и интерпретация**: Наиболее распространённое использование Symbol Table - компиляция и интерпретация. Когда компилятор анализирует код, он создает таблицу символов для отслеживания имен и свойств переменных, функций и других элементов кода. 14 | 15 | - **Базы данных**: Symbol Table также используются в базах данных для отображения имен столбцов на соответствующие значения. 16 | 17 | - **Обнаружение дубликатов**: Если вам нужно найти дубликаты среди большого количества элементов, Symbol Table может оказаться полезным инструментом. Вместо просмотра каждого элемента, вы можете просто проверить, есть ли ключ в Symbol Table. 18 | 19 | - **Поиск**: Если вам нужно выполнить множество операций поиска, Symbol Table с хорошей хэш-функцией может существенно улучшить производительность по сравнению со стандартными методами, такими как поиск в массиве. 20 | 21 | Важно помнить, что эффективность Symbol Table зависит от выбора хорошей хэш-функции и решения проблемы коллизий хэшей. 22 | ## Использование 23 | 24 | ```javascript 25 | let table = new SymbolTable(); 26 | table.insertKeyValue(1, 'apple'); 27 | console.log(table.getValue(1)); // Выводит 'apple' 28 | ``` 29 | 30 | ## Методы 31 | 32 | ### insertKeyValue(key, value) 33 | 34 | Добавляет пару ключ-значение к таблице. Параметры `key` и `value` задают ключ и ассоциируемое с ним значение соответственно. 35 | 36 | ### getValue(key) 37 | 38 | Возвращает значение, ассоциированное с ключом `key`. Если такого ключа нет, то возвращается `undefined`. 39 | 40 | ### deleteKey(key) 41 | 42 | Удаляет пару ключ-значение с ключом `key`. Если такого ключа нет, то ничего не происходит. 43 | 44 | ### hasKey(key) 45 | 46 | Возвращает `true`, если ключ `key` присутствует в таблице, и `false` в противном случае. 47 | 48 | ### isTableEmpty() 49 | 50 | Возвращает `true`, если таблица пуста, и `false` в противном случае. 51 | 52 | ### numberOfPairs() 53 | 54 | Возвращает количество пар ключ-значение в таблице. 55 | 56 | ### minimumKey() 57 | 58 | Возвращает наименьший ключ в таблице. 59 | 60 | ### maximumKey() 61 | 62 | Возвращает наибольший ключ в таблице. 63 | 64 | ### highestKeyUnder(key) 65 | 66 | Возвращает наибольший ключ в таблице, который меньше или равен `key`. 67 | 68 | ### lowestKeyAbove(key) 69 | 70 | Возвращает наименьший ключ в таблице, который больше или равен `key`. 71 | 72 | ### rankOfKey(key) 73 | 74 | Возвращает количество ключей, которые меньше `key`. 75 | 76 | ### selectKey(k) 77 | 78 | Возвращает k-й ключ в отсортированном списке ключей. 79 | 80 | ### deleteMinimumKey() 81 | 82 | Удаляет пару ключ-значение с наименьшим ключом в таблице. 83 | 84 | ### deleteMaximumKey() 85 | 86 | Удаляет пару ключ-значение с наибольшим ключом в таблице. 87 | 88 | ### subTableSize(low, high) 89 | 90 | Возвращает количество ключей в диапазоне от `low` до `high`. 91 | 92 | ### keysWithin(low, high) 93 | 94 | Возвращает все ключи в диапазоне от `low` до `high`. 95 | 96 | ### getAllKeys() 97 | 98 | Возвращает все ключи в таблице. 99 | 100 | ## Тестирование 101 | 102 | Можно использовать любой излюбленный фреймворк для тестирования этого класса. В коде проекта должны быть примеры использования, которые можно использовать как тестовые случаи. -------------------------------------------------------------------------------- /algorithms/data-structures/black-red-tree/README.md: -------------------------------------------------------------------------------- 1 | # Красно-черное дерево 2 | 3 | ## Введение 4 | Этот код представляет собой простую реализацию красно-черного дерева на JavaScript. Красно-черное дерево - это вид двоичного дерева поиска, где каждый узел имеет атрибут цвета: красный или черный. 5 | 6 | Правила красно-черного дерева: 7 | 1. Каждый узел или красный, или черный. 8 | 2. Корневой узел всегда черный. 9 | 3. Если узел красный, то оба его дочерних узла - черные. 10 | 4. Порядок черных узлов на любом пути от корня до конечного узла должен быть одинаковым. 11 | 12 | Когда мы добавляем узел в красно-черное дерево, может нарушиться красно-черное свойство. Для этого мы используем `fixTree()` метод для исправления дерева, перекрашивая узлы и выполния повороты. 13 | 14 | ## Как это работает? 15 | Давайте проанализируем классы, представленные в коде. 16 | 17 | ### Класс Node 18 | Этот класс представляет узел в двоичном дереве поиска. Каждый узел имеет данные, цвет, ссылки на левый и правый дочерние узлы и ссылку на родительский узел. 19 | 20 | ### Класс RedBlackTree 21 | Основной класс нашей реализации. Имеет методы для вращения узлов, вставки новых узлов и исправления дерева после вставки. 22 | 23 | ## Когда применять? 24 | Красно-черное дерево применяют в различных структурах данных, которые основаны на двоичных деревьях поиска. Они обеспечивают эффективный балансированный поиск, вставку и удаление, гарантируя логарифмическую производительность в худшем случае. 25 | 26 | ## Примечание 27 | Цвет каждого узла важен при удержании дерева "сбалансированным" или обеспечении глубины O(log N) для каждого узла. В случае нарушения баланса, мы восстанавливаем случаи красно-черного дерева путем выполнения операций вращения и перекрашивания узлов. Важно отметить, что это упрощенное представление красно-черного дерева, и оно не содержит обработки дублирующих значений или методов для удаления узлов. 28 | 29 | ## Описание работы кода 30 | 31 | Ниже приведено детальное описание работы методов в коде: 32 | 33 | ### Конструктор класса Node 34 | Конструктор создает новый узел с данными, цветом, ссылками на левый и правый дочерние узлы и ссылкой на родительский узел. 35 | 36 | ### Метод `isRed` 37 | Метод возвращает true, если узел красный, и false, если узел черный. 38 | 39 | ### Конструктор класса RedBlackTree 40 | Конструктор создает пустое красно-черное дерево с корневым узлом, равным null. 41 | 42 | ### Метод `rotateLeft` 43 | Метод выполняет левое вращение вокруг узла, переданного в качестве аргумента. Идея заключается в том, что правый дочерний узел становится новым родителем текущего узла, а текущий узел становится левым дочерним узлом нового родителя. Для поддержания свойств двоичного дерева поиска, левый дочерний узел нового родителя (бывший правый дочерний узел текущего узла) становится правым дочерним узлом текущего узла. 44 | 45 | ### Метод `rotateRight` 46 | Метод `rotateRight` работает аналогично `rotateLeft`, но в обратную сторону. Левый дочерний узел становится родителем текущего узла, а текущий узел становится правым дочерним узлом нового родителя. 47 | 48 | ### Метод `insert` 49 | Метод `insert` добавляет новый узел в дерево. Если дерево пустое, новый узел становится корнем дерева. В противном случае вызывается рекурсивная функция `insertRec`, которая ищет подходящее место для нового узла. После того, как новый узел добавлен, вызывается метод `fixTree`, который проверяет, нарушаются ли свойства красно-черного дерева, и вносит соответствующие исправления. 50 | 51 | ### Метод `insertRec` 52 | Это рекурсивная функция, которая ищет подходящее место для вставки нового узла. Она сравнивает данные нового узла с данными текущего узла и идет влево или вправо в зависимости от результата сравнения. 53 | 54 | ### Метод `fixTree` 55 | Этот метод вызывается после вставки нового узла, чтобы исправить любые нарушения свойств красно-черного дерева. Он может выполнять вращение узлов и перекрашивание узлов для восстановления свойств. 56 | 57 | Эта реализация не включает в себя реализацию метода `fixTree` или методов удаления или поиска узлов в дереве, что могло бы дать полное понимание того, как работают красно-черные деревья. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Изучаем Алгоритмы на JavaScript 2 | 3 | Добро пожаловать в проект "Изучаем Алгоритмы на JavaScript"! Наша цель — помочь программистам разобраться в сложном мире алгоритмов и структур данных. Мы представляем вам коллекцию задач, решаемых на JavaScript, которые покрыты тщательно подобранными видеоуроками и документацией. Следуйте рекомендациям и примерам из каждого раздела, чтобы углубить свои знания и практические навыки. 4 | 5 | ## Структура проекта 6 | 7 | ### Папка `algorithms/algorithm-tasks` 8 | 9 | - **Код курса на YouTube**: Откройте эту папку, чтобы найти все материалы курса по динамическому программированию от FreeCodeCamp, которые разбираются в нашем цикле обучающих видеороликов. 10 | 11 | - [Читать README](algorithms/algorithm-tasks/README.md) 12 | 13 | - **Графовые алгоритмы и другие практики**: В этой папке вы найдете дополнительные задачи, которые будем регулярно обновлять. С каждым уроком предоставляется подробное описание и пояснения. 14 | 15 | - [Читать README](algorithms/algorithm-tasks/#название-папки/README.md) 16 | 17 | ### Папка `algorithms/data-structures` 18 | 19 | - **Код и документация структур данных**: Здесь вы можете изучить и экспериментировать с наиболее распространенными структурами данных. 20 | 21 | - Хеш-таблица: [Читать README](algorithms/data-structures/hash-table/README.md) 22 | - Список: [Читать README](algorithms/data-structures/table/README.md) 23 | - Минимальная индексированная двоична куча: [Читать README](algorithms/data-structures/min-indexed-binary-heap/README.md) 24 | - Красно-чёрное дерево: [Читать README](algorithms/data-structures/black-red-tree/README.md) 25 | - Двоичное дерево: [Читать README](algorithms/data-structures/binary-tree/README.md) 26 | - Двоичная куча: [Читать README](algorithms/data-structures/binary-heap/README.md) 27 | - 2-3 дерево: [Читать README](algorithms/data-structures/2-3-binary-tree/README.md) 28 | - 2-3-4 дерево: [Читать README](algorithms/data-structures/2-3-4-binary-tree/README.md) 29 | 30 | ### Папка `algorithms/sort-playground` 31 | 32 | - **Код и документация алгоритмов сортировки**: Этот раздел предназначен для того, чтобы познакомить вас с различными методами сортировки и их визуализацией. 33 | 34 | - По возрастанию с использованием слияния: [Читать README](algorithms/sort-playground/ascending-merge-sort/README.md) 35 | - По убыванию с использованием слияния: [Читать README](algorithms/sort-playground/descending-merge-sort/README.md) 36 | - Сортировка кучей: [Читать README](algorithms/sort-playground/heap-sort/README.md) 37 | - Сортировка вставками: [Читать README](algorithms/sort-playground/insert-sort/README.md) 38 | - Быстрая сортировка: [Читать README](algorithms/sort-playground/quick-sort/README.md) 39 | - Трехпутевая быстрая сортировка: [Читать README](algorithms/sort-playground/quick-three-way/README.md) 40 | - Сортировка выбором: [Читать README](algorithms/sort-playground/select-sort/README.md) 41 | - Сортировка Шелла: [Читать README](algorithms/sort-playground/shell-sort/README.md) 42 | 43 | В каждой директории с алгоритмами сортировки вы найдёте подробное описание и примеры использования. Мы также предусмотрели скрипты для запуска функций с различным количеством элементов, чтобы вы могли наблюдать за эффективностью алгоритмов в реальном времени. Результаты сортировок будут отображаться в лог-файлах в соответствующих папках. 44 | 45 | Кроме того, для удобства проведения экспериментов и наглядности работы алгоритмов предусмотрена визуализация. Инструкции по запуску визуализации вы найдете в README каждого алгоритма сортировки. 46 | 47 | ## Запуск проекта 48 | 49 | Каждый модуль проекта снабжён файлом `package.json`, который содержит все необходимые скрипты для запуска примеров и тестов. Чтобы начать работу с проектом, убедитесь, что на вашей машине установлены [Node.js](https://nodejs.org/en/download/) и [npm](https://www.npmjs.com/get-npm). 50 | 51 | Для установки зависимостей и запуска скриптов выполните следующие шаги: 52 | 53 | 1. Откройте терминал. 54 | 2. Перейдите в корневую директорию нужного модуля. 55 | 3. Запустите нужный скрипт, например: 56 | 57 | ```bash 58 | npm run descending-merge-sort 59 | ``` 60 | 61 | Список всех доступных скриптов вы найдете в файле package.json каждого модуля. -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/gridTraveler/README.md: -------------------------------------------------------------------------------- 1 | [gridTraveler memoization time code](https://www.youtube.com/watch?v=oBt53YbR9Kk&t=2319s) 2 | 3 | [Назад к содержанию](../README.md) 4 | 5 | # Описание функции `gridTraveler` 6 | 7 | Функция `gridTraveler` представляет собой рекурсивную реализацию задачи о количестве уникальных путей перемещения в сетке с размерами `m` на `n`. Эта задача заключается в том, чтобы подсчитать количество способов переместиться из верхнего левого угла сетки в нижний правый, двигаясь только вниз или вправо. 8 | 9 | ## Принцип работы 10 | 11 | Для понимания работы кода рассмотрим приведенный ниже алгоритм. 12 | 13 | ```javascript 14 | export function gridTraveler(m, n) { 15 | if (m === 1 && n === 1) { 16 | return 1; 17 | } 18 | if (m === 0 || n === 0) { 19 | return 0; 20 | } 21 | 22 | return gridTraveler(m - 1, n) + gridTraveler(m, n - 1); 23 | } 24 | ``` 25 | 26 | Рассмотрим подробно каждый шаг алгоритма: 27 | 28 | - **Базовый случай**: При `m === 1 && n === 1` мы находимся в нижнем правом углу сетки, что означает, что мы нашли один уникальный путь, поэтому возвращается `1`. 29 | 30 | - **Единица измерения пути**: Когда одна из сторон сетки равна `0` (то есть `m === 0 || n === 0`), переместиться вправо или вниз невозможно (так как это выйдет за пределы сетки), и потому количество уникальных путей в этом случае равно `0`. 31 | 32 | - **Рекурсивные вызовы**: Возвращаемое значение функции состоит из суммы двух результатов рекурсивных вызовов: `gridTraveler(m - 1, n)` и `gridTraveler(m, n - 1)`. Это соответствует кол ичеству уникальных путей, если мы переместимся вниз (уменьшим `m` на `1`, при этом `n` останется неизменным) и количеству уникальных путей, если мы переместимся вправо (уменьшим `n` на `1`, при этом `m` останется неизменным). Каждый рекурсивный вызов продолжает делить проблему на подпроблемы до тех пор, пока не будет достигнут базовый случай. 33 | 34 | ## Особенности реализации 35 | 36 | Текущая реализация `gridTraveler` использует базовую рекурсию без сохранения результатов подзадач. Это может привести к повторным вычислениям одной и той же подзадачи, особенно в случаях с большими значениями `m` и `n`, что делает процесс вычисления неоптимальным с точки зрения времени. 37 | 38 | Пример использования: 39 | ```javascript 40 | console.log(gridTraveler(2, 3)); // Выведет 3, так как есть три уникальных пути из (1,1) в (2,3) 41 | ``` 42 | 43 | ## Практический анализ 44 | 45 | ### Пространственная сложность 46 | 47 | Пространственная сложность данной реализации определяется глубиной рекурсивного стека, который в худшем случае может быть O(m+n). 48 | 49 | ### Временная сложность 50 | 51 | Временная сложность — экспоненциальная, точнее O(2^(m+n)), так как каждый вызов `gridTraveler` рекурсивно вызывает себя дважды, за исключением базовых случаев. 52 | 53 | ### Концепции 54 | 55 | Данный код использует несколько важных концепций и подходов: 56 | 57 | - **Рекурсия**: Код использует рекурсивные вызовы Функция `gridTraveler` демонстрирует классический пример применения рекурсии для решения комбинаторной задачи, сочетающий в себе простоту и выразительность, однако из-за экспоненциального роста числа рекурсивных вызовов может стать крайне неэффективным при увеличении размеров сетки. Это приводит к необходимости использования оптимизаций, таких как динамическое программирование, мемоизация или табуляция, чтобы сократить количество повторных вычислений. 58 | 59 | Для большей эффективности и улучшения временной сложности рекомендуется модифицировать функцию, добавив кэширование уже вычисленных значений, что значительно ускорит её работу: 60 | 61 | ```javascript 62 | const memo = {}; 63 | export function gridTraveler(m, n) { 64 | const key = m + ',' + n; 65 | // Проверка наличия значения в кэше 66 | if (key in memo) return memo[key]; 67 | if (m === 1 && n === 1) return 1; 68 | if (m === 0 || n === 0) return 0; 69 | 70 | // Кэширование результата перед возвратом 71 | memo[key] = gridTraveler(m - 1, n) + gridTraveler(m, n - 1); 72 | return memo[key]; 73 | } 74 | ``` 75 | 76 | Таким образом, с помощью простой оптимизации мы можем улучшить временную сложность этого алгоритма до O(m*n) и сделать его пригодным для решения задач на больших сетках, сохраняя результаты вычислений по мере продвижения. -------------------------------------------------------------------------------- /algorithms/sort-playground/quick-three-way/README.md: -------------------------------------------------------------------------------- 1 | # Three-way QuickSort 2 | 3 | Three-way QuickSort (3-Way Partitioning) - это улучшенная версия быстрой сортировки, которая обрабатывает одинаковые элементы более эффективно. Этот алгоритм разрабатывался одновременно с Дькстрой и Хоаром. Он разделяет массив на три части: 4 | 5 | - Меньше опорного. 6 | - Равные опорному. 7 | - Больше опорного. 8 | 9 | Такой подход помогает эффективно управлять последовательностями с большим количеством одинаковых ключей, исключая необходимость сравнивать эти элементы в дальнейшем. 10 | 11 | ## Плюсы 12 | 13 | - Более быстрое выполнение алгоритма на массивах с множеством повторяющихся элементов. 14 | - Алгоритм прерывает работу, если обнаруживается, что все элементы массива равны, сокращая время выполнения для таких случаев. 15 | 16 | ## Минусы 17 | 18 | - Этот алгоритм сложнее в понимании и реализации по сравнению с обычной быстрой сортировкой. 19 | 20 | ## Реализация 21 | 22 | Функция `threeWayQuickSort` вызывает вспомогательную функцию `sort` с аргументами: массив данных, начальным и конечным индексами. 23 | 24 | Функция `sort`: 25 | 26 | - Проверяет, меньше ли верхний индекс или равен нижнему индексу. Если это так, то возврат из функции. 27 | - Задает указатели `leftPointer`, `next` и `rightPointer`, а также `midData` как опорный элемент. 28 | - В цикле while, поочередно сравнивает каждый элемент с опорным. Если элемент меньше опорного, он перемещается влево, если больше - вправо. Если элемент равен опорному, он остается на месте. 29 | - Вызывает `sort` рекурсивно дважды после окончания цикла while для обработки элементов меньше и больше опорного. 30 | 31 | ## Отличия от классической быстрой сортировки 32 | 33 | Классическое быстрая сортировка, придуманная Хоаром, всегда использует двухчастное разделение (также известное как "Lomuto partition scheme"), которое неэффективно работает с массивами, содержащими много повторяющихся элементов. 34 | 35 | Это достигается за счет того, что элементы, равные опорному, не обрабатываются заново в последующих вызовах функции сортировки. Таким образом, алгоритм может значительно сократить количество операций, необходимых для сортировки массивов, содержащих много повторяющихся элементов, приводя к увеличению производительности. 36 | 37 | Главная разница с классической быстрой сортировкой заключается в этом расширенном методе разделения и в способности эффективно обрабатывать повторяющиеся элементы. 38 | 39 | ```javascript 40 | /** 41 | * Функция сортировки с помощью алгоритма трехчастной быстрой сортировки. 42 | * 43 | * @param {Array} data - Массив элементов, который нужно отсортировать. 44 | * @returns {Array} Возвращает отсортированный массив. 45 | */ 46 | export function threeWayQuickSort(data) { 47 | // Запускаем алгоритм сортировки на подмассиве, начинающемся с первого элемента и заканчивающемся последним. 48 | sort(data, 0, data.length - 1); 49 | return data; 50 | } 51 | 52 | /** 53 | * Вспомогательная рекурсивная функция для выполнения сортировки. 54 | * 55 | * @param {Array} data - Массив, который нужно отсортировать. 56 | * @param {Number} lowIndex - Нижний индекс участка массива, который нужно отсортировать. 57 | * @param {Number} heightIndex - Верхний индекс участка массива, который нужно отсортировать. 58 | */ 59 | function sort(data, lowIndex, heightIndex) { 60 | // Если верхний индекс меньше или равен нижнему индексу, можем прекратить сортировку. 61 | if (heightIndex <= lowIndex) { 62 | return; 63 | } 64 | 65 | let leftPointer = lowIndex; 66 | let next = lowIndex + 1; 67 | let rightPointer = heightIndex; 68 | const midData = data[lowIndex]; 69 | 70 | // Начинаем процесс разделения на три части. 71 | while (next <= rightPointer) { 72 | const compareResult = compare(data[next], midData); 73 | 74 | // Если текущий элемент меньше опорного, меняем местами текущий элемент и элемент, на который указывает leftPointer, после чего передвигаем указатели. 75 | if (compareResult < 0) { 76 | swap(data, leftPointer++, next++); 77 | } else if (compareResult > 0) { 78 | // Если текущий элемент больше опорного, меняем местами текущий элемент и элемент, на который указывает rightPointer, после чего передвигаем указатель rightPointer. 79 | swap(data, next, rightPointer--); 80 | } else { 81 | // Если текущий элемент равен опорному, просто передвигаем указатель next. 82 | next++; 83 | } 84 | } 85 | 86 | // Разделение закончено, теперь применяем это же рекурсивно для обоих разделений: элементов меньше и больше опорного. 87 | sort(data, lowIndex, leftPointer - 1); 88 | sort(data, rightPointer + 1, heightIndex); 89 | } 90 | ``` -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/countConstructWithTable/README.md: -------------------------------------------------------------------------------- 1 | [countConstruct tabulation time code](https://www.youtube.com/watch?v=oBt53YbR9Kk&t=16686s) 2 | 3 | [Назад к содержанию](../README.md) 4 | 5 | # Документация к функции countConstruct 6 | 7 | ## Обзор 8 | 9 | Функция `countConstruct` представляет собой реализацию алгоритма динамического программирования, целью которого является подсчет количества способов составления целевой строки (`target`) путем конкатенации элементов массива подстрок (`wordBank`). Данная функция является рекурсивной и использует мемоизацию для оптимизации производительности. 10 | 11 | ## Сигнатура функции 12 | 13 | ```javascript 14 | export function countConstruct(target, wordBank, memo = {}) 15 | ``` 16 | 17 | ### Параметры 18 | 19 | - `target`: Целевая строка, которую нужно составить. 20 | - `wordBank`: Массив строк, содержащих подстроки, которые могут быть использованы для составления целевой строки. 21 | - `memo`: Необязательный параметр, используемый для хранения результатов предыдущих вычислений. Это объект JavaScript, который служит кешем для мемоизации. 22 | 23 | ## Принцип работы функции 24 | 25 | Функция работает следующим образом: 26 | 27 | 1. Проверяется наличие `target` в качестве ключа в кеше мемоизации (`memo`). Если такое значение уже существует, функция сразу возвращает сохраненный результат: 28 | ```javascript 29 | if (target in memo) { 30 | return memo[target]; 31 | } 32 | ``` 33 | 34 | 2. Затем функция проверяет, не является ли `target` пустой строкой (`''`). Если это так, возвращается `1`, поскольку есть ровно один способ составить пустую строку — не использовать никаких элементов из `wordBank`: 35 | ```javascript 36 | if (target === '') { 37 | return 1; 38 | } 39 | ``` 40 | 41 | 3. Переменная `count` инициализируется значением `0`. В эту переменную будет накапливаться количество способов составления `target`: 42 | ```javascript 43 | let count = 0; 44 | ``` 45 | 46 | 4. Организуется цикл, который проходит по каждому слову `word` в массиве `wordBank`. Для каждого `word` выполняется проверка, начинается ли `target` с этого слова, с помощью метода `startsWith`: 47 | ```javascript 48 | for (const word of wordBank) { 49 | if (target.startsWith(word)) { 50 | ``` 51 | 52 | 5. Если `target` начинается с `word`, то делается рекурсивный вызов функции `countConstruct` с оставшейся частью строки `target` (после среза префикса `word`). Для этого используется метод `slice`, который удаляет из `target` уже использованное слово. Результат рекурсивного вызова добавляется к значению переменной `count`: 53 | ```javascript 54 | count += countConstruct(target.slice(word.length), wordBank, memo); 55 | } 56 | } 57 | ``` 58 | 59 | 6. После проверки всех слов в `wordBank`, значение `count` сохраняется в кеше мемоизации с ключом, соответствующим текущей `target`: 60 | ```javascript 61 | memo[target] = count; 62 | ``` 63 | 64 | 7. В конце функция возвращает количество способов (`count`), которыми можно составить `target`: 65 | ```javascript 66 | return count; 67 | ``` 68 | 69 | ### Особенности реализации 70 | 71 | - **Рекурсия**: Функция использует рекурсию для разделения проблемы на более мелкие подзадачи. 72 | 73 | - **Мемоизация**: Объект JavaScript используется в качестве кеша для сохранения результатов вычислений, чтобы избегать повторных расчетов одних и тех же подзадач. Это существенно сокращает время выполнения алгоритма, особенно на больших входных данных. 74 | 75 | - **Строковые операции**: `startsWith` проверяет, начинается ли строка с указанной подстроки, а `slice` используется для того 76 | чтобы получить оставшуюся часть строки после убранной подстроки. Эти операции позволяют нам разрабатывать рекурсивный процесс пошагово сокращая задачу до тех пор, пока не достигнем базового случая (пустая строка). 77 | 78 | Важно отметить, что использование методов `startsWith` и `slice` четко отражает логику "разделяй и властвуй", где задача разбивается на более мелкие и управляемые части, что является ключевой концепцией в алгоритмах и структурах данных. 79 | 80 | ### Концепции, используемые в коде 81 | 82 | - **Мемоизация**: Эта техника используется для сохранения результатов выполнения функции с целью их повторного использования. Таким образом, если функция вновь вызывается с теми же параметрами, она может вернуть результат из кеша, избегая тем самым дорогостоящей рекурсивной обработки. 83 | 84 | - **Рекурсия**: Этот метод в программировании означает, что функция вызывает сама себя. Рекурсия позволяет разбивать сложные задачи на управляемые участки до тех пор, пока не будет достигнут базовый случай, когда рекурсия прекращается. 85 | 86 | - **Immutable Strings**: В JavaScript строки являются неизменяемыми объектами, что означает, что после создания строки она не может быть изменена. Операции, такие как `slice`, создают новую строку, вместо изменения существующей, что важно при рекурсивных вызовах в функции. 87 | -------------------------------------------------------------------------------- /algorithms/data-structures/black-red-tree/BlackRedTree.mjs: -------------------------------------------------------------------------------- 1 | class Node { 2 | constructor(data, color, left = null, right = null, parent = null) { 3 | this.data = data; 4 | this.color = color; 5 | this.left = left; 6 | this.right = right; 7 | this.parent = parent; 8 | } 9 | 10 | isRed() { 11 | return this.color === "RED"; 12 | } 13 | } 14 | 15 | export class RedBlackTree { 16 | constructor() { 17 | this.root = null; 18 | } 19 | 20 | rotateLeft(node) { 21 | const rightChild = node.right; 22 | node.right = rightChild.left; 23 | 24 | if(node.right !== null) { 25 | node.right.parent = node; 26 | } 27 | 28 | node.parent = rightChild; 29 | rightChild.left = node; 30 | rightChild.parent = node.parent; 31 | node.parent = rightChild; 32 | 33 | if(rightChild.parent !== null) { 34 | if(rightChild.parent.left === node) { 35 | rightChild.parent.left = rightChild; 36 | } else if(rightChild.parent.right === node) { 37 | rightChild.parent.right = rightChild; 38 | } 39 | } 40 | 41 | return rightChild; 42 | } 43 | 44 | rotateRight(node) { 45 | const leftChild = node.left; 46 | node.left = leftChild.right; 47 | 48 | if(node.left !== null) { 49 | node.left.parent = node; 50 | } 51 | 52 | node.parent = leftChild; 53 | leftChild.right = node; 54 | leftChild.parent = node.parent; 55 | node.parent = leftChild; 56 | 57 | if(leftChild.parent !== null) { 58 | if(leftChild.parent.left === node) { 59 | leftChild.parent.left = leftChild; 60 | } else if(leftChild.parent.right === node) { 61 | leftChild.parent.right = leftChild; 62 | } 63 | } 64 | 65 | return leftChild; 66 | } 67 | 68 | insert(data) { 69 | let newNode; 70 | if(this.root === null) { 71 | newNode = new Node(data, "BLACK"); 72 | this.root = newNode; 73 | } else { 74 | newNode = this.insertRec(this.root, data); 75 | this.fixTree(newNode); 76 | } 77 | 78 | return newNode; 79 | } 80 | 81 | insertRec(node, data) { 82 | if(data < node.data) { 83 | if(node.left === null) { 84 | node.left = new Node(data, "RED", null, null, node); 85 | return node.left; 86 | } else { 87 | return this.insertRec(node.left, data); 88 | } 89 | } else if(data > node.data) { 90 | if(node.right === null) { 91 | node.right = new Node(data, "RED", null, null, node); 92 | return node.right; 93 | } else { 94 | return this.insertRec(node.right, data); 95 | } 96 | } 97 | 98 | return null; 99 | } 100 | 101 | fixTree(node) { 102 | let parent, grandparent; 103 | while (node !== this.root && node.color !== "BLACK" && node.parent.color === "RED") { 104 | parent = node.parent; 105 | grandparent = parent.parent; 106 | if (parent === grandparent.left) { 107 | let uncle = grandparent.right; 108 | if (uncle && uncle.color === "RED") { 109 | grandparent.color = "RED"; 110 | parent.color = "BLACK"; 111 | uncle.color = "BLACK"; 112 | node = grandparent; 113 | } else { 114 | if (node == parent.right) { 115 | this.rotateLeft(parent); 116 | node = parent; 117 | parent = node.parent; 118 | } 119 | this.rotateRight(grandparent); 120 | let tempColor = parent.color; 121 | parent.color = grandparent.color; 122 | grandparent.color = tempColor; 123 | node = parent; 124 | } 125 | } else { 126 | let uncle = grandparent.left; 127 | if (uncle && uncle.color === "RED") { 128 | grandparent.color = "RED"; 129 | parent.color = "BLACK"; 130 | uncle.color = "BLACK"; 131 | node = grandparent; 132 | } else { 133 | if (node === parent.left) { 134 | this.rotateRight(parent); 135 | node = parent; 136 | parent = node.parent; 137 | } 138 | this.rotateLeft(grandparent); 139 | let tempColor = parent.color; 140 | parent.color = grandparent.color; 141 | grandparent.color = tempColor; 142 | node = parent; 143 | } 144 | } 145 | } 146 | this.root.color = "BLACK"; 147 | } 148 | } -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/howSumWithTable/README.md: -------------------------------------------------------------------------------- 1 | [howSum tabulation time code](https://www.youtube.com/watch?v=oBt53YbR9Kk&t=13981s) 2 | 3 | [Назад к содержанию](../README.md) 4 | 5 | ## Описание функции `howSumWithTable` 6 | 7 | Функция `howSumWithTable` представляет собой реализацию алгоритма динамического программирования для решения задачи комбинаторной суммы. Функция находит массив чисел, который в сумме даёт заданное целочисленное значение `target`, используя элементы из предоставленного массива `nums`. 8 | 9 | ### Принцип работы функции 10 | 11 | Функция `howSumWithTable` использует "таблицу" (массив) для хранения промежуточных результатов решения задачи на каждом шаге до достижения конечного результата. Это позволяет оптимизировать вычисления и избежать множественных повторных расчётов. 12 | 13 | #### Особенности реализации и анализ концепций: 14 | 15 | 1. **Инициализация таблицы:** 16 | 17 | ```javascript 18 | const table = Array(target + 1).fill(null); 19 | table[0] = []; 20 | ``` 21 | 22 | Здесь создаётся массив `table` длиной `target + 1` и заполняется `null`, кроме первого элемента, который устанавливается в пустой массив. Это представляет собой базовый случай, когда сумма равна 0, то есть нам не нужно ни одно число для получения суммы 0. 23 | 24 | 2. **Итерация по таблице:** 25 | 26 | ```javascript 27 | for (let i = 0; i <= target; i++) { 28 | if (table[i] !== null) { 29 | ... 30 | } 31 | } 32 | ``` 33 | 34 | Алгоритм проходит по всем индексам таблицы от 0 до `target`. Если текущая ячейка таблицы не `null`, то это означает, что существует комбинация чисел, в сумме дающих текущую сумму, представленную индексом `i`. 35 | 36 | 3. **Добавление новых комбинаций:** 37 | 38 | Внутри основного цикла `for`, если ячейка таблицы `table[i]` не равна `null`, алгоритм пытается добавить каждое число из массива `nums` к текущей сумме `i` и обновить таблицу новыми комбинациями: 39 | 40 | ```javascript 41 | for (const num of nums) { 42 | if (table[i]) { 43 | table[i + num] = [...table[i], num]; 44 | } 45 | } 46 | ``` 47 | 48 | Здесь для каждого числа `num` из массива `nums`, если сумма `i` уже достижима (т.е. `table[i]` не `null` и содержит массив), то алгоритм создаёт новую комбинацию, добавляя текущее число `num` к существующему массиву, который представляет достижимую сумму `i`. Эта новая комбинация сохраняется в `table[i + num]`. 49 | 50 | Всё это позволяет алгоритму "собирать" комбинации по ходу итерации и избавляться от необходимости рассматривать каждую возможную комбинацию чисел независимо. 51 | 52 | 4. **Возврат результата:** 53 | 54 | ```javascript 55 | return table[target]; 56 | ``` 57 | 58 | После завершения всех итераций по таблице возвращается элемент `table[target]`, представляющий список чисел, которые в сумме дают целевое значение `target`. Если такой список не найден, функция вернёт `null`. 59 | 60 | ### Анализ используемых концепций: 61 | 62 | - **Динамическое программирование**: функция `howSumWithTable` является примером динамического программирования (ДП), где решение строится инкрементально, используя результаты решения подзадач. В данном случае подзадачи состоят в нахождении комбинаций чисел, которые дают сумму меньше целевой (`target`). Этот подход избавляет от необходимости повторного вычисления одинаковых подзадач, что стандартно для задач, где применяется рекурсивный подход без кэширования (мемоизации) промежуточных результатов. 63 | 64 | - **Мемоизация**: Хоть и термин "мемоизация" обычно привязывают к кэшированию результатов рекурсивных функций, в данном алгоритме принцип схож. Массив `table` является аналогом кэша, где сохраняются результаты решения подзадач (например, список чисел, дающих сумму `i`). Это позволяет избежать повторной обработки уже рассмотренной суммы. 65 | 66 | - **Табуляция**: Другой метод динамического программирования, встречающийся в `howSumWithTable`, это табуляция. Вместо рекурсивных вызовов, табулированный подход использует итерации и "строит" решение, наполняя таблицу (массив) изначально от меньших к большим значениям. В данном случае таблица заполняется от `table[0]` до `table[target]`. 67 | 68 | - **Временная сложность**: Алгоритм имеет временную сложность O(m*n), где `m` — целевое число (`target`), а `n` — количество чисел в массиве `nums`. Каждый элемент массива `nums` рассматривается один раз на каждом уровне `i`, что определяет общую временную сложность. 69 | 70 | - **Пространственная сложность**: Пространственная сложность алгоритма также составляет O(m*n), поскольку необходимо хранить таблицу размером `m + 1`, где каждый элемент может содержать массив чисел, максимальная длина которого равна `n`. 71 | 72 | Следует отметить, что сложность может быть меньше в случае, когда числа в массиве `nums` позволяют найти сумму меньшей длины. Однако в худшем случае, когда минимальные элементы повторяются, в каждой ячейке таблицы будет находиться массив размером до `n`. 73 | 74 | ### Заключение 75 | 76 | Функция `howSumWithTable` представляет собой интересный пример использования методов динамического программирования для эффективного решения проблемы поиска комбинации чисел, сумма которых равна заданному целому числу. Благодаря использованию "таблицы" для хранения промежуточных результатов, данный алгоритм позволяет уменьшить как временную, так и пространственную сложности по сравнению с наивными рекурсивными подходами. Это делает его подходящим для решения задач, где важна оптимизация производительности, особенно в случаях с большими целевыми суммами и массивами чисел. -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/allConstruct/README.md: -------------------------------------------------------------------------------- 1 | [allConstruct memoization time code](https://www.youtube.com/watch?v=oBt53YbR9Kk&t=10050s) 2 | 3 | [Назад к содержанию](../README.md) 4 | 5 | # allConstruct 6 | ```javascript 7 | /** 8 | * Функция allConstruct возвращает все возможные комбинации, которыми можно 9 | * построить target строку, используя слова из массива wordBank. Реализация 10 | * использует мемоизацию для улучшения производительности. 11 | * 12 | * @param {string} target - Целевая строка для построения. 13 | * @param {string[]} wordBank - Массив слов, которые можно использовать для построения. 14 | * @param {Object} memo - Объект для мемоизации промежуточных результатов. 15 | * @returns {Array>} - Массив, содержащий все уникальные комбинации слов, которые строят target. 16 | * 17 | * Комплексность времени: O(n^m) - где n - количество слов в wordBank, m - длина target строки. 18 | * Комплексность пространства: O(m) - максимальная глубина рекурсивного дерева вызовов. 19 | */ 20 | 21 | export function allConstruct(target, wordBank, memo = {}) { 22 | // Проверка мемоизированных результатов O(1) - константная 23 | if (target in memo) { 24 | return memo[target]; // O(1) - влияние на общую сложность константное 25 | } 26 | 27 | // Базовый случай: пустая target строка O(1) 28 | if (target === '') { 29 | return [[]]; // O(1) - константное влияние на сложности 30 | } 31 | 32 | const result = []; // Инициализация массива для хранения результатов O(1) 33 | 34 | // Цикл по каждому слову в wordBank O(n) 35 | for (const word of wordBank) { 36 | // Проверяется, начинается ли target с текущего слова O(m) 37 | if (target.startsWith(word)) { 38 | // Вычисляем суффикс, убирая найденное слово из начала target O(m) 39 | const suffix = target.slice(word.length); 40 | 41 | // Рекурсивно строим все конструкции для суффикса O(n^m) 42 | // (рассмотрены все части target за все итерации) 43 | const suffixWays = allConstruct(suffix, wordBank, memo); 44 | 45 | // Создаем конструкции для текущего target, добавив word перед каждым путем из suffixWays O(l), где l - длина suffixWays 46 | const targetWays = suffixWays.map(way => [word, ...way]); 47 | 48 | // Добавляем полученные пути в общий результат O(l), где l - общее количество путей 49 | result.push(...targetWays); 50 | } 51 | } 52 | 53 | // Сохраняем вычисленный результат в memo для текущего target O(m) 54 | memo[target] = result; 55 | 56 | // Возвращаем результат O(1) 57 | return result; 58 | } 59 | ``` 60 | 61 | Каждый вызов функции `allConstruct` может вызвать множество других вызовов самой себя в зависимости от количества слов в `wordBank`, которые могут быть префиксами `target`. Следовательно, если не учитывать оптимизацию через мемоизацию, то каждый неразрешенный вызов может порождать до `n` дальнейших вызовов где `n` - это количество слов в `wordBank`. Таким образом, пути возможных вызовов функции образуют дерево, где каждый узел может иметь до `n` дочерних узлов. В худшем случае функция на каждом уровне будет вызывать 62 | 63 | ## Принцип работы 64 | 65 | Функция `allConstruct` использует рекурсивный подход с мемоизацией для уменьшения количества повторных вычислений и оптимизации работы. Она работает следующим образом: 66 | 67 | 1. Если цель (`target`) уже есть в мемо (`memo`), то возвращается сохраненное значение, чтобы избежать повторных вычислений. 68 | 2. Если цель пустая строка (`''`), то возвращается массив, содержащий пустой массив (пустое сочетание слов). 69 | 3. Для каждого слова (`word`) в `wordBank`: 70 | - Если цель начинается с текущего слова (`word`), то: 71 | - Вычисляется оставшаяся часть цели после удаления префикса (`suffix = target.slice(word.length)`). 72 | - Рекурсивно вызывается функция `allConstruct` для оставшейся части цели (`suffix`). 73 | - Для каждого возможного сочетания оставшейся части (`suffixWays`), создается новое сочетание, добавляя текущее слово в начало. 74 | - Новые сочетания добавляются в результирующий массив (`result`). 75 | 4. Результаты сохраняются в мемо для текущей цели. 76 | 5. Возвращается результирующий массив (`result`), содержащий все возможные комбинации слов, которые могут быть объединены в целевое слово. 77 | 78 | ## Особенности реализации 79 | 80 | - Функция `allConstruct` использует рекурсию для разбиения задачи на более простые подзадачи. 81 | - Мемоизация (использование объекта `memo`) позволяет сохранять результаты ранее вычисленных целей и избегать повторных вычислений для этих целей. 82 | - Метод `startsWith` используется для проверки, начинается ли цель с заданного слова. Это позволяет определить, возможно ли объединить слово в целевое слово. 83 | - Массивы `suffixWays` и `targetWays` обновляются путем добавления текущего слова (`word`) или комбинации текущего слова и оставшейся части цели. 84 | 85 | ## Анализ концепций 86 | 87 | - **Рекурсия**: Функция `allConstruct` использует рекурсию для разбиения задачи на более простые подзадачи и последовательного решения каждой из них. 88 | - **Мемоизация**: Использование объекта `memo` позволяет сохранять результаты ранее вычисленных целей и избегать повторных вычислений для этих целей. Это значительно улучшает производительность функции. 89 | - **Динамическое программирование**: Принципы динамического программирования применяются в функции `allConstruct`. Оптимальное решение задачи строится из оптимальных решений ее подзадач. 90 | 91 | -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/canSum/README.md: -------------------------------------------------------------------------------- 1 | [canSum memoization time code](https://www.youtube.com/watch?v=oBt53YbR9Kk&t=4196s) 2 | 3 | [Назад к содержанию](../README.md) 4 | 5 | # canSum 6 | 7 | Функция `canSum` предоставляет решение задачи проверки возможности получения целевой суммы (`targetSum`) путем сложения чисел из заданного набора (`numbers`). 8 | 9 | ## Принцип работы 10 | 11 | Функция `canSum` использует рекурсивный подход с мемоизацией для уменьшения количества повторных вычислений и оптимизации работы. Она работает следующим образом: 12 | 13 | 1. Если целевая сумма (`targetSum`) уже есть в мемо (`memo`), то возвращается сохраненное значение, чтобы избежать повторных вычислений. 14 | 2. Если целевая сумма равна 0, значит, целевая сумма может быть получена путем сложения чисел из набора, поэтому возвращается значение `true`. 15 | 3. Если целевая сумма меньше 0, значит, целевая сумма не может быть получена путем сложения чисел из набора, поэтому возвращается значение `false`. 16 | 4. Для каждого числа (`num`) в наборе `numbers`: 17 | - Вычисляется остаток от разности между целевой суммой и текущим числом (`remainder = targetSum - num`). 18 | - Рекурсивно вызывается функция `canSum` для остатка (`remainder`) и того же набора чисел (`numbers`). 19 | - Если для остатка получено значение `true`, значит, целевая сумма может быть получена путем сложения чисел из набора. В этом случае, значение `true` сохраняется в мемо для текущей целевой суммы и возвращается `true`. 20 | 5. Если ни одно число из набора не приводит к достижению целевой суммы, то возвращается значение `false` и сохраняется в мемо для текущей целевой суммы. 21 | 22 | ## Особенности реализации 23 | 24 | - Функция `canSum` использует рекурсию для поиска возможности получения целевой суммы путем сложения чисел. 25 | - Мемоизация (использование объекта `memo`) позволяет сохранять результаты ранее вычисленных целевых сумм и избегать повторных вычислений для этих сумм. 26 | - Функция возвращает значение `true`, если целевая сумма может быть получена, и значение `false`, если не может. 27 | - Остаток (`remainder`) вычисляется путем вычитания текущего числа (`num`) из целевой суммы. 28 | - Полученные результаты сохраняются в мемо для каждой проверяемой целевой суммы. 29 | 30 | ## Анализ концепций 31 | 32 | - **Рекурсия**: Функция `canSum` использует рекурсию для разбиения задачи на более простые подзадачи и последовательного решения каждой из них. 33 | - **Мемоизация**: Использование объекта `memo` позволяет сохранять результаты ранее вычисленных целевых сумм и избегать повторных вычислений для этих сумм. Это значительно улучшает производительность функции. 34 | - **Динамическое программирование**: Принципы динамического программирования применяются в функции `canSum`. Оптимальное решение задачи строится из оптимальных решений ее подзадач. 35 | 36 | ## Разбор построчно с пояснениями 37 | 38 | ```javascript 39 | export function canSum(targetSum, numbers, memo = {}) { 40 | // Проверка, есть ли целевая сумма в мемо 41 | if (targetSum in memo) { 42 | return memo[targetSum]; 43 | } 44 | 45 | // Проверка, является ли целевая сумма равной 0 46 | if (targetSum === 0) { 47 | return true; 48 | } 49 | 50 | // Проверка, является ли целевая сумма отрицательным числом 51 | if (targetSum < 0) { 52 | return false; 53 | } 54 | 55 | // Проход по числам массива 56 | for (const num of numbers) { 57 | // Вычисление остатка от разности целевой суммы и числа 58 | const remainder = targetSum - num; 59 | // Рекурсивный вызов функции canSum для остатка 60 | if (canSum(remainder, numbers, memo) === true) { 61 | // Если для остатка вернулось true, сохраняем true в мемо и возвращаем true 62 | memo[targetSum] = true; 63 | return true; 64 | } 65 | } 66 | 67 | // Если ни одно число не приводит к достижению целевой суммы, 68 | // сохраняем false в мемо и возвращаем false 69 | memo[targetSum] = false; 70 | return false; 71 | } 72 | ``` 73 | 74 | ## Пояснения по коду 75 | 76 | - Функция `canSum` рекурсивно проверяет, может ли целевая сумма быть получена путем сложения чисел из массива `numbers`. 77 | - Входные параметры: 78 | - `targetSum`: Целевая сумма, которую нужно получить. 79 | - `numbers`: Массив чисел, которые могут быть использованы для получения целевой суммы. 80 | - `memo`: Объект, используемый для мемоизации результата (по умолчанию пустой объект `{}`). 81 | 82 | - Проверка наличия целевой суммы в объекте `memo`. Если результат уже сохранен, он возвращается, чтобы избежать повторных вычислений и улучшить производительность. 83 | - Проверка, является ли целевая сумма равной 0. Если равна, то целевую сумму можно получить путем сложения чисел из массива, поэтому возвращается `true`. 84 | - Проверка, является ли целевая сумма отрицательным числом. Если число отрицательное, то целевую сумму невозможно получить путем сложения чисел из массива, поэтому возвращается `false`. 85 | - Проход по каждому числу из массива `numbers`: 86 | - Вычисление остатка (`remainder`) от разности целевой суммы и текущего числа. 87 | - Рекурсивный вызов функции `canSum` для остатка и того же массива `numbers` и объекта `memo`. 88 | - Если для остатка получено значение `true`, то целевая сумма может быть получена, поэтому: 89 | - Значение `true` сохраняется в `memo` для текущей целевой суммы. 90 | - Возвращается `true`. 91 | - Если ни одно число из массива не приводит к достижению целевой суммы, значит, целевую сумму невозможно получить, поэтому: 92 | - Значение `false` сохраняется в `memo` для текущей целевой суммы. 93 | - Возвращается `false`. 94 | 95 | ## Примеры использования 96 | 97 | ```javascript 98 | console.log(canSum(7, [2, 3])); // true 99 | console.log(canSum(7, [5, 3, 4, 7])); // true 100 | console.log(canSum(7, [2, 4])); // false 101 | console.log(canSum(8, [2, 3, 5])); // true 102 | console.log(canSum(300, [7, 14])); // false 103 | ``` -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/allConstructWithTable/README.md: -------------------------------------------------------------------------------- 1 | [allConstruct tabulation time code](https://www.youtube.com/watch?v=oBt53YbR9Kk&t=17423s) 2 | 3 | [Назад к содержанию](../README.md) 4 | 5 | # allConstructWithTable 6 | ```javascript 7 | /** 8 | * Функция allConstructWithTable вычисляет все способы, которыми можно сконструировать 9 | * целевую строку `target` из кусочков `parts` используя динамическое программирование с таблицей. 10 | * 11 | * @param {string} target - Целевая строка для построения. 12 | * @param {string[]} parts - Массив строк, которые можно использовать для построения target. 13 | * @returns {string[][]} - Двумерный массив, содержащий все уникальные способы построения target. 14 | * 15 | * Алгоритмическая сложность: O(m^2 * n) - где m - длина target строки, n - количество элементов в parts. 16 | * Занимаемое пространство: O(m^2) - максимальный размер таблицы. 17 | */ 18 | 19 | export function allConstructWithTable(target, parts) { 20 | // Создаем таблицу для динамического программирования с длиной target.length + 1 O(m) 21 | const table = Array(target.length + 1) 22 | // Заполняем таблицу пустыми массивами O(m) 23 | .fill() 24 | .map(() => []); 25 | 26 | // Базовый случай: для пустой строки только один способ её построить - не использовать ни одной части O(1) 27 | table[0] = [[]]; 28 | 29 | // Перебираем строки стартовой длины от 0 до target.length O(m) 30 | for (let i = 0; i <= target.length; i++) { 31 | // Перебираем все части O(n) 32 | for (const part of parts) { 33 | // Проверяем, совпадает ли часть с подстрокой target начиная с индекса i O(m) 34 | if (target.slice(i, i + part.length) === part) { 35 | // Создаем новые комбинации путем добавления текущей части к каждому существующему пути в table[i] O(m^2) 36 | // так как потенциально может быть до m путей, и каждой добавление части занимает до m операций 37 | const newCombinations = table[i].map(subarray => [...subarray, part]); 38 | // Добавляем новые комбинации в таблицу, увеличивая индекс на длину текущей части O(m^2) 39 | // в худшем случае каждый путь в table[i] может вести к добавлению нового пути длиной до m 40 | table[i + part.length].push(...newCombinations); 41 | } 42 | } 43 | } 44 | 45 | // Возвращаем результат из последней ячейки таблицы, которая содержит искомые комбинации O(1) 46 | return table[target.length]; 47 | } 48 | ``` 49 | ## Описание 50 | 51 | Функция `allConstructWithTable` предоставляет решение задачи поиска всех возможных комбинаций с использованием заданных частей (`parts`) для формирования цели (`target`). Функция использует подход с использованием таблицы для хранения всех комбинаций. 52 | 53 | ## Принцип работы 54 | 55 | 1. Создание и инициализация таблицы (`table`) размером `(target.length + 1)`. 56 | - Длина таблицы равна длине цели `target`, увеличенной на 1, чтобы учесть случай, когда все части `parts` использованы полностью и достигнута конец цели. 57 | 58 | 2. Инициализация первого элемента таблицы (`table[0]`) пустым массивом `[]`. 59 | - Это означает, что нулевая длина цели может быть достигнута без использования каких-либо частей, поэтому комбинациями являются пустые массивы. 60 | 61 | 3. Перебор индексов таблицы от 0 до `target.length` включительно. 62 | - Это соответствует длине цели и позволяет рассматривать все префиксы цели от пустого префикса до полной цели. 63 | 64 | 4. Для каждой части `part` из массива `parts`: 65 | - Проверка, совпадает ли часть `part` с подстрокой цели `target` начиная с текущего индекса `i`. 66 | - Если совпадение обнаружено: 67 | - Создание новых комбинаций путем объединения каждой комбинации из таблицы для предыдущего индекса `i` с текущей частью `part`. 68 | - Добавление новых комбинаций в таблицу для индекса `i + part.length`. 69 | 70 | 5. Возврат всех комбинаций, находящихся в последнем элементе таблицы (`table[target.length]`). 71 | 72 | ## Особенности реализации 73 | 74 | - Функция `allConstructWithTable` использует подход с использованием таблицы для хранения всех комбинаций. 75 | - Каждый элемент таблицы (`table[i]`) является массивом комбинаций, где индекс `i` представляет текущую длину префикса цели. 76 | - Изначально, первый элемент таблицы (`table[0]`) инициализируется пустым массивом, так как пустой префикс цели может быть достигнут без использования каких-либо частей. 77 | - Для каждого индекса `i` таблицы и каждой части `part` из массива `parts`, происходит проверка на совпадение части `part` с подстрокой цели начиная с текущего индекса `i`. 78 | - Если совпадение найдено, создаются новые комбинации путем объединения текущей комбинации из таблицы для индекса `i` с текущей частью `part`, и эти новые комбинации добавляются в таблицу для индекса `i + part.length`. 79 | - На выходе возвращаются все комбинации, находящиеся в последнем элементе таблицы (`table[target.length]`). 80 | 81 | ## Примеры использования 82 | 83 | ```javascript 84 | console.log(allConstructWithTable('abcdef', ['ab', 'abc', 'cd', 'def', 'abcd'])); 85 | // [ 86 | // ['ab', 'cd', 'ef'], 87 | // ['abc', 'def'], 88 | // ['abcd', 'ef'] 89 | // ] 90 | 91 | console.log(allConstructWithTable('purple', ['purp', 'p', 'ur', 'le', 'purpl'])); 92 | // [ 93 | // ['purp', 'le'], 94 | // ['p', 'ur', 'p', 'le'] 95 | // ] 96 | 97 | console.log(allConstructWithTable('skateboard', ['bo', 'rd', 'ate', 't', 'ska', 'sk', 'boar'])); 98 | // [] 99 | 100 | console.log(allConstructWithTable('eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef', ['e', 'ee', 'eee', 'eeee', 'eeeee'])); 101 | // [] 102 | 103 | ``` 104 | 105 | ## Временная сложность 106 | 107 | Временная сложность функции `allConstructWithTable` зависит от размера входных данных, а именно от длины цели `target` и количества частей `parts`. В худшем случае, когда каждая часть `part` может быть использована для формирования цели `target`, функция может иметь экспоненциальную временную сложность O(n^m), где n - количество частей `parts`, m - длина цели `target`. Однако, использование таблицы позволяет избежать повторных вычислений комбинаций, что значительно улучшает производительность в большинстве случаев. 108 | -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/fibanacchi/README.md: -------------------------------------------------------------------------------- 1 | [fib memoization time code](https://www.youtube.com/watch?v=oBt53YbR9Kk&t=210s) 2 | 3 | [Назад к содержанию](../README.md) 4 | 5 | # fibanacci 6 | 7 | ## Обзор 8 | 9 | Функция `fibanaсchi` предназначена для вычисления n-го числа Фибоначчи. Последовательность Фибоначчи — это ряд чисел, в котором каждое следующее число равно сумме двух предыдущих. Первые два числа в последовательности Фибоначчи — это, обычно, 1 и 1 (или 0 и 1, в зависимости от выбранного начала последовательности). 10 | 11 | ## Сигнатура функции 12 | 13 | ```javascript 14 | export function fibanacci(number) 15 | ``` 16 | 17 | ### Параметры 18 | 19 | - `number`: Порядковый номер (n) числа Фибоначчи в последовательности, которое функция должна вычислить. 20 | 21 | ## Принцип работы функции 22 | 23 | Функция `fibanaсchi` использует рекурсивный подход для вычисления чисел Фибоначчи. Работа функции описывается следующими шагами: 24 | 25 | 1. Проверка базового случая: если `number` меньше или равно 2, то возвращается 1, так как первые два элемента последовательности Фибоначчи равны 1. 26 | ```javascript 27 | if (number <= 2) { 28 | return 1; 29 | } 30 | ``` 31 | 32 | 2. Рекурсивный вызов: функция вызывает саму себя дважды с уменьшенными параметрами для вычисления предшествующих чисел последовательности и возвращает их сумму, получая тем самым число Фибоначчи для заданного `number`. 33 | ```javascript 34 | return fibanaсchi(number - 1) + fibanaсchi(number - 2) 35 | ``` 36 | 37 | ### Особенности реализации 38 | 39 | - **Рекурсия**: Ключевой момент реализации — это рекурсивные вызовы функции для вычисления предшествующих 40 | чисел Фибоначчи. Рекурсия позволяет просто и элегантно выразить идею последовательности Фибоначчи, но при этом может быть неэффективной из-за повторных вычислений одних и тех же значений. 41 | 42 | ### Концепции, используемые в коде 43 | 44 | - **Рекурсия**: Эта функция является классическим примером рекурсии, когда для получения результата функция вызывает сама себя. 45 | 46 | - **Базовый случай**: Так как рекурсивные функции должны иметь условия остановки, `number <= 2` служит в качестве такого условия для функции `fibanaсchi`. 47 | 48 | - **Алгоритмическая сложность**: Наивный рекурсивный подход имеет экспоненциальную сложность времени выполнения (O(2^n)), так как каждый вызов функции порождает два новых вызова, и это число увеличивается экспоненциально с увеличением значения `number`. 49 | 50 | ### Проблемы реализации и возможные улучшения 51 | 52 | Данный рекурсивный подход без использования техник оптимизации, таких как мемоизация или динамическое программирование, не является эффективным для больших значений `number`, поскольку многие вычисления повторяются. Например, значение `fibanaсchi(5)` будет вычислено несколько раз в процессе вычисления `fibanaсchi(7)`. 53 | 54 | **Мемоизация**: Это процесс сохранения результатов выполненных ранее вычислений для предотвращения повторных рекурсивных вызовов. Мемоизация уменьшит сложность времени выполнения до O(n), так как каждое 55 | число Фибоначчи будет вычислено всего один раз. Вместе с тем, последующие вызовы функции для этих чисел будут просто возвращать уже вычисленные значения из кеша памяти. 56 | 57 | Пример улучшения функции `fibanaсchi` с использованием мемоизации: 58 | 59 | ```javascript 60 | function fibanacci(number, memo = {}) { 61 | if (memo[number]) { 62 | return memo[number]; 63 | } 64 | if (number <= 2) { 65 | return 1; 66 | } 67 | memo[number] = fibanacci(number - 1, memo) + fibanacci(number - 2, memo); 68 | return memo[number]; 69 | } 70 | ``` 71 | 72 | Здесь `memo` — это объект (хэш-таблица), который хранит уже вычисленные значения чисел Фибоначчи. Когда функция `fibanacci` вызывается с новым значением, она сначала проверяет, существует ли это значение в кеше. Если да, функция возвращает значение из кеша, избегая тем самым повторных вычислений. 73 | 74 | ### Заключение 75 | 76 | Реализация функции `fibanaсchi` демонстрирует, как могут быть использованы фундаментальные концепции рекурсии и базовых случаев в алгоритмах, однако она также показывает типичные проблемы производительности, связанные с рекурсией и повторными расчетами. Оптимизация с помощью мемоизации является эффективным решением для повышения производительности, и это широко применимая практика при работе с рекурсивными алгоритмами. В контексте данной функции мемоизация позволяет избежать значительной избыточности вычислений, что критически важно при 77 | работе с большими числами. Это сокращает количество операций с экспоненциального роста до линейного, что делает решение практически применимым даже для высоких порядковых номеров чисел в последовательности Фибоначчи. 78 | 79 | ## Использование функции 80 | 81 | Чтобы использовать функцию `fibanacci` c мемоизацией, достаточно вызвать её с желаемым порядковым номером числа в последовательности Фибоначчи: 82 | 83 | ```javascript 84 | const nthFibonacciNumber = fibanacci(10); // Вызов функции для вычисления 10-го числа Фибоначчи 85 | console.log(nthFibonacciNumber); // Выведет 55, так как 10-е число Фибоначчи — это 55 86 | ``` 87 | 88 | ## Рекомендации и предостережения 89 | 90 | 1. Все числа в JavaScript представлены в формате с плавающей запятой двойной точности (64-битный формат IEEE 754), что означает, что очень большие значения последовательности Фибоначчи могут быть представлены неточно из-за ограничений точности. 91 | 92 | 2. При вычислении очень больших чисел Фибоначчи следует учитывать ограничения памяти, так как размер кеша `memo` будет расти линейно с увеличением `number`. 93 | 94 | 3. Рекурсивные вызовы могут привести к переполнению стека вызовов, особенно без оптимизации хвостовой рекурсии, которая не всегда доступна в JavaScript. Мемоизация решает эту проблему, поскольку уменьшает глубину рекурсивных вызовов за счет кеширования результатов. 95 | 96 | ## Заключительное слово 97 | В заключение, оптимизированная реализация функции fibanaсchi с использованием мемоизации позволяет эффективно и практично получать значения из последовательности Фибоначчи для больших чисел, существенно уменьшая количество необходимых вычислений и ускоряя работу алгоритма. Однако всегда важно помнить о возможных ограничениях и нюансах при использовании рекурсии и мемоизации, особенно при работе с числами большой размерности и долговременном хранении данных. Умелое применение подходов для оптимизации, таких как мемоизация, может быть ключевым для создания эффективных и масштабируемых программных решений. -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/bestSum/README.md: -------------------------------------------------------------------------------- 1 | [bestSum memoization time code](https://www.youtube.com/watch?v=oBt53YbR9Kk&t=6726s) 2 | 3 | [Назад к содержанию](../README.md) 4 | 5 | # bestSum 6 | 7 | Функция `bestSum` предоставляет решение задачи по поиску кратчайшей комбинации чисел из заданного набора (`numbers`), 8 | которая суммируется до целевой суммы (`targetSum`). 9 | 10 | ```javascript 11 | /** 12 | * Функция bestSum находит кратчайшую комбинацию чисел из массива numbers, 13 | * которая в сумме дает число targetSum. Для ускорения используется мемоизация. 14 | * 15 | * @param {number} targetSum - Цель суммы, которую необходимо достичь. 16 | * @param {number[]} numbers - Массив чисел, которые можно использовать для получения targetSum. 17 | * @param {Object} memo - Объект для хранения уже вычисленных результатов. 18 | * @returns {number[] | null} - Массив с кратчайшей комбинацией чисел или null, если такой комбинации нет. 19 | * 20 | * Алгоритмическая сложность: O(n*m^2), где n - количество чисел в массиве numbers, m - величина targetSum. 21 | * Пространственная сложность: O(m^2) из-за хранения комбинаций чисел. 22 | */ 23 | export function bestSum(targetSum, numbers, memo = {}) { 24 | // Проверяем, является ли targetSum равным нулю (базовый случай). 25 | // Локальная сложность: O(1) 26 | // Общая временная сложность: O(1) 27 | // Общая пространственная сложность: O(1) 28 | if (targetSum === 0) { 29 | return []; 30 | } 31 | // Проверяем, является ли targetSum отрицательным числом. 32 | // Локальная сложность: O(1) 33 | // Общая временная сложность: O(1) 34 | // Общая пространственная сложность: O(1) 35 | if (targetSum < 0) { 36 | return null; 37 | } 38 | // Проверяем, содержится ли targetSum в кэше (мемоизация). 39 | // Локальная сложность: O(1) 40 | // Общая временная сложность: O(n*m^2) 41 | // Общая пространственная сложность: O(m^2) 42 | if (targetSum in memo) { 43 | return memo[targetSum]; 44 | } 45 | 46 | // Инициализируем переменную для хранения кратчайшей комбинации. 47 | // Локальная сложность: O(1) 48 | // Общая временная сложность: O(1) 49 | // Общая пространственная сложность: O(m^2) 50 | let shortestCombination = null; 51 | 52 | // Перебираем числа из входного массива для построения комбинаций. 53 | // Локальная сложность: O(n*m^2) [включает рекурсивные вызовы и операции массива] 54 | // Общая временная сложность: O(n*m^2) 55 | // Общая пространственная сложность: O(m^2) 56 | for (const num of numbers) { 57 | const remainder = targetSum - num; 58 | const remainderCombination = bestSum(remainder, numbers, memo); 59 | 60 | // Проверяем, возможно ли создать комбинацию с оставшейся суммой. 61 | if (remainderCombination !== null) { 62 | // Формируем новую комбинацию, добавляя текущее число. 63 | const combination = [...remainderCombination, num]; 64 | 65 | // Проверяем, является ли новая комбинация самой короткой. 66 | if (shortestCombination === null || combination.length < shortestCombination.length) { 67 | shortestCombination = combination; 68 | } 69 | } 70 | } 71 | 72 | // Сохраняем найденную кратчайшую комбинацию в кэш для мемоизации. 73 | // Локальная сложность: O(m) - потенциально копирование массива длины m. 74 | // Общая временная сложность: По-прежнему O(n*m^2) - не влияет на максимальную сложность. 75 | // Общая пространственная сложность: O(m^2) 76 | memo[targetSum] = shortestCombination; 77 | 78 | // Возвращаем найденную кратчайшую комбинацию чисел или null, если таковой нет. 79 | // Локальная сложность: O(1) - возврат ссылки на объект в памяти. 80 | // Общая временная сложность: Остается неизменной O(n*m^2) - возврат значения не влияет на сложность. 81 | // Общая пространственная сложность: Остается неизменной O(m^2) - возврат значения не добавляет новых данных в памяти. 82 | return shortestCombination; 83 | } 84 | ``` 85 | 86 | ## Принцип работы 87 | 88 | Функция `bestSum` использует рекурсивный подход с мемоизацией для уменьшения количества повторных вычислений и 89 | оптимизации работы. Она работает следующим образом: 90 | 91 | 1. Если целевая сумма (`targetSum`) равна 0, значит, нужно вернуть пустой массив, так как нет чисел, которые нужно 92 | объединить, чтобы получить сумму 0. 93 | 2. Если целевая сумма меньше 0, значит, комбинация чисел в наборе невозможна, поэтому возвращается `null`. 94 | 3. Если целевая сумма уже есть в мемо (`memo`), то возвращается сохраненное значение, чтобы избежать повторных 95 | вычислений. 96 | 4. Инициализируется переменная `shortestCombination` как `null` - это комбинация чисел, которая будет содержать 97 | минимальное количество чисел для достижения целевой суммы. 98 | 5. Для каждого числа (`num`) в наборе `numbers`: 99 | - Вычисляется остаток от разницы между целевой суммой и текущим числом (`remainder = targetSum - num`). 100 | - Рекурсивно вызывается функция `bestSum` для остатка (`remainder`) и того же набора чисел (`numbers`). 101 | - Если комбинация остатка не равна `null`, то создается новая комбинация путем объединения комбинации остатка и 102 | текущего числа (`combination = [...remainderCombination, num]`). 103 | - Если `shortestCombination` равна `null` или длина новой комбинации меньше длины `shortestCombination`, 104 | то `shortestCombination` обновляется новой комбинацией. 105 | 6. Результаты сохраняются в мемо для текущей целевой суммы. 106 | 7. Возвращается `shortestCombination` - кратчайшая комбинация чисел, которая суммируется до целевой суммы. 107 | 108 | ## Особенности реализации 109 | 110 | - Функция `bestSum` использует рекурсию для разбиения задачи на более простые подзадачи. 111 | - Мемоизация (использование объекта `memo`) позволяет сохранять результаты ранее вычисленных целевых сумм и избегать 112 | повторных вычислений для этих сумм. 113 | - Переменная `shortestCombination` обновляется только в том случае, если новая комбинация короче 114 | текущей `shortestCombination`. 115 | - Массив `combination` обновляется путем объединения комбинации остатка и текущего числа. 116 | 117 | ## Анализ концепций 118 | 119 | - **Рекурсия**: Функция `bestSum` использует рекурсию для разбиения задачи на более простые подзадачи и 120 | последовательного решения каждой из них. 121 | - **Мемоизация**: Использование объекта `memo` позволяет сохранять результаты ранее вычисленных целевых сумм и избегать 122 | повторных вычислений для этих сумм. Это значительно улучшает производительность функции. 123 | - **Динамическое программирование**: Принципы динамического программирования применяются в функции `bestSum`. 124 | Оптимальное решение задачи строится из оптимальных решений ее подзадач. -------------------------------------------------------------------------------- /algorithms/algorithm-tasks/fibanacciWithTable/README.md: -------------------------------------------------------------------------------- 1 | [fib tabulation time code](https://www.youtube.com/watch?v=oBt53YbR9Kk&t=11453s) 2 | 3 | [Назад к содержанию](../README.md) 4 | 5 | # Документация функции `fibanacchiWithTable` 6 | 7 | ## Описание 8 | 9 | Функция `fibanacchiWithTable` реализует эффективный алгоритм для вычисления числа Фибоначчи с использованием табличного метода (bottom-up approach), который избегает экспоненциального времени выполнения, свойственного рекурсивной реализации. 10 | 11 | Последовательность Фибоначчи — это ряд чисел, в котором каждое следующее число является суммой двух предыдущих. Первые два числа последовательности обычно равны 1 (или 0 и 1), а каждое последующее число равно сумме двух предшествующих чисел. 12 | 13 | ## Принцип работы 14 | 15 | Функция принимает один аргумент: 16 | 17 | - `num`: индекс (позиция) числа в последовательности Фибоначчи, которое нужно вычислить. 18 | 19 | Алгоритм работы функции `fibanacchiWithTable` состоит из следующих шагов: 20 | 21 | 1. Инициализация массива `table` фиксированной длины, равной `num + 1`, заполненного нулями. Этот массив будет использоваться как таблица для хранения промежуточных результатов вычисления чисел Фибоначчи. 22 | 23 | ```javascript 24 | const table = Array(num + 1).fill(0); 25 | ``` 26 | 27 | 2. Установка значения второго элемента массива `table` равным 1, так как первое число Фибоначчи (для индекса 1) равно 1. 28 | 29 | ```javascript 30 | table[1] = 1; 31 | ``` 32 | 33 | 3. Использование цикла `for` для итерации от нулевого элемента до `num`. На каждом шаге цикла: 34 | 35 | - К текущему значению в таблице для позиции `i + 1` прибавляется значение текущей позиции `i`. Это позволяет вычислить следующее число Фибоначчи и сохранить его в таблице. 36 | 37 | ```javascript 38 | table[i + 1] += table[i]; 39 | ``` 40 | 41 | - К текущему значению в таблице для позиции `i + 2` прибавляется значение текущей позиции `i`. Это действие вычисляет число Фибоначчи на две позиции вперед и сохраняет его в таблице. Эта операция необходима, чтобы при следующем проходе цикла у нас уже было сохранено значение для `i + 1`. 42 | 43 | ```javascript 44 | table[i + 2] += table[i]; 45 | ``` 46 | 47 | 4. После завершения цикла возвращается элемент массива с индексом `num`, который содержит искомое число Фибоначчи. 48 | 49 | ```javascript 50 | return table[num]; 51 | ``` 52 | 53 | ## Особенности реализации 54 | 55 | - Метод итеративного вычисления позволяет избежать проблем, связанных с рекурсией, таких как переполнение стека вызовов и повторные вычисления уже известных значений. 56 | 57 | - Функция использует принцип динамического программирования с табуляцией, т.е. сохраняет результаты промежуточных вычислений, чтобы они могли быть использованы повторно. 58 | 59 | - Временная сложность алгоритма составляет O(n), что делает его значительно более эффективным, чем наивный рекурсивный подход с временной сложностью O(2^n). 60 | 61 | - Пространственная сложность тоже O(n) из-за необходимости сохранения таблицы значений. 62 | 63 | - Использование таблицы значений позволяет функции поддерживать вычисление больших значений чисел Фибоначчи, которые могли бы быть недоступны из-за ограничений рекурсии или неэффективности при больших значениях `num`. 64 | 65 | ## Пример использования 66 | 67 | Допустим, вам нужно вычислить 10-е число Фибоначчи. Используйте функцию следующим образом: 68 | 69 | ```javascript 70 | import { fibanacchiWithTable } from 'путь-к-файлу'; // Замените 'путь-к-файлу' реальным путём 71 | 72 | const result = fibanacchiWithTable(10); 73 | console.log(result); // Выведет 55, так как 10-е число Фибоначчи — 55 74 | ``` 75 | 76 | ## Примечания 77 | 78 | Важно отметить, что хотя алгоритм и позволяет вычислять большие числа быстрее и без риска переполнения стека вызовов, которые присущи рекурсивной реализации, объем используемой памяти остаётся значительным. Для случаев, когда память ограничена, может потребоваться другой подход, который не сохраняет все промежуточные значения последовательности Фибоначчи. 79 | 80 | Ещё одним значимым фактором является тип числовых значений, с которыми может работать JavaScript. В современных JavaScript-движках числа представлены с использованием 64-битных значений с плавающей запятой, что может влиять на точность при работе с очень большими числами. 81 | 82 | ## Заключение 83 | 84 | Функция `fibanacchiWithTable` представляет собой пример эффективного алгоритма для работы с классической задачей вычисления чисел Фибоначчи. Через применение динамического программирования и табуляции, данная функция демонстрирует, как можно избежать недостатков наивной рекурсии и значительно ускорить вычисления за счёт использования дополнительной памяти для хранения промежуточных результатов. 85 | 86 | Алгоритм хорошо подходит для случаев, когда нужно многократно вычислять числа Фибоначчи для различных значений, поскольку он позволяет избегать повторного вычисления уже известных чисел, сохраняя промежуточные результаты в таблице. 87 | 88 | Реализуя этот метод, разработчики могут обеспечить повышенную производительность и оптимизацию памяти для приложений, требующих расчётов в областях таких как математика, финансы, машинное обучение и других, где последовательности чисел Фибоначчи находят своё применение. 89 | 90 | Вместе с тем, необходимо помнить о том, что при работе с очень длинными последовательностями возможны ограничения памяти, и в таких случаях порой целесообразнее использовать более продвинутые техники, такие как алгоритмы, которые используют матричное представление чисел Фибоначчи или формулу Бине для нахождения приблизительных значений чисел Фибоначчи. 91 | 92 | В любом случае, выбор метода остается за разработчиком и зависит от конкретных задач и условий, в которых функция будет использоваться. Функция `fibanacchiWithTable` является мощным инструментом в наборе инструментов программиста, особенно когда речь идёт о быстром и эффективном решении проблем, связанных с числами Фибоначчи, благодаря своей способности оптимизировать использование ресурсов и обеспечивать результаты с высокой производительностью. 93 | 94 | Тем не менее, следует также учитывать возможность целочисленного переполнения. В JavaScript (на момент написания, до марта 2023 года) максимально безопасное целое число, с которым можно работать, это `Number.MAX_SAFE_INTEGER`, равное `9007199254740991`. При расчётах больших чисел Фибоначчи этот предел может быть быстро достигнут, что приведёт к неточностям в вычислениях. В таких случаях потребуется использование специальных библиотек для работы с большими числами (big integer libraries), способных обрабатывать числа произвольной длины. 95 | 96 | И, наконец, необходимо отметить, что, несмотря на универсальность и производительность алгоритма с табуляцией, он может быть не всегда подходящим для использования в условиях ограниченного объёма памяти. В таких случаях может быть более целесообразным использование метода, который вычисляет числа Фибоначчи "на лету", без сохранения всех предыдущих значений последовательности, например, храня только последние два вычисленных числа. 97 | 98 | В результате, функция `fibanacchiWithTable` является примером динамического программирования с подходом "с низу вверх", который может быть использован не только для вычисления чисел Фибоначчи, но и для решения широкого спектра других задач, где принцип разбиения большой задачи на подзадачи и сохранение результатов промежуточных вычислений для их использования в будущем может быть важен. 99 | Используя этот подход, разработчики могут рационализировать ресурсы и время выполнения программ, что делает эту технику особенно ценной для задач оптимизации, компьютерной графики, биоинформатики, а также многих других областей, где встают вопросы об обработке больших массивов данных или выполнении сложных вычислений. 100 | Заключительным моментом при работе с алгоритмом fibanacchiWithTable является понимание, что данная функция хорошо поддается тестированию, в том числе автоматизированному. Модульность функции — её способность работать изолированно от остальной части программы — облегчает создание тестовых сценариев, которые могут проверять её работу на различных наборах входных данных, обеспечивая надёжность и корректность работы в рамках более крупного приложения или системы. -------------------------------------------------------------------------------- /algorithms/sort-playground/utils.mjs: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import {createInterface} from 'readline'; 3 | import * as path from 'path'; 4 | 5 | export function less(first, second) { 6 | return first <= second; 7 | } 8 | 9 | export const SORT_TYPE = { 10 | select: Symbol('selectSort'), 11 | insert: Symbol('insert'), 12 | shell: Symbol('shell'), 13 | descendingMergeSort: Symbol('descendingMergeSort'), 14 | ascendingMergeSort: Symbol('ascendingMergeSort'), 15 | quickSort: Symbol('quickSort'), 16 | threeWayQuickSort: Symbol('threeWayQuickSort'), 17 | heapSort: Symbol('heapSort'), 18 | } 19 | 20 | export const ALGORITHMS_DIRECTORIES = { 21 | [SORT_TYPE.shell]: './shell-sort', 22 | [SORT_TYPE.insert]: './insert-sort', 23 | [SORT_TYPE.select]: './select-sort', 24 | [SORT_TYPE.descendingMergeSort]: './descending-merge-sort', 25 | [SORT_TYPE.ascendingMergeSort]: './ascending-merge-sort', 26 | [SORT_TYPE.quickSort]: './quick-sort', 27 | [SORT_TYPE.threeWayQuickSort]: './quick-three-way', 28 | [SORT_TYPE.heapSort]: './heap-sort', 29 | } 30 | 31 | export const STRING_TO_SYMBOL = { 32 | ['insert']: SORT_TYPE.insert, 33 | ['select']: SORT_TYPE.select, 34 | ['shell']: SORT_TYPE.shell, 35 | ['descendingMergeSort']: SORT_TYPE.descendingMergeSort, 36 | ['ascendingMergeSort']: SORT_TYPE.ascendingMergeSort, 37 | ['quickSort']: SORT_TYPE.quickSort, 38 | ['threeWayQuickSort']: SORT_TYPE.threeWayQuickSort, 39 | ['heapSort']: SORT_TYPE.heapSort, 40 | } 41 | 42 | export function swap(comparable, firstPointer, secondPointer) { 43 | const temp = comparable[firstPointer]; 44 | comparable[firstPointer] = comparable[secondPointer]; 45 | comparable[secondPointer] = temp; 46 | return comparable; 47 | } 48 | 49 | export function show(comparable) { 50 | console.log(comparable.join(',')); 51 | return comparable; 52 | } 53 | 54 | export function generateData(size = 10) { 55 | return new Array(size) 56 | .fill(0) 57 | .map(() => { 58 | const number = Math.round(Math.random() * 1000) / Math.floor(Math.random() * 10); 59 | return number === Infinity || isNaN(number) ? Math.random() * 10 : number; 60 | }); 61 | } 62 | 63 | 64 | export function carry(func, algorithm, makeFrames = false) { 65 | const frameMaker = makeFrames ? prepareJson(algorithm) : null; 66 | return (newArguments) => func(newArguments, frameMaker); 67 | } 68 | 69 | export function sort(sortAlgorithm, data) { 70 | return sortAlgorithm(data); 71 | } 72 | 73 | export function isSorted(comparable) { 74 | for (let i = 0, len = comparable.length - 1; i < len; i++) { 75 | if (comparable[i] > comparable[i + 1]) { 76 | return false; 77 | } 78 | } 79 | return true; 80 | } 81 | 82 | export function compose(...functions) { 83 | return (args) => functions.reduce((result, func) => func(result), args); 84 | } 85 | 86 | export function timeTaken(fn) { 87 | return function (...args) { 88 | const before = Date.now(); 89 | const result = fn.apply(this, args); 90 | const after = Date.now(); 91 | const time = new Date(after - before); 92 | return {result, time: `${time.getSeconds()}s.${time.getMilliseconds()}ms`}; 93 | }; 94 | } 95 | 96 | export const EXAMPLE = ['S', 'O', 'R', 'T', 'E', 'X', 'A', 'P', 'L', 'E']; 97 | 98 | export function logResult(sortType, items, time, isSorted) { 99 | return ` 100 | =============================== 101 | [algorithm type: ${sortType}] 102 | [elements: ${items}] 103 | [time: ${time}] 104 | [isSorted: ${isSorted}] 105 | ================================\n`; 106 | } 107 | 108 | const LOG_FILE_NAME = './log/sorting_log.txt'; 109 | 110 | export function logAlgorithmResult(sortType) { 111 | return function ({result, time}) { 112 | const resultLog = logResult(sortType.toString(), result.data.length, time, isSorted(result)); 113 | console.log(resultLog); 114 | createFileWithNameAndWriteString(LOG_FILE_NAME, resultLog); 115 | }; 116 | } 117 | 118 | export function prepareJson(algorithm) { 119 | const frames = []; 120 | return function (data) { 121 | if (data) { 122 | frames.push(printGraph(data)); 123 | } else { 124 | return { 125 | path: ALGORITHMS_DIRECTORIES[algorithm], 126 | frames 127 | }; 128 | } 129 | } 130 | } 131 | 132 | export function saveFrames({result, time}) { 133 | if (result.frameMaker) { 134 | const {path, frames} = result.frameMaker(); 135 | createFileWithNameAndWriteString(`${path}/${result.data.length}_frames.json`, JSON.stringify({frames})) 136 | } 137 | return {result, time}; 138 | } 139 | 140 | export function compare(first, second) { 141 | if (first > second) { 142 | return 1; 143 | } else if (first < second) { 144 | return -1; 145 | } else { 146 | return 0; 147 | } 148 | } 149 | 150 | export function printGraph(data) { 151 | const MAX_HEIGHT = 10; 152 | const MAX_WIDTH = 100; 153 | const GRAPH_SPACE = ' '; 154 | const GRAPH_BAR = '|'; 155 | const AXIS_SYMBOL = '_'; 156 | 157 | const maxValueInData = Math.max(...data); 158 | 159 | let scaledData = data.length > MAX_WIDTH ? data.reduce((acc, currentValue, index) => { 160 | if (index % Math.floor(data.length / MAX_WIDTH) === 0) { 161 | let sum = 0; 162 | for (let i = index; i < index + Math.floor(data.length / MAX_WIDTH); i++) { 163 | sum = sum + data[i]; 164 | } 165 | acc.push(sum / Math.floor(data.length / MAX_WIDTH)); 166 | } 167 | return acc; 168 | }, []) : [...data]; 169 | 170 | let scaledAndRoundedData = scaledData.map(value => Math.round(value / maxValueInData * MAX_HEIGHT)); 171 | 172 | let emptyGraph = Array(MAX_HEIGHT).fill('').map(() => Array(scaledAndRoundedData.length).fill(GRAPH_SPACE)); 173 | let graphWithBars = scaledAndRoundedData.reduce((graph, value, index) => { 174 | for(let height = 0; height < value; height++){ 175 | graph[MAX_HEIGHT - 1 - height][index] = GRAPH_BAR; 176 | } 177 | return graph; 178 | }, emptyGraph); 179 | 180 | graphWithBars.push(Array(scaledAndRoundedData.length).fill(AXIS_SYMBOL)); 181 | return graphWithBars.map(line => line.join('')).join('\n'); 182 | } 183 | 184 | export function createFileWithNameAndWriteString(fileName, content) { 185 | fs.appendFile(fileName, content, function (error) { 186 | if (error) throw error; 187 | console.log('Content added to file successfully.'); 188 | }); 189 | } 190 | 191 | function readAndParseJSON(filePath) { 192 | try { 193 | const data = fs.readFileSync(filePath, 'utf-8'); 194 | return JSON.parse(data); 195 | } catch (error) { 196 | console.error(`Ошибка при чтении или парсинге файла: ${error}`); 197 | } 198 | } 199 | 200 | const EXIT_SIGN = 3; 201 | 202 | const VISUALIZATION_TYPE = new Map([ 203 | [1, 'Пошаговое'], 204 | [2, 'Непрерывное'], 205 | [EXIT_SIGN, 'Выйти'], 206 | ]) 207 | 208 | export async function visualization(path){ 209 | const dataToVisualization = readAndParseJSON(path); 210 | if (!dataToVisualization?.frames) { 211 | console.error('Incorrect JSON format!'); 212 | } 213 | const readlineInterface = createInterface({ 214 | input: process.stdin, 215 | output: process.stdout 216 | }); 217 | 218 | await permanentVisualization(readlineInterface, dataToVisualization); 219 | 220 | } 221 | 222 | function permanentVisualization(readlineInterface, dataToVisualization) { 223 | return new Promise((resolve) => { 224 | const {frames} = dataToVisualization; 225 | let currentIndex = 0; 226 | 227 | const interval = setInterval(() => { 228 | console.clear(); 229 | console.log(frames[currentIndex++]) 230 | 231 | if (!frames[currentIndex++]) { 232 | clearInterval(interval); 233 | resolve(); 234 | } 235 | 236 | }) 237 | }) 238 | } 239 | 240 | 241 | 242 | export async function chooseVisualizationType(readlineInterface, dataToVisualizationPath) { 243 | readlineInterface.question('Выберите формат воспроизведения: ', async (userInput) => { 244 | for(const [key, text] of VISUALIZATION_TYPE.entries()) { 245 | console.log(`${key}: ${text}`); 246 | } 247 | const input = parseInt(userInput); 248 | 249 | if (VISUALIZATION_TYPE.has(input)){ 250 | console.clear(); 251 | await visualization(dataToVisualizationPath) 252 | } else if(input === EXIT_SIGN){ 253 | console.log('Выход из приложения'); 254 | } else { 255 | console.log('Невозможное действие'); 256 | await chooseVisualizationType(readlineInterface, dataToVisualizationPath) 257 | } 258 | }); 259 | } 260 | 261 | export function getJsonFiles(directoryPath) { 262 | console.log(directoryPath) 263 | const filesAndDirectories = fs.readdirSync(directoryPath); 264 | const jsonFiles = filesAndDirectories.filter(fileName => path.extname(fileName) === '.json'); 265 | return jsonFiles.map(fileName => path.join(directoryPath, fileName)); 266 | } 267 | 268 | export function promptUserToSelectFile(filePaths, visualizationCallback) { 269 | const readlineInterface = createInterface({ 270 | input: process.stdin, 271 | output: process.stdout 272 | }); 273 | 274 | if (!filePaths.length) { 275 | console.log('Отсутствуют JSON файлы для воспроизведения'); 276 | } 277 | 278 | filePaths.forEach((filePath, index) => { 279 | console.log(`${index + 1}: ${filePath}`); 280 | }); 281 | 282 | readlineInterface.question('Выберите файл, введя его номер: ', (userInput) => { 283 | const selectedFile = filePaths[parseInt(userInput) - 1]; 284 | 285 | if (selectedFile) { 286 | console.log(`Вы выбрали: ${selectedFile}`); 287 | visualizationCallback(readlineInterface, selectedFile); 288 | } else { 289 | console.log('Введен недопустимый номер.'); 290 | } 291 | }); 292 | } --------------------------------------------------------------------------------