@@ -12,6 +12,8 @@ function getSortingVisualizerHTML() {
1212 <option value="selection">Selection Sort</option>
1313 <option value="insertion">Insertion Sort</option>
1414 <option value="quick">Quick Sort</option>
15+ <option value="merge">Merge Sort</option>
16+ <option value="heap">Heap Sort</option>
1517 </select>
1618 </div>
1719
@@ -289,7 +291,9 @@ function initSortingVisualizer() {
289291 bubble : { time : "O(N²)" , space : "O(1)" } ,
290292 selection : { time : "O(N²)" , space : "O(1)" } ,
291293 insertion : { time : "O(N²)" , space : "O(1)" } ,
292- quick : { time : "O(N log N) avg / O(N²) worst" , space : "O(log N)" }
294+ quick : { time : "O(N log N) avg / O(N²) worst" , space : "O(log N)" } ,
295+ merge : { time : "O(N log N)" , space : "O(N)" } ,
296+ heap : { time : "O(N log N)" , space : "O(1)" }
293297 } ;
294298
295299 // Update complexities when algorithm changes
@@ -507,6 +511,121 @@ function initSortingVisualizer() {
507511 renderBars ( arr , [ ] , [ ] , Array . from ( { length : arr . length } , ( _ , idx ) => idx ) ) ;
508512 }
509513
514+ async function mergeSortVisual ( arr ) {
515+ const sortedIndices = [ ] ;
516+
517+ async function mergeHelper ( left , right ) {
518+ if ( left >= right ) {
519+ if ( ! sortedIndices . includes ( left ) ) sortedIndices . push ( left ) ;
520+ return ;
521+ }
522+ if ( cancelSorting ) return ;
523+
524+ const mid = Math . floor ( ( left + right ) / 2 ) ;
525+ await mergeHelper ( left , mid ) ;
526+ await mergeHelper ( mid + 1 , right ) ;
527+ if ( cancelSorting ) return ;
528+
529+ const leftArr = arr . slice ( left , mid + 1 ) ;
530+ const rightArr = arr . slice ( mid + 1 , right + 1 ) ;
531+ let i = 0 , j = 0 , k = left ;
532+ const range = Array . from ( { length : right - left + 1 } , ( _ , idx ) => left + idx ) ;
533+
534+ while ( i < leftArr . length && j < rightArr . length ) {
535+ if ( cancelSorting ) return ;
536+ comparisons ++ ;
537+ renderBars ( arr , range , [ ] , sortedIndices ) ;
538+ await sleep ( delay ( ) ) ;
539+
540+ const shouldPickLeft = isAscending ? leftArr [ i ] <= rightArr [ j ] : leftArr [ i ] >= rightArr [ j ] ;
541+ arr [ k ] = shouldPickLeft ? leftArr [ i ++ ] : rightArr [ j ++ ] ;
542+ swaps ++ ;
543+ renderBars ( arr , [ ] , [ k ] , sortedIndices ) ;
544+ await sleep ( delay ( ) ) ;
545+ k ++ ;
546+ }
547+ while ( i < leftArr . length ) {
548+ arr [ k ] = leftArr [ i ++ ] ;
549+ swaps ++ ;
550+ renderBars ( arr , [ ] , [ k ] , sortedIndices ) ;
551+ await sleep ( delay ( ) ) ;
552+ k ++ ;
553+ }
554+ while ( j < rightArr . length ) {
555+ arr [ k ] = rightArr [ j ++ ] ;
556+ swaps ++ ;
557+ renderBars ( arr , [ ] , [ k ] , sortedIndices ) ;
558+ await sleep ( delay ( ) ) ;
559+ k ++ ;
560+ }
561+
562+ for ( let idx = left ; idx <= right ; idx ++ ) {
563+ if ( ! sortedIndices . includes ( idx ) ) sortedIndices . push ( idx ) ;
564+ }
565+ renderBars ( arr , [ ] , [ ] , sortedIndices ) ;
566+ await sleep ( delay ( ) ) ;
567+ }
568+
569+ await mergeHelper ( 0 , arr . length - 1 ) ;
570+ renderBars ( arr , [ ] , [ ] , Array . from ( { length : arr . length } , ( _ , idx ) => idx ) ) ;
571+ }
572+
573+ async function heapSortVisual ( arr ) {
574+ const n = arr . length ;
575+ const sortedIndices = [ ] ;
576+
577+ async function siftDown ( root , end ) {
578+ while ( true ) {
579+ if ( cancelSorting ) return ;
580+ let child = 2 * root + 1 ;
581+ if ( child > end ) break ;
582+
583+ comparisons ++ ;
584+ renderBars ( arr , [ root , child ] , [ ] , sortedIndices ) ;
585+ await sleep ( delay ( ) ) ;
586+
587+ if ( child + 1 <= end ) {
588+ comparisons ++ ;
589+ renderBars ( arr , [ root , child , child + 1 ] , [ ] , sortedIndices ) ;
590+ await sleep ( delay ( ) ) ;
591+ const rightIsNext = isAscending ? arr [ child + 1 ] > arr [ child ] : arr [ child + 1 ] < arr [ child ] ;
592+ if ( rightIsNext ) child ++ ;
593+ }
594+
595+ const shouldSwap = isAscending ? arr [ root ] < arr [ child ] : arr [ root ] > arr [ child ] ;
596+ if ( shouldSwap ) {
597+ swaps ++ ;
598+ renderBars ( arr , [ ] , [ root , child ] , sortedIndices ) ;
599+ await sleep ( delay ( ) ) ;
600+ [ arr [ root ] , arr [ child ] ] = [ arr [ child ] , arr [ root ] ] ;
601+ root = child ;
602+ } else {
603+ break ;
604+ }
605+ }
606+ }
607+
608+ for ( let start = Math . floor ( n / 2 ) - 1 ; start >= 0 ; start -- ) {
609+ if ( cancelSorting ) return ;
610+ await siftDown ( start , n - 1 ) ;
611+ }
612+
613+ for ( let end = n - 1 ; end > 0 ; end -- ) {
614+ if ( cancelSorting ) return ;
615+ swaps ++ ;
616+ renderBars ( arr , [ ] , [ 0 , end ] , sortedIndices ) ;
617+ await sleep ( delay ( ) ) ;
618+ [ arr [ 0 ] , arr [ end ] ] = [ arr [ end ] , arr [ 0 ] ] ;
619+ sortedIndices . push ( end ) ;
620+ renderBars ( arr , [ ] , [ ] , sortedIndices ) ;
621+ await sleep ( delay ( ) ) ;
622+ await siftDown ( 0 , end - 1 ) ;
623+ }
624+
625+ if ( ! sortedIndices . includes ( 0 ) ) sortedIndices . push ( 0 ) ;
626+ renderBars ( arr , [ ] , [ ] , sortedIndices ) ;
627+ }
628+
510629 randomSort . addEventListener ( 'click' , ( ) => {
511630 if ( isSorting ) return ;
512631 generateRandomArray ( ) ;
@@ -563,6 +682,10 @@ function initSortingVisualizer() {
563682 await insertionSort ( workingArr ) ;
564683 } else if ( algo === 'quick' ) {
565684 await quickSortVisual ( workingArr ) ;
685+ } else if ( algo === 'merge' ) {
686+ await mergeSortVisual ( workingArr ) ;
687+ } else if ( algo === 'heap' ) {
688+ await heapSortVisual ( workingArr ) ;
566689 }
567690
568691 if ( cancelSorting ) {
@@ -590,4 +713,4 @@ function initSortingVisualizer() {
590713
591714 // Initial render
592715 generateRandomArray ( ) ;
593- }
716+ }
0 commit comments