merge sort

MergeSort(js)

Typescript

Part 1 – The split

Originally, we recurse into a function, along with a base case.

Printing the parameter first will count down from 3.

When we hit our terminating case of zero at (1), we return, and execution from the recursion continues downwards at (2).
As each function pops off from the stack, we log the index again. This time we go 1, 2, and finally back to 3.

Thus display is:

3 2 1 0 1 2 3

In Mergesort, the idea is that there is a total of n elements. We use splitting in half in order to cut down everything into single elements. Each cut is the same concept behind going down one level in a binary tree. We half all the elements.

In order to reach single elements, the running time is the height of the tree, or (log n) splits.

And in order to go into each halved section to split some more, we use recursion.

Let’s look at how MergeSort does this split:

in code:

The output is:

haha, hoho, hehe, hihi, alloy, Zoy

Part 2 – The Merge

Printing the words out is our first step, and we’ve used recursion. Now we need to compare each word and sort everything.

When we get to haha, we return an array with “haha”. Same with “hoho”. As we recursive back down the stack one step, we now have two arrays. We need to compare everything in these two arrays and sort them into a new array.

  1. We first create a n space ‘result’ array that is the total length of the arrays.
  2. We do this so that we will sort each element of these two arrays and put them into our result array.
  3. We do this until one of the arrays finish.
  4. Then we just tack on whatever is left from the array that still has untouched elements

Full code

Each level represents one split. And we have a total of (log n) split.
At every level, we do a comparison for every element. Thus our running time for the sort is O(n log n).

The standard merge sort on an array is not an in-place algorithm, since it requires O(N) additional space to perform the merge. During merge, we create a result array in order to carry the left and right array. This is where O(n) additional space comes from:

However, there exists variants which offer lower additional space requirement.

It is stable because it keeps the ordering intact from the input array.

Say we have [‘a’, ‘a’, ‘x’, ‘z’]. We split until we get single elements.

[‘a’, ‘a’] and [‘x’, ‘z’]

[‘a’] [‘a’] [‘x’] [‘z’]

We compare ‘a’ and ‘a’.

This is where it is stable because we always keep the 1st ‘a’ before the 2nd ‘a’ in the results array, which requires O(n) Auxiliary space, during the merge.

We place the leftmost ‘a’ first. Then we put the right side ‘a’ next into the result array.

Say we have another sample data where [‘a’, ‘z’, ‘m’,’b’, ‘a’, ‘c’] and we start recursing via mid splits:

[‘a’, ‘z’, ‘m’] [‘b’, ‘a’, ‘c’]
[‘a’, ‘z’] [‘m’] [‘b’, ‘a’] [‘c’]
[‘a’] [‘z’] [‘m’] [‘b’] [‘a’] [‘c’]

Now we star the merge process:

[‘a’, ‘z’] [‘m’] [‘a’, ‘b’] [‘c’]
[‘a’, ‘m’, ‘z’] [‘a’, ‘b’, ‘c’]

As you can see at this part, we do the final merge and because ‘a’ === ‘a’, we always insert the left-side ‘a’ inside of our results array first. Then we place the right-side ‘a’. Thus, keeping it stable.

Full Source (Typescript)

Javascript version

In-Place: The standard merge sort on an array is not an in-place algorithm, since it requires O(N) additional space to perform the merge. There exists variants which offer lower additional space requirement.

Stable: yes. It keeps the ordering intact from the input array.

http://www.codenlearn.com/2011/10/simple-merge-sort.html

Merge Sort has 2 main parts. The 1st part is the double recursion. The 2nd part is the Merge itself.

1) double recursion

2 recursion calls is executed, one after the other. Usually, in a single recursion, we draw a stack with the recursion and its parameter, and calculate it that way.

However, with two recursions, we simply have more content on the stack in our drawing.

For example, say we have an array of integers, with index 0 to 10.

mergeSort says:

So we call mergeSort recursively twice, with different parameters. The way to cleanly calculate them is to
write out the parameters, see if the left < right condition holds. If it does, then write result for the 'center' variable. Evaluate the 2 mergeSort calls and the merge Call. You will then clearly see the order of execution:

Let’s take a look at how the cursive call operates

Say we have [‘a’, ‘b’, ‘c’, ‘d’, ‘e’]

So we first operate on partition(0, 4).
start = 0, end = 4
[‘a’, ‘b’, ‘c’, ‘d’, ‘e’]

We try to find the floor mid point index:
(0+4)/2 = 2, mid value is ‘c’

Now we have:

partition(0, 2) or partition([‘a’, ‘b’, ‘c’])
partition(3,4) or partition([‘d’, ‘e’])

Let’s work on the first one:

partition([‘a’, ‘b’, ‘c’])
floor mid point is (0+2)/2 = 1 [0, 1] mid value is ‘b’

Now we have:
partition(0, 1) or partition([‘a’, ‘b’])
partition(2,2) or partition([‘c’])

We try to find mid point index:
(0+1)/2 = floor (0.5) = 0

Now we have:

partition(0, 0) or partition([‘a’]) returns ‘a’
partition(1, 1) or partition([‘b’]). returns ‘b’

going up the recursion, partition([‘c’]) returns c.

Great.. so now we have values a, b, c. and finished [‘a’, ‘b’, ‘c’] at the top.

Now we need to process

partition([‘d’, ‘e’])

find mid point index (3+4)/2 = floor(3.5) = 3.

So we get

partition(3,3) returns ‘d’
and partition(4,4) returns ‘e’

2) The merging itself

The merging itself is quite easy.

  • Use a while loop, and compare the elements of both arrays IFF the cur for both arrays have not reached the end.
  • Whichever is smaller, goes into the result array.
  • Copy over leftover elements from the left array.
  • Copy over leftover elements from the right array.

Running Time

Running time for the double recursion

Average and Worst time:

O(n log n)

How?

The log n comes from the fact that merge sort is recursive in a binary way.

4 items, takes 2 divisions to get to the base items.
8 items, takes 3 divisions to get to the base items.

…if you keep going like this, you’ll see that its logarithmic in nature.

Running time for the merge

We are dividing the array along the recursive path.

So length n1 and n2 keeps changing and the complexity of merge routine/method at each call.

It is ‘n’ on the first call to recursion (leftArray of size n/2 and rightArray of size n/2)

It is ‘n/2’ on the second call to recursion. (we merge two n/4 arrays)

It is ‘n/4’ on the third call to recursion. (we merge two n/8 arrays)
……. and so on …..

till we merge just 2 items ( leftArray of size 1 and rightArray of size 1) and base case there is just one item, so no need to merge.

As you can see the first call to recursion is the only time the entire array is merged together at each level, a total of ‘n’ operations take place in the merge routine.

Hence that is why its O( n log n )