├── .gitignore ├── app-demo.gif ├── src ├── utils │ ├── generateRandomNumber.js │ ├── swap.js │ ├── getNumberOfAlgosInRow.js │ ├── appendChildrenToHtmlElement.js │ ├── wait.js │ ├── generateRandomArray.js │ └── createHtmlArrayElements.js ├── consts.js ├── sortingAlgos │ ├── bubbleSort.js │ ├── insertionSort.js │ ├── mergeSort.js │ ├── quickSort.js │ └── heapSort.js └── index.js ├── package.json ├── README.md ├── yarn.lock ├── style.css ├── LICENSE └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | dist 4 | -------------------------------------------------------------------------------- /app-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loonywizard/sorting-algos-visualizer/HEAD/app-demo.gif -------------------------------------------------------------------------------- /src/utils/generateRandomNumber.js: -------------------------------------------------------------------------------- 1 | /* 2 | * generates random number between min and max 3 | */ 4 | function generateRandomNumber(min, max) { 5 | return Math.round(Math.random() * (max - min) + min) 6 | } 7 | 8 | export { generateRandomNumber } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Vladimir Nikitin vladimirnikitin.com", 3 | "scripts": { 4 | "build": "esbuild src/index.js --bundle --minify --target=chrome58 --outfile=dist/index.js" 5 | }, 6 | "devDependencies": { 7 | "esbuild": "^0.12.8" 8 | }, 9 | "license": "MIT" 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/swap.js: -------------------------------------------------------------------------------- 1 | /* 2 | * swaps two given elements in a given array 3 | */ 4 | function swap(arr, firstElementIndex, secondElementIndex) { 5 | const t = arr[firstElementIndex] 6 | arr[firstElementIndex] = arr[secondElementIndex] 7 | arr[secondElementIndex] = t 8 | } 9 | 10 | export { swap } 11 | -------------------------------------------------------------------------------- /src/utils/getNumberOfAlgosInRow.js: -------------------------------------------------------------------------------- 1 | // these breakpoints are also declared in styles.css file 2 | function getNumberOfAlgosInRow() { 3 | if (document.body.offsetWidth <= '900') return 1 4 | if (document.body.offsetWidth <= '1400') return 2 5 | 6 | return 3 7 | } 8 | 9 | export { getNumberOfAlgosInRow } 10 | -------------------------------------------------------------------------------- /src/utils/appendChildrenToHtmlElement.js: -------------------------------------------------------------------------------- 1 | /* 2 | * appends child elements to parent element 3 | */ 4 | function appendChildrenToHtmlElement(htmlElement, children) { 5 | children.forEach((elementToAppend) => { 6 | htmlElement.appendChild(elementToAppend) 7 | }) 8 | } 9 | 10 | export { appendChildrenToHtmlElement } 11 | -------------------------------------------------------------------------------- /src/utils/wait.js: -------------------------------------------------------------------------------- 1 | import { OPERATIONS_PER_SECOND } from '../consts' 2 | 3 | 4 | /* 5 | * wait a given time (in milliseconds) 6 | */ 7 | function wait(timeToWait = 1000 / OPERATIONS_PER_SECOND) { 8 | return new Promise((resolve) => { 9 | setTimeout(() => resolve(), timeToWait) 10 | }) 11 | } 12 | 13 | export { wait } 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # sorting-algos-visualizer 4 | 5 | Visualization of common sorting algorithms 6 | 7 | Implemented sorting algorithms: 8 | - Quick sort 9 | - Merge sort 10 | - Heap sort 11 | - Bubble sort 12 | - Insertion sort 13 | 14 | live demo: https://loonywizard.github.io/sorting-algos-visualizer/ 15 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | esbuild@^0.12.8: 6 | version "0.12.8" 7 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.8.tgz#ac90da77cb3bfbf49ab815200bcef7ffe1a3348f" 8 | integrity sha512-sx/LwlP/SWTGsd9G4RlOPrXnIihAJ2xwBUmzoqe2nWwbXORMQWtAGNJNYLBJJqa3e9PWvVzxdrtyFZJcr7D87g== 9 | -------------------------------------------------------------------------------- /src/utils/generateRandomArray.js: -------------------------------------------------------------------------------- 1 | import { generateRandomNumber } from './generateRandomNumber' 2 | 3 | 4 | /* 5 | * generates array of length n of random numbers between min and max 6 | */ 7 | function generateRandomArray({ n, min, max }) { 8 | const arr = [] 9 | 10 | for (let i = 0; i < n; i++) { 11 | arr.push(generateRandomNumber(min, max)) 12 | } 13 | 14 | return arr 15 | } 16 | 17 | export { generateRandomArray } 18 | -------------------------------------------------------------------------------- /src/consts.js: -------------------------------------------------------------------------------- 1 | /* 2 | * declare constants 3 | */ 4 | const OPERATIONS_PER_SECOND = 20 5 | 6 | const LEFT_ITEM_CSS_CLASS = 'left-item' 7 | const SAME_ITEM_CSS_CLASS = 'same-item' 8 | const RIGHT_ITEM_CSS_CLASS = 'right-item' 9 | 10 | const ARRAY_ITEM_WIDTH = 10 11 | 12 | export { 13 | OPERATIONS_PER_SECOND, 14 | 15 | LEFT_ITEM_CSS_CLASS, 16 | SAME_ITEM_CSS_CLASS, 17 | RIGHT_ITEM_CSS_CLASS, 18 | 19 | ARRAY_ITEM_WIDTH, 20 | } 21 | -------------------------------------------------------------------------------- /src/utils/createHtmlArrayElements.js: -------------------------------------------------------------------------------- 1 | import { appendChildrenToHtmlElement } from './appendChildrenToHtmlElement' 2 | 3 | 4 | /* 5 | * creates HTML element - item of sorting array 6 | */ 7 | function createArrayHtmlElement(value) { 8 | const htmlElement = document.createElement('div') 9 | 10 | htmlElement.classList.add('item') 11 | htmlElement.style.height = `${value}px` 12 | 13 | return htmlElement 14 | } 15 | 16 | 17 | /* 18 | * inits HTML elements for given array and iserts them to DOM 19 | */ 20 | function createHtmlArrayElements(arr, wrapperId) { 21 | const algoWrapper = document.getElementById(wrapperId) 22 | const algoContainer = algoWrapper.getElementsByClassName('algo-container')[0] 23 | 24 | const elements = arr.map(createArrayHtmlElement) 25 | 26 | appendChildrenToHtmlElement(algoContainer, elements) 27 | 28 | return elements 29 | } 30 | 31 | export { createHtmlArrayElements } 32 | -------------------------------------------------------------------------------- /src/sortingAlgos/bubbleSort.js: -------------------------------------------------------------------------------- 1 | import { wait } from '../utils/wait' 2 | import { swap } from '../utils/swap' 3 | import { LEFT_ITEM_CSS_CLASS, RIGHT_ITEM_CSS_CLASS } from '../consts' 4 | 5 | 6 | /* 7 | * bubble sort visualization 8 | */ 9 | async function bubbleSort(arr, htmlElements) { 10 | const n = arr.length 11 | 12 | for (let i = 0; i < n; i++) { 13 | for (let j = 0; j < n - i - 1; j++) { 14 | htmlElements[j].classList.add(LEFT_ITEM_CSS_CLASS) 15 | htmlElements[j + 1].classList.add(RIGHT_ITEM_CSS_CLASS) 16 | 17 | 18 | await wait() 19 | 20 | if (arr[j] > arr[j + 1]) { 21 | swap(arr, j, j + 1) 22 | 23 | htmlElements[j].style.height = `${arr[j]}px` 24 | htmlElements[j + 1].style.height = `${arr[j + 1]}px` 25 | } 26 | 27 | htmlElements[j].classList.remove(LEFT_ITEM_CSS_CLASS) 28 | htmlElements[j + 1].classList.remove(RIGHT_ITEM_CSS_CLASS) 29 | } 30 | } 31 | } 32 | 33 | export { bubbleSort } 34 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | padding: 0; 3 | margin: 0; 4 | background: #03045e; 5 | color: #caf0f8; 6 | } 7 | a { 8 | color: #ade8f4; 9 | } 10 | .info { 11 | padding: 25px; 12 | } 13 | .wrapper { 14 | padding: 10px; 15 | display: grid; 16 | grid-template-columns: 1fr 1fr 1fr; 17 | grid-row-gap: 35px; 18 | grid-column-gap: 20px; 19 | } 20 | 21 | @media screen and (max-width: 1400px) { 22 | .wrapper { 23 | grid-template-columns: 1fr 1fr; 24 | } 25 | } 26 | 27 | @media screen and (max-width: 900px) { 28 | .wrapper { 29 | grid-template-columns: 1fr; 30 | } 31 | } 32 | 33 | @media screen and (max-width: 600px) { 34 | h1 { 35 | font-size: 1.4em; 36 | } 37 | } 38 | 39 | .algo-container { 40 | height: 400px; 41 | display: flex; 42 | align-items: flex-end; 43 | } 44 | .algo-info { 45 | margin-top: 10px; 46 | } 47 | .item { 48 | margin-left: 1px; 49 | flex-grow: 1; 50 | background: #0077b6; 51 | } 52 | 53 | .left-item { 54 | background: #90e0ef; 55 | } 56 | .right-item { 57 | background: #00b4d8; 58 | } 59 | .same-item { 60 | background: #caf0f8; 61 | } 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Vladimir Nikitin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/sortingAlgos/insertionSort.js: -------------------------------------------------------------------------------- 1 | import { wait } from '../utils/wait' 2 | import { swap } from '../utils/swap' 3 | import { LEFT_ITEM_CSS_CLASS, RIGHT_ITEM_CSS_CLASS } from '../consts' 4 | 5 | 6 | /* 7 | * insertion sort implementation 8 | */ 9 | async function insertionSort(arr, htmlElements) { 10 | const n = arr.length 11 | 12 | for (let i = 1; i < n; i++) { 13 | let currentElementIndex = i 14 | 15 | htmlElements[currentElementIndex].classList.add(RIGHT_ITEM_CSS_CLASS) 16 | await wait() 17 | 18 | for (let j = i - 1; j >= 0; j--) { 19 | htmlElements[j].classList.add(LEFT_ITEM_CSS_CLASS) 20 | await wait() 21 | 22 | if (arr[currentElementIndex] >= arr[j]) { 23 | htmlElements[currentElementIndex].classList.remove(RIGHT_ITEM_CSS_CLASS) 24 | htmlElements[j].classList.remove(LEFT_ITEM_CSS_CLASS) 25 | await wait() 26 | break 27 | } 28 | 29 | swap(arr, currentElementIndex, j) 30 | 31 | htmlElements[currentElementIndex].style.height = `${arr[currentElementIndex]}px` 32 | htmlElements[j].style.height = `${arr[j]}px` 33 | 34 | htmlElements[currentElementIndex].classList.remove(RIGHT_ITEM_CSS_CLASS) 35 | htmlElements[j].classList.remove(LEFT_ITEM_CSS_CLASS) 36 | htmlElements[j].classList.add(RIGHT_ITEM_CSS_CLASS) 37 | 38 | await wait() 39 | 40 | 41 | currentElementIndex-- 42 | } 43 | 44 | htmlElements[currentElementIndex].classList.remove(LEFT_ITEM_CSS_CLASS) 45 | htmlElements[currentElementIndex].classList.remove(RIGHT_ITEM_CSS_CLASS) 46 | } 47 | } 48 | 49 | export { insertionSort } 50 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { generateRandomArray } from './utils/generateRandomArray' 2 | import { createHtmlArrayElements } from './utils/createHtmlArrayElements' 3 | import { getNumberOfAlgosInRow } from './utils/getNumberOfAlgosInRow' 4 | 5 | import { quickSort } from './sortingAlgos/quickSort' 6 | import { mergeSort } from './sortingAlgos/mergeSort' 7 | import { heapSort } from './sortingAlgos/heapSort' 8 | import { bubbleSort } from './sortingAlgos/bubbleSort' 9 | import { insertionSort } from './sortingAlgos/insertionSort' 10 | 11 | import { ARRAY_ITEM_WIDTH } from './consts' 12 | 13 | 14 | /* 15 | * init array and html elements 16 | */ 17 | const ARRAY_LENGTH = Math.round(document.body.offsetWidth / (ARRAY_ITEM_WIDTH * getNumberOfAlgosInRow())) 18 | const initialRandomArray = generateRandomArray({ n: ARRAY_LENGTH, min: 10, max: 400 }) 19 | 20 | const htmlElementsQuickSort = createHtmlArrayElements(initialRandomArray, 'quick-sort') 21 | const htmlElementsMergeSort = createHtmlArrayElements(initialRandomArray, 'merge-sort') 22 | const htmlElementsHeapSort = createHtmlArrayElements(initialRandomArray, 'heap-sort') 23 | const htmlElementsBubbleSort = createHtmlArrayElements(initialRandomArray, 'bubble-sort') 24 | const htmlElementsInsertionSort = createHtmlArrayElements(initialRandomArray, 'insertion-sort') 25 | 26 | 27 | /* 28 | * start visualization process 29 | */ 30 | quickSort([...initialRandomArray], htmlElementsQuickSort) 31 | mergeSort([...initialRandomArray], htmlElementsMergeSort) 32 | heapSort([...initialRandomArray], htmlElementsHeapSort) 33 | bubbleSort([...initialRandomArray], htmlElementsBubbleSort) 34 | insertionSort([...initialRandomArray], htmlElementsInsertionSort) 35 | -------------------------------------------------------------------------------- /src/sortingAlgos/mergeSort.js: -------------------------------------------------------------------------------- 1 | import { wait } from '../utils/wait' 2 | import { LEFT_ITEM_CSS_CLASS, SAME_ITEM_CSS_CLASS, RIGHT_ITEM_CSS_CLASS } from '../consts' 3 | 4 | 5 | /* 6 | * merge sort visualization 7 | */ 8 | async function mergeSort(arr, htmlElements, shiftIndex = 0) { 9 | if (arr.length < 2) return arr 10 | 11 | const middleElementIndex = Math.round(arr.length / 2) 12 | 13 | htmlElements[middleElementIndex + shiftIndex].classList.add(SAME_ITEM_CSS_CLASS) 14 | await wait() 15 | htmlElements[middleElementIndex + shiftIndex].classList.remove(SAME_ITEM_CSS_CLASS) 16 | 17 | const leftElements = arr.slice(0, middleElementIndex) 18 | const rightElements = arr.slice(middleElementIndex) 19 | 20 | const sortedLeftElements = await mergeSort(leftElements, htmlElements, shiftIndex) 21 | const sortedRightElements = await mergeSort(rightElements, htmlElements, shiftIndex + middleElementIndex) 22 | 23 | let i = 0, j = 0, k = 0 24 | 25 | while (i < sortedLeftElements.length && j < sortedRightElements.length) { 26 | if (sortedLeftElements[i] < sortedRightElements[j]) { 27 | arr[k] = sortedLeftElements[i] 28 | htmlElements[k + shiftIndex].classList.add(LEFT_ITEM_CSS_CLASS) 29 | htmlElements[k + shiftIndex].style.height = `${arr[k]}px` 30 | k++ 31 | i++ 32 | } else { 33 | arr[k] = sortedRightElements[j] 34 | htmlElements[k + shiftIndex].classList.add(RIGHT_ITEM_CSS_CLASS) 35 | htmlElements[k + shiftIndex].style.height = `${arr[k]}px` 36 | k++ 37 | j++ 38 | } 39 | await wait() 40 | } 41 | 42 | while (i < sortedLeftElements.length) { 43 | arr[k] = sortedLeftElements[i] 44 | htmlElements[k + shiftIndex].classList.add(LEFT_ITEM_CSS_CLASS) 45 | htmlElements[k + shiftIndex].style.height = `${arr[k]}px` 46 | k++ 47 | i++ 48 | await wait() 49 | } 50 | while (j < sortedRightElements.length) { 51 | arr[k] = sortedRightElements[j] 52 | htmlElements[k + shiftIndex].classList.add(RIGHT_ITEM_CSS_CLASS) 53 | htmlElements[k + shiftIndex].style.height = `${arr[k]}px` 54 | k++ 55 | j++ 56 | await wait() 57 | } 58 | 59 | for (let i = 0; i < arr.length; i++) { 60 | htmlElements[i + shiftIndex].classList.remove(LEFT_ITEM_CSS_CLASS) 61 | htmlElements[i + shiftIndex].classList.remove(SAME_ITEM_CSS_CLASS) 62 | htmlElements[i + shiftIndex].classList.remove(RIGHT_ITEM_CSS_CLASS) 63 | } 64 | 65 | return arr 66 | } 67 | 68 | export { mergeSort } 69 | -------------------------------------------------------------------------------- /src/sortingAlgos/quickSort.js: -------------------------------------------------------------------------------- 1 | import { wait } from '../utils/wait' 2 | import { swap } from '../utils/swap' 3 | import { LEFT_ITEM_CSS_CLASS, SAME_ITEM_CSS_CLASS, RIGHT_ITEM_CSS_CLASS } from '../consts' 4 | 5 | 6 | /* 7 | * partition function for quick sort in-place visualization 8 | * 9 | * reference: https://www.geeksforgeeks.org/quick-sort/ 10 | */ 11 | async function partition(arr, htmlElements, start, end) { 12 | const pivotIndex = start 13 | const pivotElement = arr[pivotIndex] 14 | 15 | htmlElements[start].classList.add(LEFT_ITEM_CSS_CLASS) 16 | htmlElements[pivotIndex].classList.add(SAME_ITEM_CSS_CLASS) 17 | htmlElements[end].classList.add(RIGHT_ITEM_CSS_CLASS) 18 | 19 | await wait() 20 | 21 | while (start < end) { 22 | while (start < arr.length && arr[start] <= pivotElement) { 23 | if (htmlElements[start]) { 24 | htmlElements[start].classList.remove(LEFT_ITEM_CSS_CLASS) 25 | htmlElements[start].classList.remove(RIGHT_ITEM_CSS_CLASS) 26 | } 27 | 28 | start++ 29 | 30 | if (htmlElements[start]) { 31 | htmlElements[start].classList.add(LEFT_ITEM_CSS_CLASS) 32 | } 33 | 34 | await wait() 35 | } 36 | 37 | while (arr[end] > pivotElement) { 38 | if (htmlElements[end]) { 39 | htmlElements[end].classList.remove(LEFT_ITEM_CSS_CLASS) 40 | htmlElements[end].classList.remove(RIGHT_ITEM_CSS_CLASS) 41 | } 42 | 43 | end-- 44 | 45 | if (htmlElements[end]) { 46 | htmlElements[end].classList.add(RIGHT_ITEM_CSS_CLASS) 47 | } 48 | 49 | await wait() 50 | } 51 | 52 | if (start < end) { 53 | swap(arr, start, end) 54 | 55 | htmlElements[start].classList.remove(LEFT_ITEM_CSS_CLASS) 56 | htmlElements[start].classList.add(RIGHT_ITEM_CSS_CLASS) 57 | 58 | htmlElements[end].classList.remove(RIGHT_ITEM_CSS_CLASS) 59 | htmlElements[end].classList.add(LEFT_ITEM_CSS_CLASS) 60 | 61 | htmlElements[start].style.height = `${arr[start]}px` 62 | htmlElements[end].style.height = `${arr[end]}px` 63 | 64 | await wait() 65 | } 66 | } 67 | 68 | if (htmlElements[start]) { 69 | htmlElements[start].classList.remove(LEFT_ITEM_CSS_CLASS) 70 | } 71 | 72 | swap(arr, pivotIndex, end) 73 | 74 | htmlElements[pivotIndex].classList.remove(SAME_ITEM_CSS_CLASS) 75 | htmlElements[end].classList.remove(RIGHT_ITEM_CSS_CLASS) 76 | 77 | htmlElements[pivotIndex].style.height = `${arr[pivotIndex]}px` 78 | htmlElements[end].style.height = `${arr[end]}px` 79 | 80 | return end 81 | } 82 | 83 | 84 | /* 85 | * quick sort visualization 86 | */ 87 | async function quickSort(arr, htmlElements, start = 0, end = arr.length - 1) { 88 | if (start >= end) return 89 | 90 | const p = await partition(arr, htmlElements, start, end) 91 | 92 | await quickSort(arr, htmlElements, start, p - 1) 93 | await quickSort(arr, htmlElements, p + 1, end) 94 | } 95 | 96 | export { quickSort } 97 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 |

