How To Grailsort: The Definitive Guide

How To Grailsort: The Definitive Guide is a place to understand the Block Merge Sort Grailsort!

= Chapter 1: Improving Merge Sort = This is Merge Sort. >

It is a useful sort, being $$O(n \log n)$$ worst case time and stable. But, it has $$O(n)$$ space complexity.

Remember this figure, our main focus will be reducing it to $$O(1)$$, while retaining Merge Sort's other properties. now, how do we go about doing this?

Why does Merge Sort use $$O(n)$$ memory?

It uses the space to insert items, like Insertion sort. Insertion sort is not $$O(n)$$ though, because for it to insert its items, it must shift a ton of elements over using swaps or overwrites. The merge operation is more efficient because it takes advantage of the structure of the array: e.g there being 2 sorted halves, and because it dosen't need to shift elements over to insert its items, it has an external array for that.

So, how do we make the merge in-place?

With a buffer!

= Chapter 2: Merging with a scrolling buffer= The buffer is a place in the array dedicated to being a "scratchpad", or an auxiliary space. we cannot afford to overwrite the items in the buffer, so we swap them instead!

An Example
Here's an example:

Take the array [x x x x x x x x] [2 3 6 7] [1 4 4 5]. The x's are buffer items that we cannot overwrite, and we want to merge the second 2 blocks.

