In-situ Merge
The problem, given array arr[0..N) where section A, arr[0..B), is sorted and section B, arr[B..N), is sorted, is to rearrange the contents of arr[0..N) so that arr[0..N) is sorted and to do so efficiently, i.e., in O(N)-time using only O(1)-space.
...sec.A:sorted...|...sec.B:sorted... B N -- state of arr[0..N) at time t=0 --
The following description and implementation of a solution are based on the paper of Mannila and Ukkonen [MU84]. The aim is to give the simplest description and the simplest implementation running in linear-time and using O(1)-space; the reader may spot some places where optimisations are possible to reduce the "constant factor". Plainly the problem cannot be solved in less than linear time in general because it may be necessary to move every element of the array.
1. Some useful routines
There is some notation and some operations that it is convenient to define for later use:
- arr[i..j) stands for a[i], ..., a[j-1].
- |arr[i..j)| = j-i.
- swap_ij(i, j):
- |arr[i..j)| = j-i.
- temp=arr[i]; arr[i]=arr[j]; arr[j]=temp; also known as arr[i]:=:arr[j]
- swap_ijk(i, j, k):
- arr[i..j) :=: arr[j..k)
one method [MU84] is reverse arr[i..j); reverse arr[j..k); reverse arr[i..k); but we can do better [click]. - swap_nij(n, i, j):
- arr[i..i+n) :=: arr[j..j+n)
- sort(i, j):
- sort arr[i..j)
A simple quadratic-time sorting algorithm is good enough because we only sort two parts of arr each of size O(√N), although we might use a faster sort for the sake of honour (maybe heap-sort but not quick-sort because that would use more than O(1)-space). - smallMerge(i, j, k, ws):
- merge sorted blocks arr[i..j) and arr[j..k)
using workspace arr[ws..ws+j-i)
The contents of the workspace must be preserved but may be permuted. The solution is firstly to swap the contents of arr[i..j) with the contents of the workspace, and then to merge workspace and arr[j..k) into arr[i..k) swapping an element of arr[i..k) with one from the workspace when the latter is chosen. - nextBblock(j):
- return the first k>B s.t. arr[k]>arr[j] or 'N' if there is no such k. If j is the end of the current A-block At, arr[B..nextBblock(j)) is Bt, the B-block that is to be merged with At.
2. General Strategy
If the array is "small" say less 16 elements just sort it using any passable sort algorithm. Otherwise, if section A is small say less than eight elements, copy section A from arr into a new (bounded) workspace and merge section B and the workspace back into arr.
Otherwise, the approach [MU84] is to merge blocks from A with blocks from B. The A-blocks are all of size ABS=⌊√|A|⌋, except possibly the first one A0 which has ABS+(|A| mod ABS) elements. Note that there must be at least three A-blocks and that the first is less than twice the size of the other A-blocks. The B-blocks, see nextBblock(.), can be of various sizes possibly from zero to |B| elements.
As the algorithm progresses, arr[0..N) is in three sections; the first section grows and the other two sections shrink. The first section is done with, except that its first ABS elements can be used as workspace and their contents may be permuted. The second section, C, contains the remaining A-blocks but although each individual A-block is sorted, collectively they can be (i) out of order and (ii) frame-shifted so that the first block can be split. The third section is what remains of section B.
..done..|..sec.C..|..sec.B.. C B N -- state at time t≥1 --
3. Getting Started
The first A-block A0 may be larger than the others. It is conveniently in the right place. The corresponding B-block, B0 is found – see nextBblock(.). Swap B0 with the rest of C to bring B0 adjacent to A0.
--A0-- -B0- |aaaaaaxxxxxxxx|bbbbyyyyyyyyyy C B N aaaaaabbbb|xxxxxxxx|yyyyyyyyyy ---------- C B N
Advance C and B. Now use smallMerge(...) to merge A0 and B0 using the start of C, which must be big enough, as workspace and finally sort the workspace (it's size is <2×ABS).
4. Iterations
The remaining A-blocks are all of size ABS and each one is sorted, but they can be collectively out of order and frame-shifted within C so At has to be looked for and found. Mannila and Ukkonen suggest that
- "this can be done by searching [for] the block with the smallest last element." [MU84 p.205]
- but this only works if section A does not contain duplicate values and is not sufficient; in general it is necessary to look at both the first and last elements – consider blocks
- '5,6,7,8', '4,4,4,4' and '1,2,3,4', and blocks
'5,6,7,8', '1,2,3,4' and '1,1,1,1'. - Note that the values in one block cannot fully "embrace" the values in another block (other than trivially, e.g., '4,4,4,4' and '4,4,4,4') because section A was sorted.
The A-blocks start at positions of the form C+(((j−1)×ABS−frameShift)mod (B−C)) and end at positions of the form C+j×ABS−1−frameShift, for j≥1. The next A-block, At, can be found by looking for the block with the smallest start- or(!) end-value (that is enough, e.g., consider C = '3,4,5,6; 2,2,2,2; 1,1,2,2; 2,2,3,3').
At is brought to the front of C. If At is split because of frame-shifting its head (at the end of C) is swapped with the elements next to its tail (at the start of C). The two parts of At are then swapped, making it sorted.
....|AAApqrstuvaa|... --- -- ....|AAApqrstuvaa|... >> << ....|AAAaarstuvpq|... >>><< ....|aaAAArstuvpq|...
Otherwise At is not the first block in C. If At is the second block in C and the first block is not split then swap them. If the first block is split, swap its tail with the tail of At and then swap the two parts of At.
....|tuvaaAAApqrs|... >>> <<< ....|AAAaatuvpqrs|... >>><< ....|aaAAAtuvpqrs|...
If At is neither of the first two blocks in C swap it with the second block and then proceed as before.
When At is at the start of C, locate the corresponding B-block Bt. If Bt is no longer than the rest of C, swap Bt with part of C to bring Bt adjacent to At; this will change the frame-shift unless |Bt| is a multiple of ABS. If Bt is longer than the rest of C swap all of the latter with Bt. When At and Bt are adjacent smallMerge them using the first ABS elements of the array, arr[0..ABS), as workspace.
The process is repeated until C becomes empty at which point the first ABS elements of the arr, which have been used for workspace, must be sorted and we are done.
5. Time and Space
It can be seen that, other than the given array, the algorithm [MU84] uses a few scalar variables, and a small bounded workspace in the case that section A of the array is small, i.e., it uses O(1)-space.
Locating the next A-block is performed O(√|A|) times and each requires O(√|A|) comparisons, O(|A|)-time in total. Two blocks of size O(√|A|) are sorted but even if a slow quadratic sort is used this takes O(|A|)-time. Apart from that, each element of the given array is moved at most a fixed number of times so the algorithm runs in O(N)-time, i.e., linear time.
6. Summary
The in-situ merge problem restricted to O(1)-space and O(N)-time is highly constrained and solutions are tricky with algorithmic steps being chosen like moves in a chess puzzle. Are solutions to this problem of any practical use (e.g., in merge-sort) compared to the well-known O(N)-space and -time merges given that modern computers have huge memories? It is hard to say but just maybe when you consider multi-level memory caches and working-set size.
7. References
- [Kro69] M. A. Kronrod,
An optimal ordering algorithm without a field of operation,
Dokl. Akad. Nauk USSR, 186(6), pp.1256-1258,
[mi.mathnet.ru/eng/dan34705],
1969.
- [MU84] H. Mannila & E. Ukkonen, A simple linear-time algorithm for in-situ merging, Inf. Proc. Lett., 18(4), pp.203-208, doi:10.1016/0020-0190(84)90112-1 1984.
- [HL88] B.-C. Huang & M. A. Langston, Practical in-place merging, Comm. of the ACM, 31(3), pp.348-352, doi:10.1145/42392.42403, 1988.
- [MU84] H. Mannila & E. Ukkonen, A simple linear-time algorithm for in-situ merging, Inf. Proc. Lett., 18(4), pp.203-208, doi:10.1016/0020-0190(84)90112-1 1984.
- Also pdf@[www]['18].
- [HL92] B.-C. Huang & M. A. Langston, Fast stable merging and sorting in constant extra space, The Computer Jrnl, 35(6), pp.643-650, doi:10.1093/comjnl/35.6.643, 1992.
- Also pdf@[www]['18].