Merge Sort

One of the fastest sorting algorithms. Used by general computing and machine learning.

  • This is the most important sorting algorithm.
  • Discovered by John von Neumann (1945).
  • Uses the methodology divide and conquer.

Performance

Θ(nlgn)

Pseudo-code

Recursive algorithm composed of two stages:

  1. Split the array into halves. Θ(1)
  2. Merge the units in a sorted way. Θ$$(n)$$

The recursive part brings the lgn since the splitting creates a binary tree data structure which reduces by a power of two each increase of n.

My example code using recursion

  1. /**
  2. * sorts and merge two sub arrays.
  3. * Written in a recursive functional programming.
  4. * Free of side-effects.
  5. *
  6. * Concatenating arrays before returning,
  7. * to use Proper Tail Call optimization.
  8. *
  9. * @param {array} sorted
  10. * @param {array} subA
  11. * @param {array} subB
  12. * @result {array} final sorted array
  13. */
  14. function merge (sorted, subA, subB ) {
  15. if (!subA.length && !subB.length) {
  16. return sorted;
  17. }
  18. if (!subA.length && subB.length) {
  19. const newSorted = [...sorted, ...subB];
  20. return newSorted;
  21. }
  22. if (subA.length && !subB.length) {
  23. const newSorted2 = [...sorted, ...subA];
  24. return newSorted2;
  25. }
  26. if (subA[0] <= subB[0]) {
  27. const newSubA = subA.slice(1);
  28. const newSorted3 = [ ...sorted, subA[0] ];
  29. return merge(newSorted3, newSubA, subB);
  30. }
  31. const newSubB = subB.slice(1);
  32. const newSorted4 = [ ...sorted, subB[0] ];
  33. return merge(newSorted4, subA, newSubB);
  34. }
  35. function mergeSort (array) {
  36. if (array.length <= 1) {
  37. return array;
  38. }
  39. if (array.length === 2) {
  40. const subA2 = [array[0]];
  41. const subB2 = [array[1]];
  42. const sorted = merge([], subA2, subB2);
  43. return sorted;
  44. }
  45. const midIndex = Math.floor( (array.length) / 2 );
  46. const subA = mergeSort( array.slice(0, midIndex) );
  47. const subB = mergeSort( array.slice(midIndex) );
  48. const sorted2 = merge([], subA, subB);
  49. return sorted2;
  50. }
  51. // Unit tests
  52. // Test1
  53. const sorted = merge([], [3,5], [2,6]);
  54. const should = [2,3,5,6];
  55. console.log(`
  56. Test merge():
  57. ${sorted.toString() === should.toString()}:
  58. ${sorted.toString()} should be ${should}
  59. `);
  60. // Test2
  61. const sorted2 = merge([], [5,3], [2,6]);
  62. const should2 = [2,3,5,6];
  63. console.log(`
  64. Test2 merge():
  65. This should not sort correctly since the sub arrays arenot sorted.
  66. ${sorted2.toString() !== should2.toString()}:
  67. ${sorted2.toString()} should not be ${should2}
  68. `);
  69. // Test3
  70. const array = [5,3,2,6];
  71. const should3 = [2, 3, 5, 6];
  72. const sorted3 = mergeSort(array);
  73. console.log(`
  74. Test3 mergeSort(${array.toString()}):
  75. ${sorted3.toString() === should3.toString()}:
  76. ${sorted3.toString()} should be ${should3}
  77. `);
  78. // Test4
  79. const array2 = [-2,5,0,7];
  80. const should4 = [-2, 0, 5, 7];
  81. const sorted4 = mergeSort(array2);
  82. console.log(`
  83. Test4 mergeSort(${array2.toString()}):
  84. Should handle negative numbers and 0's.
  85. ${sorted4.toString() === should4.toString()}:
  86. ${sorted4.toString()} should be ${should4}
  87. `);
  88. // Test5
  89. const array3 = [1, 3, 4, 7];
  90. const should5 = [1, 3, 4, 7];
  91. const sorted5 = mergeSort(array3);
  92. console.log(`
  93. Test5 mergeSort(${array3.toString()}):
  94. Should work with sorted arrays.
  95. ${sorted5.toString() === should5.toString()}:
  96. ${sorted5.toString()} should be ${should5}
  97. `);
  98. // Test6
  99. const array4 = [ 1, 209, 8, 80, 3, 7, 0 ];
  100. const should6 = [ 0, 1, 3, 7, 8, 80, 209 ];
  101. const sorted6 = mergeSort(array4);
  102. console.log(`
  103. Test6 mergeSort(${array4.toString()}):
  104. Should work with long and odd length arrays
  105. ${sorted6.toString() === should6.toString()}:
  106. ${sorted6.toString()} should be ${should6}
  107. `);
  108. // Display initial array in HTML
  109. const initElement = document.querySelector('.init');
  110. initElement.textContent = array.toString();
  111. // Display sorted array in HTML
  112. const resultElement = document.querySelector('.result');
  113. const resultContent = mergeSort(array);
  114. resultElement.textContent = resultContent.toString();

Example code from Khan Academy

  1. // Takes in an array that has two sorted subarrays,
  2. // from [p..q] and [q+1..r], and merges the array
  3. var merge = function(array, p, q, r) {
  4. var lowHalf = [];
  5. var highHalf = [];
  6. var k = p;
  7. var i;
  8. var j;
  9. for (i = 0; k <= q; i++, k++) {
  10. lowHalf[i] = array[k];
  11. }
  12. for (j = 0; k <= r; j++, k++) {
  13. highHalf[j] = array[k];
  14. }
  15. k = p;
  16. i = 0;
  17. j = 0;
  18. // Repeatedly compare the lowest untaken element in
  19. // lowHalf with the lowest untaken element in highHalf
  20. // and copy the lower of the two back into array
  21. while (i < lowHalf.length && j < highHalf.length) {
  22. if (lowHalf[i] < highHalf[j]) {
  23. array[k] = lowHalf[i];
  24. i++;
  25. } else {
  26. array[k] = highHalf[j];
  27. j++;
  28. }
  29. k++;
  30. }
  31. // Once one of lowHalf and highHalf has been fully copied
  32. // back into array, copy the remaining elements from the
  33. // other temporary array back into the array
  34. while (i < lowHalf.length) {
  35. array[k] = lowHalf[i];
  36. k++;
  37. i++;
  38. }
  39. while (j < highHalf.length) {
  40. array[k] = highHalf[j];
  41. k++;
  42. j++;
  43. }
  44. };
  45. // Takes in an array and recursively merge sorts it
  46. var mergeSort = function(array, p, r) {
  47. if (r > p) {
  48. var q = Math.floor((r - p) / 2 + p);
  49. mergeSort(array, p, q);
  50. mergeSort(array, q + 1, r);
  51. merge(array, p, q, r);
  52. }
  53. };
  54. var array = [14, 7, 3, 12, 9, 11, 6, 2];
  55. mergeSort(array, 0, array.length-1);