first, look at the first items of the blocks (not including x's): [2] and [1].

[1] is smaller, so we swap it with an x to the front.

[1 x x x x x x x] [2 3 6 7] [x 4 4 5]

look at the first items of the blocks (not including x's): [2] and [4].

[2] is smaller, so we swap it with an x to the next position.

[1 2 x x x x x x] [x 3 6 7] [x 4 4 5]

keep going...

[1 2 3 x x x x x] [x x 6 7] [x 4 4 5]

[1 2 3 4 x x x x] [x x 6 7] [x x 4 5]

[1 2 3 4 4 x x x] [x x 6 7] [x x x 5]

[1 2 3 4 4 5 x x] [x x 6 7] [x x x x]

At this point, one subarray is entirely filled with x's, so we swap the rest of the elements:

[1 2 3 4 4 5 6 7] [x x x x] [x x x x]

Notice how the x's all moved to the end, and we can now merge another pair of blocks:

[1 2 3 4 4 5 6 7] [x x x x x x x x] [? ? ? ?] [? ? ? ?]

The scrolling buffer
We can keep going on like this until we get to the end.

The process can be done backwards (just use the last x instead of the first), merging until we get to the start. The aptly named scrolling buffer is scrolling through the array, merging as it goes.

The buffer is scrambled by this process, though. so the buffer must consist of distinct values, because as long as they are distinct we can recover their order later.

An animated example where the buffer is blue and the blocks to be merged are green: --->

Grailsort actually uses a half-size buffer, only the size of 1 subarray instead of 2. I won't get into why it works, but suffice it to say you can get away with using a buffer half as big to do the same work.

An Example:

[x x x x] [1 4 4 5] [2 3 6 7]

[1 x x x] [x 4 4 5] [2 3 6 7]

[1 2 x x] [x 4 4 5] [x 3 6 7]

[1 2 3 x] [x 4 4 5] [x x 6 7]

[1 2 3 4] [x x 4 5] [x x 6 7]

[1 2 3 4] [4 x x 5] [x x 6 7]

[1 2 3 4] [4 5 x x] [x x 6 7]

[1 2 3 4 4 5 6 7] [x x x x]

=Chapter 3: Collecting The Keys= The first step of Grailsort is to collect $${2^{\lceil \frac {\log_2 n} {2} \rceil}} + {\lfloor 2^{-{\lceil \frac {\log_2 n} {2} \rceil}}n \rfloor} $$ unique keys, to form the buffer. the buffer consists of 2 regions: the Scrolling Buffer and the Key Buffer. The scrolling buffer is placed after the Key Buffer and is $${2^{\lceil \frac {\log_2 n} {2} \rceil}} $$ elements in length. The key buffer is placed in front and consists of the rest of the unique keys.

If there are less unique keys than say, 4, we must resort to a different sorting algorithm, as it will become impossible to efficiently tag and sort blocks with the Grailsort method.

If there are more than 4, we can use stategy 2, smaller / no scrolling buffer and larger blocks.

The process
We can collect keys by maintaining a sorted region of unique keys, so we can see if a new key is unique by binary-searching for it. If the binary search fails, we can insert it into its correct spot in the key-buffer. if the search succeds, we rotate the key-buffer so that the next key to be examined is always right in front of the sorted region.

After that is done, we rotate the sorted region to where it belongs.

Example
Take the list [13 2 12 3 4 8 15 1 11 10 7 5 14 9 6 16], and we want to collect 8 keys.

The first element is always unique, so we can put it in the key buffer.

[13] [2 12 3 4 8 15 1 11 10 7 5 14 9 6 16]

Examine the second element (the first is always unique), [2]. Search for it in the buffer [13]. The search fails, so insertion sort it into its correct place.

[2 13] [12 3 4 8 15 1 11 10 7 5 14 9 6 16]

Examine the third element, [12]. Search for it in the buffer [2 13]. The search fails, so insertion sort it into its correct place.

[2 12 13] [3 4 8 15 1 11 10 7 5 14 9 6 16]

keep going...

[2 3 12 13] [4 8 15 1 11 10 7 5 14 9 6 16]

[2 3 4 12 13] [8 15 1 11 10 7 5 14 9 6 16]

[2 3 4 8 12 13] [15 1 11 10 7 5 14 9 6 16]

[2 3 4 8 12 13 15] [1 11 10 7 5 14 9 6 16]

[1 2 3 4 8 12 13 15] [11 10 7 5 14 9 6 16]

we have now collected 8 keys, so we are done.

we can split the buffer into the scrolling and key buffers. both are length 4 in this case.

[1 2 3 4] [8 12 13 15] [11 10 7 5 14 9 6 16]

How to collect when there are duplicate keys
Let's try it with few uniques.

The list [14 8 16 2 8 16 2 6 10 12 2 2 6 12 2 2] has few unique keys. let's see what grailsort does here.

The first element is always unique, so we can put it in the key buffer.

[14] [8 16 2 8 16 2 6 10 12 2 2 6 12 2 2]

keep going...

[8 14] [16 2 8 16 2 6 10 12 2 2 6 12 2 2]

[8 14 16] [2 8 16 2 6 10 12 2 2 6 12 2 2]

[2 8 14 16] [8 16 2 6 10 12 2 2 6 12 2 2]

Here. the search for [8] succeeds, so we must rotate the buffer.

[8] [2 8 14 16] [16 2 6 10 12 2 2 6 12 2 2]

The search for [16] also succeeds, so we must rotate the buffer again.

[8 16] [2 8 14 16] [2 6 10 12 2 2 6 12 2 2]

[8 16 2] [2 8 14 16] [6 10 12 2 2 6 12 2 2]

[8 16 2] [2 6 8 14 16] [10 12 2 2 6 12 2 2]

[8 16 2] [2 6 8 10 14 16] [12 2 2 6 12 2 2]

Two uniques are found.

[8 16 2] [2 6 8 10 12 14 16] [2 2 6 12 2 2]

[8 16 2 2] [2 6 8 10 12 14 16] [2 6 12 2 2]

[8 16 2 2 2] [2 6 8 10 12 14 16] [6 12 2 2]

[8 16 2 2 2 6] [2 6 8 10 12 14 16] [12 2 2]

[8 16 2 2 2 6 12] [2 6 8 10 12 14 16] [2 2]

Another unique.

[8 16 2 2 2 6 12 2] [2 6 8 10 12 14 16] [2]

[8 16 2 2 2 6 12 2 2] [2 6 8 10 12 14 16]

We now rotate the buffer to the front.

[2 6 8 10 12 14 16] [8 16 2 2 2 6 12 2 2]

We could not find 8 keys, but we can still use strategy 2.

Animation
An animation of the collect keys procedure. collected keys in blue -->

= Chapter 4: Build Blocks = Now that we have a scrolling buffer of size $${2^{\lceil \frac {\log_2 n} {2} \rceil}} $$, we can build blocks, or sorted subarrays, two times that size. The process is as follows:

first, we do a full size scrolling-buffer-merge using the last 2 items of the full scrolling buffer. for example:

[x x x x x x x x] [1 4 8 19 3 0 2 7]

[x x x x x x] [1 4] [8 19] [0 3] [2 7] [x x]

then, we do a half-size scrolling-buffer-merge with the next 2 elements of the full scrolling buffer.

then, we do a half-size scrolling-buffer-merge with the next 4 elements of the full scrolling buffer.

and so on until the buffer runs out, at which point the entire buffer is at the end. we can do one last backwards scrolling-buffer-merge using the entirety of the buffer at the end.

after this, the block selection phase begins.

Animation
Animation of the build blocks procedure --> TBA

= Chapter 5: Block selection and merging = Now that our scrolling buffer has run out of room to merge, we must break down the runs into chunks of the scrolling buffer's size. we will call these blocks. Now we sort the blocks with selection sort by their first element. As selection sort is not stable, we use our Key Buffer to force stability in the same way as indices would. Finally, we use the scrolling buffer to merge adjacent blocks until it has got to the end and the whole thing is merged.

However, there is a problem. If the left block has elements left after the comparing part of the merge, we cannot drop them there because that would not merge the segments correctly. Instead, [insert explanation of buffer rewinds here].

= Chapter 6: The uniques problem =