sorting-algos-visualizer

13 |

14 | Visualization of common sorting algorithms. 15 | Source code is available on 16 | GitHub, 17 | made by Vladimir Nikitin 18 |

19 |
20 |
21 |
22 |
23 |
24 |
25 | Quick Sort 26 | is a divide-and-conquer algorithm. 27 | It works by selecting a 'pivot' element from the array and partitioning the other elements into two sub-arrays, 28 | according to whether they are less than or greater than the pivot. The sub-arrays are then sorted recursively. 29 | Link to Wikipedia 30 |
31 |
32 | 33 |
34 |
35 |
36 | Merge Sort 37 | algorithm works as follows: divide the unsorted list into n sublists, each containing one element (a list of one element is considered sorted). 38 | Repeatedly merge sublists to produce new sorted sublists until there is only one sublist remaining. This will be the sorted list. 39 | Link to Wikipedia 40 |
41 |
42 | 43 |
44 |
45 |
46 | Heap Sort: 47 | in the first step, a Max Heap is built out of the input array. 48 | In the second step, a sorted array is created by repeatedly removing the largest element from the heap, and inserting it into the array. 49 | The heap is updated after each removal to maintain the heap property. 50 | Link to Wikipedia 51 |
52 |
53 | 54 |
55 |
56 |
57 | Bubble Sort 58 | is a simple sorting algorithm that repeatedly steps through the list, compares adjacent elements and swaps them if they are in the wrong order. 59 | The pass through the list is repeated until the list is sorted. 60 | Link to Wikipedia 61 |
62 |
63 | 64 |
65 |
66 |
67 | Insertion Sort 68 | iterates, consuming one input element each repetition, and grows a sorted output list. 69 | At each iteration, insertion sort removes one element from the input data, 70 | finds the location it belongs within the sorted list, and inserts it there. It repeats until no input elements remain. 71 | Link to Wikipedia 72 |
73 |
74 |
75 | 76 | 77 | -------------------------------------------------------------------------------- /src/sortingAlgos/heapSort.js: -------------------------------------------------------------------------------- 1 | import { wait } from '../utils/wait' 2 | import { LEFT_ITEM_CSS_CLASS, RIGHT_ITEM_CSS_CLASS } from '../consts' 3 | 4 | 5 | /* 6 | * MaxHeap is used in heap sort algorithm 7 | */ 8 | class MaxHeap { 9 | constructor(htmlElements) { 10 | this.heap = [] 11 | this.htmlElements = htmlElements 12 | } 13 | 14 | getLeftChildIndex = (parentIndex) => 2 * parentIndex + 1 15 | getRightChildIndex = (parentIndex) => 2 * parentIndex + 2 16 | getParentIndex = (childIndex) => Math.floor((childIndex - 1) / 2) 17 | 18 | hasLeftChild = (index) => this.getLeftChildIndex(index) < this.heap.length 19 | hasRightChild = (index) => this.getRightChildIndex(index) < this.heap.length 20 | hasParent = (index) => this.getParentIndex(index) >= 0 21 | 22 | getLeftChild = (index) => this.heap[this.getLeftChildIndex(index)] 23 | getRightChild = (index) => this.heap[this.getRightChildIndex(index)] 24 | getParent = (index) => this.heap[this.getParentIndex(index)] 25 | 26 | swap(firstIndex, secondIndex) { 27 | [this.heap[firstIndex], this.heap[secondIndex]] = [this.heap[secondIndex], this.heap[firstIndex]] 28 | } 29 | 30 | isEmpty() { 31 | return this.heap.length === 0 32 | } 33 | 34 | async heapifyUp() { 35 | let index = this.heap.length - 1 36 | 37 | this.htmlElements[index].classList.add(LEFT_ITEM_CSS_CLASS) 38 | 39 | while (this.hasParent(index) && this.comparatorFn(this.heap[index], this.getParent(index))) { 40 | const parentIndex = this.getParentIndex(index) 41 | 42 | this.htmlElements[parentIndex].classList.add(RIGHT_ITEM_CSS_CLASS) 43 | await wait() 44 | 45 | this.swap(index, parentIndex) 46 | 47 | this.htmlElements[index].classList.remove(LEFT_ITEM_CSS_CLASS) 48 | this.htmlElements[index].classList.add(RIGHT_ITEM_CSS_CLASS) 49 | 50 | this.htmlElements[parentIndex].classList.remove(RIGHT_ITEM_CSS_CLASS) 51 | this.htmlElements[parentIndex].classList.add(LEFT_ITEM_CSS_CLASS) 52 | 53 | this.htmlElements[index].style.height = `${this.heap[index]}px` 54 | this.htmlElements[parentIndex].style.height = `${this.heap[parentIndex]}px` 55 | 56 | await wait() 57 | this.htmlElements[index].classList.remove(RIGHT_ITEM_CSS_CLASS) 58 | 59 | index = parentIndex 60 | } 61 | 62 | this.htmlElements[index].classList.remove(LEFT_ITEM_CSS_CLASS) 63 | } 64 | 65 | async insert(nodeValue) { 66 | this.heap.push(nodeValue) 67 | await this.heapifyUp() 68 | } 69 | 70 | async heapifyDown() { 71 | let index = 0 72 | 73 | this.htmlElements[index].classList.add(LEFT_ITEM_CSS_CLASS) 74 | 75 | while (this.hasLeftChild(index)) { 76 | let childIndexToReplace = this.getLeftChildIndex(index) 77 | if (this.hasRightChild(index) && this.comparatorFn(this.getRightChild(index), this.getLeftChild(index))) { 78 | childIndexToReplace = this.getRightChildIndex(index) 79 | } 80 | 81 | if (this.comparatorFn(this.heap[index], this.heap[childIndexToReplace])) { 82 | break 83 | } 84 | 85 | this.htmlElements[childIndexToReplace].classList.add(RIGHT_ITEM_CSS_CLASS) 86 | 87 | await wait() 88 | 89 | this.swap(index, childIndexToReplace) 90 | this.htmlElements[index].classList.remove(LEFT_ITEM_CSS_CLASS) 91 | this.htmlElements[index].classList.add(RIGHT_ITEM_CSS_CLASS) 92 | 93 | this.htmlElements[childIndexToReplace].classList.remove(RIGHT_ITEM_CSS_CLASS) 94 | this.htmlElements[childIndexToReplace].classList.add(LEFT_ITEM_CSS_CLASS) 95 | 96 | this.htmlElements[index].style.height = `${this.heap[index]}px` 97 | this.htmlElements[childIndexToReplace].style.height = `${this.heap[childIndexToReplace]}px` 98 | await wait() 99 | this.htmlElements[index].classList.remove(RIGHT_ITEM_CSS_CLASS) 100 | index = childIndexToReplace 101 | } 102 | 103 | this.htmlElements[index].classList.remove(LEFT_ITEM_CSS_CLASS) 104 | } 105 | 106 | async pop() { 107 | if (this.isEmpty()) throw new Error('Heap is empty!') 108 | 109 | // remove first element 110 | const item = this.heap[0] 111 | 112 | if (this.heap.length <= 2) { 113 | this.heap.shift() 114 | return item 115 | } 116 | 117 | // insert last element to the beginning 118 | this.heap[0] = this.heap.pop() 119 | 120 | await this.heapifyDown() 121 | 122 | return item 123 | } 124 | 125 | comparatorFn(valueA, valueB) { 126 | return valueA > valueB 127 | } 128 | } 129 | 130 | 131 | /* 132 | * heap sort visualization 133 | */ 134 | async function heapSort(arr, htmlElements) { 135 | const heap = new MaxHeap(htmlElements) 136 | 137 | for (let element of arr) { 138 | await heap.insert(element) 139 | } 140 | 141 | for (let i = arr.length - 1; i >= 0; i--) { 142 | arr[i] = await heap.pop() 143 | 144 | htmlElements[i].style.height = `${arr[i]}px` 145 | } 146 | } 147 | 148 | export { heapSort } 149 | --------------------------------------------------------------------------------