text:
# An In-Place Sorting with 𝑂(𝑛log𝑛) Comparisons and 𝑂(𝑛) Moves

## 1 . Introduction

From the very beginnings of computer science, sorting is one of the most fundamental problems, of great practical and theoretical importance. Virtually in every field of computer science there are problems that have the sorting of a set of objects as a primary step toward solution. (For early history of sorting, see \[9, Sect. 5.5\]). It is well-known that a comparison-based algorithm must perform, in the worst case, at least $`\mathrm{log}n!n\mathrm{log}nn\mathrm{log}en\mathrm{log}n1.443n`$ comparisons to sort an array consisting of $`n`$ elements. (All logarithms throughout this paper are to the base $`2`$, unless otherwise stated explicitly). By , the corresponding lower bound for element moves is $`3/2n`$.

Concerning upper bounds for the number of comparisons, already the plain version of mergesort gets closely to the optimum, with at most $`n\mathrm{log}nn+1`$ comparisons. However, this algorithm needs also an auxiliary array for storing $`n`$ elements, it is not an in-place algorithm. That is, it does not work with only a constant auxiliary storage, besides the data stored in the input array. In-place algorithms play an important role, because they maximize the size of data that can be processed in the main memory without an access, during the computation, to a secondary storage device.

The rich history of comparisons-storage family of sorting algorithms, using $`O(n\mathrm{log}n)`$ comparisons and, at the same time, $`O(1)`$ auxiliary storage, begins with a binary-search version of insertsort. This algorithm uses less than $`\mathrm{log}n!+n`$ comparisons, only a single storage location for putting elements aside, and only $`O(1)`$ index variables, of $`\mathrm{log}n`$ bits each, for pointing to the input array. Unfortunately, the algorithm performs $`\mathrm{\Omega }(n^2)`$ element moves, which makes it unacceptably slow, as $`n`$ increases.

The heapsort  was the first in-place sorting algorithm with a total running time bounded by $`O(n\mathrm{log}n)`$ in the worst case. More precisely, it uses less than $`2n\mathrm{log}n`$ comparisons with the same $`O(1)`$ storage requirements as insertsort, but only $`n\mathrm{log}n+O(n)`$ moves, if the moves are organized a little bit carefully. Since then, many cloned versions of heapsort have been developed; the two most important ones are bottom-up-heapsort  and a $`\mathrm{log}^{}`$-variant . Both these variants use not only the same number of moves as the standard heapsort, but even exactly the same sequence of element moves for each input. (See also the procedure “shiftdown” in ). However, they differ in the number of comparisons. Though bottom-up variant uses only $`3/2n\mathrm{log}n+O(n)`$ comparisons, its upper bound for the average case is even more important; with $`n\mathrm{log}n+O(n)`$ comparisons, it is one of the most efficient in-place sorting algorithms. The $`\mathrm{log}^{}`$-variant is slightly less efficient in an average, but it guarantees less than $`n\mathrm{log}n+n\mathrm{log}^{}n`$ comparisons in the worst case. For a more detailed analysis, see also .

Then in-place variants of a $`k`$-way mergesort came to the scene , with at most $`n\mathrm{log}n+O(n)`$ comparisons, $`O(1)`$ auxiliary storage, and $`\epsilon n\mathrm{log}n+O(n)`$ moves. Instead of merging only $`2`$ blocks, $`k`$ sorted blocks are merged together at the same time. Here $`k`$ denotes an arbitrarily large, but fixed, integer constant, and $`\epsilon >0`$ an arbitrarily small, but fixed, real constant. Except for the first extracted element in each $`k`$-tuple of blocks, the smallest element is found with $`\mathrm{log}k`$ comparisons, if $`k`$ is a power of two, since the $`k`$ currently leftmost elements of the respective blocks are organized into a selection tree. Though $`\mathrm{log}k`$ is more than one comparison required in the standard $`2`$-way merging, the number of merging sweeps across the array comes down to $`\mathrm{log}n/\mathrm{log}k`$, so the number of comparisons is almost unchanged. As an additional bonus, the number of element moves is reduced if, instead of elements, only pointers to elements are swapped in the selection tree. By the use of some other tricks, the algorithm is made in-place and the size of auxiliary storage is reduced to $`O(1)`$. The early implementation of this algorithm, having so promising upper bounds, turned out to be unacceptably slow. It was observed that operations with indices representing the current state of the selection tree became a bottleneck of the program. Fortunately, the state of a selection tree with a constant number of leaves can be represented implicitly, without swapping indices. This indicates that even by summing comparisons and moves we do not get the whole truth, the arithmetic operations with indices are also important.

The $`k`$-way variant has been generalized to a $`(\mathrm{log}n/\mathrm{log}\mathrm{log}n)`$-way in-place mergesort . This algorithm uses $`n\mathrm{log}n+O(n\mathrm{log}\mathrm{log}n)`$ comparisons, $`O(1)`$ auxiliary storage, and only $`O(n\mathrm{log}n/\mathrm{log}\mathrm{log}n)`$ element moves. Since $`k`$ is no longer a constant here, the information about the selection tree is compressed, among others, into bits of $`(\mathrm{log}n)`$-bit index variables by complicated bitwise operations, which increases, among others, the number of arithmetic operations. Therefore, the algorithm is mainly of theoretical interest; it is the first member of the comparisons-storage family breaking the bound $`\mathrm{\Omega }(n\mathrm{log}n)`$ for the number of moves.

The transports-storage family of algorithms, sorting with $`O(n)`$ element moves and $`O(1)`$ auxiliary storage, is not so numerous. The first algorithm of this type is selectsort, which is a natural counterpart of insertsort. Carefully implemented, it sorts with at most $`2n1`$ moves, a single location for putting one element aside, and $`O(1)`$ index variables. Unfortunately, it performs also $`\mathrm{\Omega }(n^2)`$ comparisons.

As shown in , $`O(n^2)`$ comparisons and $`O(1)`$ indices suffice for reduction of the number of moves to the lower bound $`3/2n`$.

Another improvement is a generalized heapsort : It is based on a heap in which internal nodes have $`n^{1/k}`$ children, for a fixed integer $`k`$. The corresponding heap tree is thus of constant height, which results in an algorithm sorting with $`O(n)`$ moves, $`O(1)`$ storage, and $`O(n^{1+\epsilon })`$ comparisons.

Finally, consider the comparisons-transports family, sorting with $`O(n\mathrm{log}n)`$ comparisons and $`O(n)`$ element moves. The first member is a so-called tablesort . We use any algorithm with $`O(n\mathrm{log}n)`$ comparisons but, instead of elements, we move only indices pointing to the elements. When each element’s final position has been determined, we transport all elements to their destinations in linear time. However, this algorithm requires $`\mathrm{\Omega }(n)`$ auxiliary indices.

The storage requirements have been reduced to $`O(n^\epsilon )`$ by a variant of samplesort . The same result can also be obtained by the in-place variant of the $`k`$-way mergesort , mentioned above, if $`k=n^\epsilon `$. This reduces the number of merging sweeps down to a constant, which results in $`O(n\mathrm{log}n)`$ comparisons and $`O(n)`$ element moves. Such modification is no longer in-place, it uses $`O(n^\epsilon )`$ auxiliary indices to represent a selection tree. We leave the details to the reader.

So far, there was no known algorithm sorting, in the worst case, with $`O(n\mathrm{log}n)`$ comparisons, $`O(n)`$ moves, $`O(1)`$ auxiliary storage, and, at the same time, $`O(n\mathrm{log}n)`$ arithmetic operations.

This ultimate goal has only been achieved in the average case . In the worst case, the algorithm uses $`\mathrm{\Omega }(n^2)`$ comparisons but, for a randomly chosen permutation of input elements, the probability of this worst case scenario is negligible.

It was generally conjectured, for many years, that an algorithm matching simultaneously the asymptotic lower bounds on all above computational resources does not exist. For example, in , it was proved that the algorithm with $`O(n^{1+\epsilon })`$ comparisons using generalized heaps is optimal among a certain restricted family of in-place sorting algorithms performing $`O(n)`$ moves. It was hoped that, by generalizing from a restricted computational model to all comparison-based algorithms, we could get a higher trade-off among comparisons, moves, and storage.

1.1. Our result. The result we shall present in this paper contradicts the above conjectures and closes a long-standing open problem. We shall exhibit the first sorting algorithm of the type comparisons-transports-storage. Our algorithm operates in-place, with at most $`2n\mathrm{log}n+o(n\mathrm{log}n)`$ element comparisons and $`(13+\epsilon )n`$ element moves in the worst case, for each $`n1`$. Here $`\epsilon >0`$ denotes an arbitrarily small, but fixed, real constant. The number of auxiliary arithmetic operations with indices is bounded by $`O(n\mathrm{log}n)`$. We can slightly reduce the number of moves, to $`(12+\epsilon )n`$, in a modified version that uses $`6n\mathrm{log}n+o(n\mathrm{log}n)`$ comparisons.

The algorithm was born as a union of the ideas contained in two independent technical reports, . We believe that, besides the theoretical breakthrough achieved by its analysis, the algorithm can also be of practical interest, because of its simplicity.

1.2. Algorithm in a nutshell. Using an evenly distributed sample $`a_1,\mathrm{},a_f`$ of size $`\mathrm{\Theta }(n/(\mathrm{log}n)^4)`$, split the elements into segments $`\sigma _0,\sigma _1,\mathrm{},\sigma _f`$, of length $`\mathrm{\Theta }((\mathrm{log}n)^4)`$ each, so that elements in $`\sigma _k`$ satisfy $`a_kaa_{k+1}`$. The sorted array is obtained by forming $`\sigma _0^{},a_1,\sigma _1^{},\mathrm{},a_f,\sigma _f^{}`$, where $`\sigma _k^{}`$ denotes $`\sigma _k`$ in sorted order. To sort $`\sigma _k`$, use a modified heapsort, with internal nodes having $`\mathrm{\Theta }((\mathrm{log}n)^{4/5})`$ sons, which results in a constant number of moves per each element extracted from the heap.

Since an evenly distributed sample is hard to find, it grows dynamically; when some $`\sigma _k`$ becomes too large, halve it into two segments of equal length, and insert the median in the sample. To minimize moves required for insertions in the sample, it is sparsely distributed in a block of size $`\mathrm{\Theta }(n/(\mathrm{log}n)^3)`$, not losing advantage of a quick binary search. A local density of elements is eliminated by redistributing the sample more evenly, which does not happen “too often.” To avoid the corresponding segment movement, only pointers connecting $`a_k`$’s with $`\sigma _k`$’s are moved, the segments stay motionless in a separate workspace.

However, we do not have a buffer of size $`3n`$, required for the sample and the segments, nor $`P\mathrm{\Theta }(n/(\mathrm{log}n)^2)`$ bits, for pointers. The bits are “created” at the very beginning by a modified heapsort, collecting the smallest and the largest $`P`$ elements to blocks $`\mathrm{\Pi }_\mathrm{L}`$ and $`\mathrm{\Pi }_\mathrm{R}`$, which leaves a block $`𝒜^{}`$ in between. Then the $`j`$th bit can be encoded by swapping the $`j`$th element in $`\mathrm{\Pi }_\mathrm{L}`$ with the $`j`$th element in $`\mathrm{\Pi }_\mathrm{R}`$.

To “create” a buffer for sorting the block $`𝒜^{}`$ of length $`n^{}`$, select the element $`b^{^{}}`$ of rank $`n^{}/4`$ and partition $`𝒜^{}`$ into blocks $`A_<`$ and $`B_{}`$, using $`b^{^{}}`$ as a pivot. Then sort $`A_<`$, using $`B_{}`$ as an empty buffer. (We can test if a given location contains a buffer element, by a single comparison with $`b^{^{}}`$. Before an “active” element is moved, one buffer element escapes to the current location of the hole). After sorting $`A_<`$ we iterate, focusing on $`B_{}`$ as a new block $`𝒜^{}`$. After $`O(\mathrm{log}n)`$ iterations, we are done.

## 2 . Sorting with an Additional Memory

Before presenting our in-place algorithm, we shall concentrate on a simpler task. We are going to sort a given contiguous block $`A`$, consisting of $`m`$ elements, using only $`O(m\mathrm{log}m)`$ comparisons and $`O(m)`$ element moves. As some additional resources, we are given a buffer memory, of size at least $`3m1`$, that can be used as a temporary workspace, and a pointer memory, capable of containing at least $`4m/(\mathrm{log}m)^2`$ bits.

To let the elements move, we also have a hole, that is, one location, the content of which can be modified without destroying any element. An assignment $`a_j:=a_i`$ transports not only one element from the location $`i`$ to $`j`$, but also the hole from $`j`$ to $`i`$. At the very beginning, the hole is in a single extra location, besides the given input array.

2.1. Buffer memory. The buffer memory forms a separate contiguous block $`B`$, initially consisting of at least $`3m1`$ buffer elements. All buffer elements are greater than or equal to a given buffer separator $`b^{^{}}`$, placed in an extra location, while all elements in $`A`$ are strictly smaller than $`b^{^{}}`$. During the computation, the elements of $`A`$ and $`B`$ are mixed up. However, by a single comparison with $`b^{^{}}`$, we can test whether any given location contains a buffer element, or an active element, a subject of sorting, placed originally in $`A`$.

The buffer memory $`B`$ consists of two parts. First, there is a low level segment memory, a sequence of segments allocated dynamically from the right end of $`B`$ and growing to the left, as the computation demands. All allocated segments are of the same fixed length. Second, there is a fixed high level frame memory, placed at the left end of $`B`$.

2.2. Structure of the segment memory. All segments are of a fixed length $`s`$, where

$$s=\{\begin{array}{c}(\mathrm{log}m)^4\text{}\hfill \\ (\mathrm{log}m)^4+1\hfill \end{array}\text{ so that }s\text{ is odd}.$$
(1)
During the computation, the number of active segments never exceeds $`s_\mathrm{\#}`$, defined by

$$s_\mathrm{\#}=2m/s2m/(\mathrm{log}m)^4,$$
(2)
and hence the size of workspace reserved for the segment memory is bounded by

$$S=s_\mathrm{\#}s2m.$$
(3)
Here we assume that $`m`$ is “sufficiently large,” such that $`sm`$, and hence $`s_\mathrm{\#}2`$. We shall later discuss how to handle a block $`A`$ that is “short.”

Initially, all segments are free, containing buffer elements only. The algorithm keeps the starting position of the last segment that has been allocated in a global index variable $`\stackrel{}{s}`$. Initially, $`\stackrel{}{s}`$ points to the right end of the buffer memory $`B`$. To allocate a new segment, the procedure simply performs the operation $`\stackrel{}{s}:=\stackrel{}{s}s`$, and returns the new value of $`\stackrel{}{s}`$ as the starting position of the new segment. Immediately after allocation, some $`s/2`$ active elements (smaller than $`b^{^{}}`$) are transported to the first $`s/2`$ positions of the new segment. The corresponding buffer elements are saved in the locations released by the active elements. From this point forward, the segment becomes active.

In general, the structure of an active segment is $`c_1\mathrm{}c_hb_{h+1}\mathrm{}b_s`$, where $`c_1\mathrm{}c_h`$ are active elements stored in the segment, while $`b_{h+1}\mathrm{}b_s`$ are some buffer elements. The value of $`h`$ is kept between $`s/2`$ and $`s1`$, so that at least one half (roughly) of elements in each active segment is active, and still there is a room for storing one more active element. Neither $`c_1\mathrm{}c_h`$ nor $`b_{h+1}\mathrm{}b_s`$ are sorted. In addition, the algorithm does not keep any information about the boundary $`h`$ separating active and buffer elements, if the segment is not being manipulated at the present moment. However, since all active elements are strictly smaller than $`b^{^{}}`$ and all buffer elements are greater than or equal to $`b^{^{}}`$, we can quickly determine the number of active elements in any given segment, using a binary search with $`b^{^{}}`$ over the $`s`$ locations of the segment, which costs only $`1+\mathrm{log}sO(\mathrm{log}\mathrm{log}m)`$ comparisons, by (1).

2.3. Structure of the frame memory. The frame memory, placed at the left end of $`B`$, consists of $`r_\mathrm{\#}`$ so-called frame blocks, each of length $`r`$, where

$$\begin{array}{ccccc}\hfill r& =& 1+\mathrm{log}(2m/s)\hfill & 2+\mathrm{log}(2m/8)\hfill & =\mathrm{log}m,\hfill \\ \hfill r_\mathrm{\#}& =& 2^{r1}=2^{\mathrm{log}(2m/s)}\hfill & 22m/s\hfill & 4m/(\mathrm{log}m)^4,\hfill \end{array}$$
(4)
using (1) and $`m4`$. That is, the frame memory is of total length

$$R=r_\mathrm{\#}r4m/(\mathrm{log}m)^3.$$
(5)
Using (3) and $`m4`$, we get that the total space requirements for the segment and frame memories do not exceed the size of the buffer $`B`$, since $`R+S4m/(\mathrm{log}m)^3+2m3m1`$.

A frame block is either free, containing buffer elements only, or it is active, containing some active elements followed by some buffer elements. Initially, all frame blocks are free. During the computation, active frame blocks are concentrated in a contiguous left part of the frame, followed by some free frame blocks in the right part. However, there are some important differences from the segment memory structure:

First, the active elements, forming a left part of a frame block, are in sorted order. So are the active frame blocks, forming a left part of the frame memory. More precisely, let $`a_1,a_2,\mathrm{},a_f`$ denote the sequence of all active elements stored in the frame memory, obtained by reading active elements from left to right, ignoring buffer elements and frame block boundaries. Then $`a_1,a_2,\mathrm{},a_f`$ is a sorted sequence of elements. Consequently, a subsequence of these, stored in the first (leftmost) positions of active frame blocks, denoted here by $`a_{i_1},a_{i_2},\mathrm{},a_{i_g}`$, must also be sorted. Here $`f`$ denotes the total number of active elements in the frame, while $`g`$ the number of active frame blocks, at the given moment. Similarly, $`a_{i_j}a_{i_j+1}a_{i_j+2}\mathrm{}a_{i_{j+1}1}`$, the sequence of active elements stored in the $`j`$th frame block, is also sorted.

Second, the number of active elements in an active frame block can range between $`1`$ and $`r1`$. That is, we keep room for potential storing of one more active element in each active frame block, but we do not care about a sparse distribution of active elements in the frame. The only restriction follows from the fact that there are no free blocks in between some active blocks.

2.4. Relationship between the frame and segments. Each active element in the frame memory, i.e., each of the elements $`a_1,a_2,\mathrm{},a_f`$, has an associated segment $`\sigma _1,\sigma _2,\mathrm{},\sigma _f`$ in the segment memory. The segment $`\sigma _k`$, for $`k`$ ranging between $`1`$ and $`f`$, contains some active elements satisfying $`a_kaa_{k+1}`$, taken from $`A`$ and stored in the structure so far. The active elements satisfying $`a_fa`$ are stored in $`\sigma _f`$, similarly, those satisfying $`aa_1`$ are stored in a special segment $`\sigma _0`$. Note that the segment $`\sigma _0`$ has no “parent” in the sequence $`a_1,a_2,\mathrm{},a_f`$, that is, no frame element to be associated with. Chronologically, $`\sigma _0`$ is the first active segment that has been allocated. If $`f=0`$, i.e., no active elements have been stored in the frame yet, all active elements are transported from $`A`$ to $`\sigma _0`$.

Note also that (in order to keep the number of active elements in active segments balanced) we do allow some elements equal to $`a_k`$ be stored both in $`\sigma _{k1}`$ and in $`\sigma _k`$. In general, we may even have $`a_k=a_{k+1}=\mathrm{}=a_k^{}`$, for some $`k<k^{}`$. Then elements equal to $`a_k`$ may be found in any of the segments $`\sigma _{k1},\sigma _k,\mathrm{},\sigma _k^{}`$. However, the algorithm tries to store each “new” active element $`a`$, coming from $`A`$, in the leftmost segment that can be used at the moment, i.e., it searches for $`k`$ satisfying $`a_k<aa_{k+1}`$.

Recall that we also maintain the invariant that each active segment contains at least $`s/2`$ active elements. Thus, if the frame contains $`f`$ active elements at the given moment, namely, $`a_1,a_2,\mathrm{},a_f`$, for some $`f1`$, the total number of active elements, stored both in the frame and the segments $`\sigma _0,\sigma _1,\sigma _2,\mathrm{},\sigma _f`$, is at least $`f+(f+1)s/2`$. Now, using the fact that $`s`$ is odd, by (1), we get that this number is at least $`f+(f+1)(s/21/2)=(f+1)s/2+(f/21/2)(f+1)s/2`$. However, the total number of all active elements is exactly equal to $`m`$, which gives $`m(f+1)s/2`$, and hence also $`f+12m/s`$. But $`f+1`$, the number of active segments, is an integer number, which gives that $`f+12m/s`$. Therefore, using (2) and (4),

$$\begin{array}{ccc}\hfill f+1& & 2m/s=s_\mathrm{\#},\hfill \\ \hfill f& & 2m/s2^{\mathrm{log}(2m/s)}=r_\mathrm{\#}.\hfill \end{array}$$
(6)
(The argument has used the assumption that $`f1`$. However, (6) is trivial for $`f=0`$, since $`r_\mathrm{\#}s_\mathrm{\#}2`$, if $`m`$ is sufficiently large).

As a consequence, we get that $`f+1`$, the number of active segments, does not exceed $`s_\mathrm{\#}`$, the capacity of the segment memory. Second, $`f`$, the number of active elements in the frame, will never exceed $`r_\mathrm{\#}`$, the total number of blocks in the frame, and hence there is enough room to store all active frame elements, even if each active frame block contained only a single element of the sequence $`a_1,a_2,\mathrm{},a_f`$.

2.5. Structure of the pointer memory. The relative order of active frame elements in the sequence $`a_1,a_2,\mathrm{},a_f`$ does not correspond to the chronological order, in which the segments $`\sigma _0,\sigma _1,\sigma _2,\mathrm{},\sigma _f`$ are allocated in the segment memory. Therefore, with each element position in the frame, we associate a pointer to the starting position of corresponding segment. More precisely, if the frame is viewed as a single contiguous zone of elements $`x_1\mathrm{}x_R`$ (ignoring boundaries between the frame blocks), then the corresponding zone of pointers is $`\pi _1\mathrm{}\pi _R`$. If, for some $`\mathrm{}`$, the element $`x_{\mathrm{}}`$ is a buffer element, then $`\pi _{\mathrm{}}=0`$, which represents a NIL pointer. Conversely, if $`x_{\mathrm{}}`$ is an active element belonging to the sequence $`a_1,a_2,\mathrm{},a_f`$, then the value of $`\pi _{\mathrm{}}`$ represents the starting position of the segment associated with $`x_{\mathrm{}}`$. (The pointer $`\pi _0`$ to the segment $`\sigma _0`$, having no “parent” in the frame, is stored separately, in a global index variable).

Since there are at most $`s_\mathrm{\#}`$ segments, all of equal length, a pointer to a segment can be represented by an integer value ranging between $`0`$ and $`s_\mathrm{\#}=2m/sm/2`$, using (2). Thus, a single pointer can be represented by a block of $`p`$ bits, where

$$p=1+\mathrm{log}s_\mathrm{\#}\mathrm{log}m.$$
(7)
The number of pointers is clearly equal to $`R`$, the total size of the frame. Therefore,

$$p_\mathrm{\#}=R.$$

Thus, the pointer memory can be viewed as a contiguous array consisting of $`p_\mathrm{\#}`$ bit blocks, of $`p`$ bits each, and hence, by (5), its total length is at most

$$P=p_\mathrm{\#}p=Rp4m/(\mathrm{log}m)^2,$$
(8)
using also the fact that $`P`$ must be an integer number.

Since an in-place algorithm can store only a limited amount of information in index variables, the pointer memory is actually simulated by two separate contiguous blocks $`\mathrm{\Pi }_\mathrm{L}`$ and $`\mathrm{\Pi }_\mathrm{R}`$, each containing at least $`4m/(\mathrm{log}m)^2`$ elements. Initially, $`\mathrm{\Pi }_\mathrm{L}`$ and $`\mathrm{\Pi }_\mathrm{R}`$ are sorted, and the largest (rightmost) element in $`\mathrm{\Pi }_\mathrm{L}`$ is strictly smaller than the smallest (leftmost) element in $`\mathrm{\Pi }_\mathrm{R}`$. This allows us to encode the value of the $`j`$th bit, for any $`j`$ ranging between $`1`$ and $`4m/(\mathrm{log}m)^2`$, by swapping the $`j`$th element of $`\mathrm{\Pi }_\mathrm{L}`$ with the $`j`$th element of $`\mathrm{\Pi }_\mathrm{R}`$. Testing the value of the $`j`$th bit is thus equivalent to comparing the relative order of the corresponding elements in $`\mathrm{\Pi }_\mathrm{L}`$ and $`\mathrm{\Pi }_\mathrm{R}`$, which costs only a single comparison. Setting a single bit value requires a single comparison and, optionally, a single swap of two elements, i.e., $`3`$ element moves. The initial distribution of elements in $`\mathrm{\Pi }_\mathrm{L}`$ and $`\mathrm{\Pi }_\mathrm{R}`$ represents all $`4m/(\mathrm{log}m)^2`$ bits cleared to zero.

2.6. Inserting elements in the structure. The procedure sorting the block $`A`$ works in two phases. In the first phase, the procedure takes, one after another, all $`m`$ active elements from $`A`$ and inserts them in the structure described above. The procedure also saves some buffer elements from $`B`$, and keeps the structure “balanced.” In the second phase, all active elements are transported back to $`A`$, this time in sorted order.

For each active element $`a`$ in $`A`$, we find a segment, among $`\sigma _0,\sigma _1,\sigma _2,\mathrm{},\sigma _f`$, where this element should go.

First, by the use of a binary search with the given element $`a`$ over $`a_{i_2},\mathrm{},a_{i_g}`$, that is, over the leftmost locations in the active frame blocks, find the “proper” frame block for the element $`a`$, i.e., the index $`j`$ satisfying $`a_{i_j}<aa_{i_{j+1}}`$. Note that the element $`a_{i_1}`$ is excluded from the range of the binary search. If $`aa_{i_2}`$, the binary search will return $`j=1`$, i.e., the first frame block. Similarly, for $`a_{i_g}<a`$, the binary search returns $`j=g`$, i.e., the last frame block. If $`g<2`$, we can go directly to the first (and only) active frame block without using any binary search, that is, $`j:=1`$.

Second, by the use of a binary search with the given element $`a`$ over the $`r`$ locations in the $`j`$th active frame block, find the “proper” active frame element for the element $`a`$, i.e., the index $`k`$ satisfying $`a_k<aa_{k+1}`$. Note that, since $`a_{i_j}<aa_{i_{j+1}}`$, the elements $`a_k`$ and $`a_{k+1}`$ are between $`a_{i_j}`$ and $`a_{i_{j+1}}`$ in the sequence $`a_1,a_2,\mathrm{},a_f`$ of all frame elements, not excluding the possibility that $`a_{i_j}=a_k`$, and/or $`a_{k+1}=a_{i_{j+1}}`$. Recall that the $`j`$th active frame block begins with the active elements $`a_{i_j}a_{i_j+1}a_{i_j+2}\mathrm{}a_{i_{j+1}1}`$, followed by some buffer elements, to fill up the room, so that the length of the block is exactly equal to $`r`$. These buffer elements are not sorted, however, they are all greater than or equal to $`b^{^{}}`$, the smallest buffer element. On the other hand, the element $`a`$, being active, is strictly smaller than $`b^{^{}}`$. This allows us to use the binary search with the given $`a`$ in the standard way, which returns the index $`k`$ satisfying $`a_k<aa_{k+1}`$. For $`a_{i_{j+1}1}<a`$, the binary search returns correctly $`k=i_{j+1}1`$. If $`j=1`$, that is, if we are in the first frame block, the binary search may end up with $`k=0`$, indicating that $`aa_1=a_{i_1}`$.

Third, let the active frame element $`a_k`$, satisfying $`a_k<aa_{k+1}`$, be placed in a position $`\mathrm{}`$ of the frame memory, that is, $`a_k=x_{\mathrm{}}`$. (For $`k=0`$, we take $`\mathrm{}:=0`$). Then read the information from $`\pi _{\mathrm{}}`$ in the pointer memory and compute the starting position of the segment $`\sigma _k`$. This segment contains elements ranging between $`a_k`$ and $`a_{k+1}`$. If $`k=0`$, i.e., the element $`a`$ should go to $`\sigma _0`$, the starting position of the segment is obtained from a separate global index variable.

Fourth, by the use of a binary search with the buffer separator $`b^{^{}}`$ over the $`s`$ locations in the current segment, find the boundary $`h`$ dividing the segment into two parts, namely, $`c_1\mathrm{}c_h`$, the active elements stored in the segment, and $`b_{h+1}\mathrm{}b_s`$, some buffer elements, filling up the room.

Fifth, save the buffer element $`b_{h+1}`$ aside, to the current location of the hole, and, after that, store the given element $`a`$ in the segment. If $`h+1<s`$, we are ready to insert the next element from $`A`$. However, if $`h+1=s`$, the current segment cannot absorb any more elements. Therefore, if the segment has become full, we call a procedure “rebalancing” the structure before trying to store the next element. This procedure will be described later, in Sect. 2.

The above process is repeated until all $`m`$ active elements have been inserted in the structure.

Initially, the procedure allocates the segment $`\sigma _0`$, and stores the first $`s1`$ active elements directly in $`\sigma _0`$, without travelling via the frame. The number of moves for these elements is the same as in the standard case, i.e., two moves per each inserted element.

Let us now determine the standard cost of inserting a single element. The binary search looking for a proper frame block inspects a range consisting of $`g1<r_\mathrm{\#}`$ elements, and hence it performs at most $`1+\mathrm{log}r_\mathrm{\#}\mathrm{log}m`$ comparisons, by (4). The second binary search, looking for a proper active element within the given frame block, inspects a range of $`r`$ elements, performing at most $`1+\mathrm{log}rO(\mathrm{log}\mathrm{log}m)`$ comparisons, using (4). Reading the value encoded in the pointer $`\pi _{\mathrm{}}`$ requires $`p\mathrm{log}m`$ element comparisons, by (7). The binary search with $`b^{^{}}`$ over the $`s`$ locations in the current segment uses $`1+\mathrm{log}sO(\mathrm{log}\mathrm{log}m)`$ comparisons, by (1). Finally, saving one buffer element and transporting the element $`a`$ to the current segment can be performed with $`2`$ element moves. However, these costs do not include rebalancing. Since $`m`$ elements are inserted this way, we get:

###### Lemma 1

If we exclude the costs of rebalancing, inserting $`m`$ elements in the structure requires $`2m\mathrm{log}m+O(m\mathrm{log}\mathrm{log}m)`$ comparisons and $`2m`$ moves.

2.7. Extracting in sorted order — frame level. In the second phase, the active elements are transported back to $`A`$, in sorted order. Let $`f_\mathrm{m}`$ denote the maximal value of $`f`$, corresponding to the number of active elements in the frame at the moment when the last active element has been stored in the structure. Thus, the frame memory contains the sorted sequence of active elements $`a_1,a_2,\mathrm{},a_{f_\mathrm{m}}`$, intertwined with some buffer elements, so the total size of the frame is $`R`$, consisting of elements $`x_1\mathrm{}x_R`$. Then we have active elements in the segments $`\sigma _0,\sigma _1,\sigma _2,\mathrm{},\sigma _{f_\mathrm{m}}`$, with $`\sigma _k`$ containing active elements that satisfy $`a_kaa_{k+1}`$. Thus, to produce the sorted order of all active elements, it is sufficient to move, back to $`A`$, the sequence $`\sigma _0^{},a_1,\sigma _1^{},a_2,\sigma _2^{},\mathrm{},a_{f_\mathrm{m}},\sigma _{f_\mathrm{m}}^{}`$, where $`\sigma _k^{}`$ denotes the block of sorted active elements contained in $`\sigma _k`$.

The procedure begins with moving the block $`\sigma _0^{}`$ to $`A`$. (We shall return to the problem of sorting a given segment $`\sigma _k`$ below, in Sect. 2).

Then, in a loop iterated for $`\mathrm{}=1,\mathrm{},R`$, check whether $`x_{\mathrm{}}`$ is an active element. This requires only a single comparison, comparing $`x_{\mathrm{}}`$ with $`b^{^{}}`$. If $`x_{\mathrm{}}`$ is a buffer element, it is skipped, we can go to the next element in the frame.

If $`x_{\mathrm{}}`$ is an active element, i.e., $`x_{\mathrm{}}=a_k`$, for some $`k`$, the procedure saves the leftmost buffer element, not moved yet from the output block $`A`$, in the current location of the hole and, after that, moves $`x_{\mathrm{}}=a_k`$ to $`A`$. (The first free position in $`A`$, i.e., the position of the leftmost buffer element, is kept in a separate global index variable, and incremented each time a new active element is transported back to $`A`$). Then we read the value encoded in the pointer $`\pi _{\mathrm{}}`$ and compute the starting position of the segment $`\sigma _k`$. After that, we move all active elements contained in $`\sigma _k`$ to $`A`$, in sorted order, by the procedure presented in Sect. 2.

Before showing how the segment $`\sigma _k`$ can be sorted, let us derive computational costs of the above procedure, not including the cost of sorting $`\sigma _k`$. Testing whether $`x_{\mathrm{}}`$ is an active element, for $`\mathrm{}=1,\mathrm{},R`$, requires $`RO(m/(\mathrm{log}m)^3)`$ comparisons, by (5). Transporting $`x_{\mathrm{}}=a_k`$ to $`A`$ requires only $`2f_\mathrm{m}`$ element moves in total, since only active elements are moved. This gives $`2f_\mathrm{m}2r_\mathrm{\#}O(m/(\mathrm{log}m)^4)`$ element moves, by (6) and (4). Reading the values of $`f_\mathrm{m}`$ pointers, of length $`p`$ bits each, can be done with $`f_\mathrm{m}pr_\mathrm{\#}pO(m/(\mathrm{log}m)^3)`$ comparisons, using (6), (4), and (7). Summing up, we have:

###### Lemma 2

If we exclude the costs of sorting the segments, extracting in sorted order requires $`O(m/(\mathrm{log}m)^3)`$ comparisons and $`O(m/(\mathrm{log}m)^4)`$ moves.

2.8. Extracting in sorted order — segment level. Now we can describe the routine extracting, in sorted order, all active elements contained in the given segment $`\sigma _k`$. Let $`h_k`$ denote the number of active elements in $`\sigma _k`$. Clearly, $`h_ks(\mathrm{log}m)^4+1`$, using (1). Initially, the routine determines the value of $`h_k`$ by the use of a binary search with $`b^{^{}}`$ over the $`s`$ locations of the segment. This costs $`1+\mathrm{log}sO(\mathrm{log}\mathrm{log}m)`$ comparisons.

After that, the routine uses a generalized version of heapsort, which in turn uses a modified heap-like structure, with

$$t=(\mathrm{log}m)^{4/5}$$

root nodes (instead of a single root node), and with internal nodes having $`t`$ sons (instead of two sons). More precisely, we organize $`c_1\mathrm{}c_{h_k}`$, the active elements contained in the segment, into the implicit structure with the following properties:

First, the father of the node $`c_e`$ is the node $`c_e^{}`$, where $`e^{}=(e1)/t`$, provided that $`e^{}1`$. If $`e^{}<1`$, then $`c_e`$ is one of the root nodes. This implies that the heap has $`t`$ roots, and that the sons of $`c_e`$ are the nodes $`c_{te+1},c_{te+2},\mathrm{},c_{te+t}`$. If, for some $`e`$ and $`d<t`$, we have $`te+d=h_k`$, the corresponding node $`c_e`$ has only $`d`$ sons, instead of $`t`$. A leaf is a node $`c_e`$ without any sons, that is, with $`teh_k`$.

Before passing further, note that the heap does not have more than five levels, since, by travelling to a root from $`c_{h_k}`$, we get

$$\begin{array}{cccc}\hfill h^{(1)}& =& (h_k1)/t\hfill & <h_k/t,\hfill \\ \hfill h^{(2)}& =& (h^{(1)}1)/t\hfill & <h_k/t^2,\hfill \\ \hfill h^{(3)}& =& (h^{(2)}1)/t\hfill & <h_k/t^3,\hfill \\ \hfill h^{(4)}& =& (h^{(3)}1)/t\hfill & <h_k/t^4,\hfill \\ \hfill h^{(5)}& =& (h^{(4)}1)/t\hfill & h^{(4)}/t1/t<h_k/t^51/ts/t^51/t^5.\hfill \end{array}$$

If we had $`1h^{(5)}`$, then $`1<s/t^51/t^5`$, and hence also $`t^5<s1`$. Now, using $`t=(\mathrm{log}m)^{4/5}`$ and $`s(\mathrm{log}m)^4+1`$, by (1), we would obtain $`(\mathrm{log}m)^{4/5}^5<(\mathrm{log}m)^4`$, which is a contradiction. To see this, note that, for each real $`x>0`$, $`x^{4/5}^5x^4`$. But $`x^{4/5}^5`$ is an integer number, and hence $`x^{4/5}^5x^4`$.

The second property of our heap is that, if a node contains an active element, then this element is not greater than any of its sons. Note that we do not care about sons of a node containing a buffer element. (Initially, there are no buffer elements in the heap. However, when some active elements have been extracted, buffer elements will fill up the holes).

This heap property is established in the standard way: For $`e=(h_k1)/t,\mathrm{},1`$, establish this property in the positions $`e,\mathrm{},h_k`$. This only requires to determine whether $`c_e`$ is not greater than the smallest of its sons and, if necessary, swap the smallest son with $`c_e`$. Processing a single node this way costs $`t`$ comparisons and $`3`$ element moves. After that, the heap property is re-established for the son just swapped in the same way. This may activate a further walk, up to some leaf.

Taking into account that there are $`h^{(1)}`$ nodes with paths of lengths $`1`$, $`2`$, $`3`$, or $`4`$ (starting from the given node and ending in a leaf), $`h^{(2)}`$ nodes with paths of lengths $`2`$, $`3`$, or $`4`$, $`h^{(3)}`$ nodes with paths of lengths $`3`$ or $`4`$, and $`h^{(4)}`$ nodes with paths of length $`4`$, we get that building the heap costs $`t_{i=1}^4h^{(i)}<2h_k`$ comparisons and $`3_{i=1}^4h^{(i)}<6h_k/(\mathrm{log}m)^{4/5}`$ moves.

After building the heap, the routine transports, $`h_k`$ times, the smallest element from the heap to the output block $`A`$. Here the moves are organized as follows. First, save the leftmost buffer element, not moved yet from $`A`$, in the current location of the hole. Then find the smallest element, placed in one of the $`t`$ roots, and move this element to $`A`$. After that, find the smallest element among the $`t`$ sons of this root, and move this element to the node corresponding to its father. Iterating this process at most five times, we end up with a hole in some leaf. Now, we are done. The hole in the leaf will be filled up by a buffer element in the future, as a side effect. (Usually, in the next iteration, extracting the next smallest element from the heap).

Thus, unlike in the standard version of heapsort, the size of the heap does not shrink but, rather, some new buffer elements are inserted into the heap structure, filling up the leaf holes. These buffer elements are then handled by the extracting routine in the standard way, as ordinary active elements. Since these elements may travel down, from the leaf level closer to the root level, a node containing a buffer element may have a son containing a smaller buffer element. This will do no harm, however, since each buffer element is strictly greater than any active element, because of the buffer separator $`b^{^{}}`$. Thus, no buffer element can be extracted from the heap as the smallest element in the first $`h_k`$ iterations, when the routine terminates.

Deriving the costs of the above routine is straightforward. The routine repeats $`h_k`$ iterations, performing each time at most $`5(t1)5(\mathrm{log}m)^{4/5}`$ comparisons and $`6`$ moves, since the heap has at most five levels. This gives $`h_k5(\mathrm{log}m)^{4/5}`$ comparisons and $`h_k6`$ moves.

Now we can sum the costs of sorting the segment $`\sigma _k`$. Determining the value of $`h_k`$ costs $`O(\mathrm{log}\mathrm{log}m)`$ comparisons. Building the heap costs at most $`2h_k`$ comparisons and $`6h_k/(\mathrm{log}m)^{4/5}`$ moves. Extracting active elements in sorted order costs $`h_k5(\mathrm{log}m)^{4/5}`$ comparisons and $`h_k6`$ moves. Summing up, we get $`h_kO((\mathrm{log}m)^{4/5})`$ comparisons and $`h_k(6/(\mathrm{log}m)^{4/5}+6)`$ moves.

To obtain the total cost of sorting all segments $`\sigma _0,\sigma _1,\sigma _2,\mathrm{},\sigma _{f_\mathrm{m}}`$, we use the fact that $`_{k=0}^{f_\mathrm{m}}h_km`$, since the number of active elements stored in the segments is bounded by the total number of active elements. Therefore, the sum over all segments results in the following upper bounds:

###### Lemma 3

Sorting all segments does not require more than $`O(m(\mathrm{log}m)^{4/5})`$ comparisons or $`6m+O(m/(\mathrm{log}m)^{4/5})`$ moves.

Alternatively, we could use the heap structure with parameter $`t=\mathrm{log}m`$. This results in a heap with four levels, instead of five (since $`x^4x^4`$, for each real $`x>0`$). This reduces the leading factor for the number of moves from $`6m`$ to $`5m`$. The price we pay is increasing the number of comparisons, from $`o(m\mathrm{log}m)`$ to $`4m\mathrm{log}m+O(m)`$. The detailed argument is very similar to the proof for $`t=(\mathrm{log}m)^{4/5}`$.

2.9. Rebalancing at the segment level. This procedure is activated by the routine of Sect. 2, inserting a new active element in the structure, when, for some $`k`$, the segment $`\sigma _k`$ has become full, having absorbed $`s`$ active elements.

At the moment of activation, some global index variable is pointing to the starting position of $`\sigma _k`$. The procedure also remembers $`\mathrm{}`$, the position of the associated active element $`a_k=x_{\mathrm{}}`$ in the frame memory, as well as $`j`$, the position of the frame block containing the element $`a_k`$. We shall call this block the current frame block. (If $`\sigma _k=\sigma _0`$, i.e., $`k=0`$, there is no associated element in the frame. Then $`\mathrm{}=0`$, but we still have the current frame block, namely, $`j=1`$). The above indices were computed when the latest active element was inserted in the structure.

First, by the use of a binary search with the buffer separator $`b^{^{}}`$ over the $`r`$ locations in the current frame block, find $`\mathrm{}^{}`$, the position of the leftmost buffer element in this block. We shall denote this element by $`b^{}`$. Recall that we maintain the invariant that each active frame block has a room for one more active element, and therefore it does contain at least one buffer element.

Second, find a median in the segment $`\sigma _k`$, i.e., an element $`a^{}`$ of rank $`s/2+1`$. Without loss of efficiency, the selection procedure will position $`a^{}`$ at the end of $`\sigma _k`$.

Third, the median $`a^{}`$ is inserted in the current frame block, one position to the right of $`a_k`$. The active elements lying in between $`a_k`$ and $`b^{}`$, that is, occupying locations $`x_{\mathrm{}+1}\mathrm{}x_\mathrm{}^{}1`$ in the frame memory, are shifted one position to the right. At the same time, $`b^{}`$ is saved from $`x_{\mathrm{}^{}}`$ to the location released by $`a^{}`$ at the end of the segment $`\sigma _k`$. (As a special case, if $`a_k`$ is the rightmost active element in the current frame block, only $`b^{}`$ and $`a^{}`$ are swapped. The same holds when $`\sigma _0`$ is rebalanced for the first time, with $`\mathrm{}=0`$ and $`\mathrm{}^{}=1`$). Since $`a^{}`$ has been picked from $`\sigma _k`$, it satisfies $`a_ka^{}a_{k+1}`$, and hence the sequence of active elements stored in the frame memory remains sorted.

Fourth, after shifting the active elements in the locations $`x_{\mathrm{}+1}\mathrm{}x_\mathrm{}^{}1`$ one position to the right, we have to shift the corresponding pointers $`\pi _{\mathrm{}+1}\mathrm{}\pi _\mathrm{}^{}1`$ as well, so the active elements remain connected with their segments. To move an integer pointer value from $`\pi _e`$ to $`\pi _{e+1}`$, we only have to read the value encoded in $`\pi _e`$ and, at the same time, clear $`\pi _e`$, and then to encode this value in $`\pi _{e+1}`$. Such transport of a pointer costs $`O(p)`$ comparisons and moves.

Fifth, we need to connect a new active element in the frame with a new segment. This concerns the element $`a^{}`$, now placed in $`x_{\mathrm{}+1}`$. Thus, we allocate a new segment $`\sigma ^{}`$ and encode its starting position in the pointer $`\pi _{\mathrm{}+1}`$.

Sixth, the full segment $`\sigma _k`$ is halved, that is, we place some $`s/2`$ active elements greater than or equal to $`a^{}`$ into the left part of $`\sigma ^{}`$ and collect the remaining $`s/2`$ active elements, smaller than or equal to $`a^{}`$, in the left part of the original segment $`\sigma _k`$. Since many elements may be equal to $`a^{}`$, we distribute such elements both to $`\sigma _k`$ and $`\sigma ^{}`$, so that their active parts are of equal lengths. This also requires to save $`s/2`$ buffer elements, placed originally in $`\sigma ^{}`$, to the locations released in $`\sigma _k`$. (We shall give more details below, in Sect. 2). The outcome of halving is that the active elements in $`\sigma _k`$ are split into two segments $`\sigma _k`$ and $`\sigma ^{}`$, satisfying $`a_kaa^{}`$ and $`a^{}aa_{k+1}`$, respectively.

Seventh, if there is still a room for storing one more active element in the current frame block, the structure has been rebalanced. We are done, ready to take the next element from $`A`$. However, if this block has become full, because of $`a^{}`$, the program control jumps to a routine rebalancing the frame level, described later, in Sect. 2.

Let us now derive the computational costs. The binary search, determining the position of the leftmost buffer element in the current frame block, inspects a range of $`r`$ elements, performing $`1+\mathrm{log}rO(\mathrm{log}\mathrm{log}m)`$ comparisons, by (4). Finding a median, in a segment of length $`s`$, requires only $`O(s)O((\mathrm{log}m)^4)`$ comparisons and $`\epsilon s\epsilon (2+(\mathrm{log}m)^4)`$ element moves, where $`\epsilon >0`$ is an arbitrarily small, but fixed, real constant, by  and (1). Rearranging the elements $`a^{}`$, $`b^{}`$, and $`x_{\mathrm{}+1}\mathrm{}x_\mathrm{}^{}1`$ in their locations can be done with at most $`r+2O(\mathrm{log}m)`$ moves, by (4). Shifting the pointers $`\pi _{\mathrm{}+1}\mathrm{}\pi _\mathrm{}^{}1`$ one position to the right costs $`O(rp)O((\mathrm{log}m)^2)`$ comparisons, by (4) and (7), together with the same number of moves. Encoding the starting position of a new segment in the pointer $`\pi _{\mathrm{}+1}`$ requires $`O(p)O(\mathrm{log}m)`$ element moves, by (7). Halving the active elements in $`\sigma _k`$ into two segments $`\sigma _k`$ and $`\sigma ^{}`$ requires only $`O(s)O((\mathrm{log}m)^4)`$ comparisons and $`3/2s3/2(2+(\mathrm{log}m)^4)`$ moves, using Lem. 5, displayed in Sect. 2 below, and (1).

By summing the bounds above, we get that a single activation of the procedure rebalancing a segment performs $`O((\mathrm{log}m)^4)`$ comparisons and $`(3/2+\epsilon )(\mathrm{log}m)^4`$ moves. Taking into account that each activation increases the number of active segments, that we start with one segment, namely, $`\sigma _0`$, and that we end up with $`f_\mathrm{m}+1`$ segments, we see that the number of activations is bounded by $`f_\mathrm{m}`$. This value is bounded by $`f_\mathrm{m}s_\mathrm{\#}2m/(\mathrm{log}m)^4`$, using (6) and (2). This gives:

###### Lemma 4

The total cost of keeping the segment level balanced is $`O(m)`$ comparisons and $`(3+\epsilon )m`$ moves, where $`\epsilon >0`$ is an arbitrarily small, but fixed, real constant.

2.10. Halving a segment. Here we describe a simple procedure for halving, needed in Sect. 2 above. We are given a segment $`\sigma _k`$ of size $`s`$, and a median $`a^{}`$, that is, an element of rank $`s/2+1`$, put aside. We want to place some $`s/2`$ active elements greater than or equal to $`a^{}`$ into the left part of another given segment $`\sigma ^{}`$, of size $`s`$ again, and collect the remaining $`s/2`$ elements smaller than or equal to $`a^{}`$ in the left part of $`\sigma _k`$. The first $`s/2`$ buffer elements of $`\sigma ^{}`$ must be saved.

In the first phase, with $`s1`$ comparisons and no moves, we count $`c^{}`$, the number of elements strictly smaller than $`a^{}`$, in $`\sigma _k`$. This gives us $`c=s/2c^{}`$, the number of elements equal to $`a^{}`$ that should remain in $`\sigma _k`$. This number will be required in the second phase, when each element $`a`$ of $`\sigma _k`$ is compared with $`a^{}`$ twice, using “$`a<a^{}`$ ” and “$`a>a^{}`$ ”. The elements strictly smaller than $`a^{}`$ and the first $`c`$ elements detected to be equal to $`a^{}`$ will be considered “small,” while the remaining equal elements and those strictly greater than $`a^{}`$ will be “large.” Each time an element $`a=a^{}`$ will be detected, the counter $`c`$ will be decreased by one, until it gets to zero. From then on, any “new” element $`a`$ will be considered “small” if and only if $`a<a^{}`$, and “large” otherwise.

In the second phase, the configurations of the segments are $`\sigma _k=A_1UB_1b^{}`$ and $`\sigma ^{}=A_2B_2`$, where $`A_1`$ and $`A_2`$ denote, respectively, the active elements of $`\sigma _k`$ found to be “small” or “large,” collected so far, $`B_1`$ the buffer elements moved from $`\sigma ^{}`$ to $`\sigma _k`$, $`B_2`$ the elements of $`\sigma ^{}`$ not moved yet, $`U`$ the elements of $`\sigma _k`$ not examined yet, and $`b^{}`$ a single buffer element, filling up the room. $`A_2`$ and $`B_1`$ are of equal length, not exceeding $`s/2`$. Initially, $`\sigma _k=Ub^{}`$, $`\sigma ^{}=B_2`$, with $`A_1,A_2,`$ and $`B_1`$ empty. The procedure also remembers the current position of the hole. (After the first iteration, the hole is always in the leftmost location of $`B_1`$).

The second phase proceeds in a loop, as follows. Using at most two comparisons, the rightmost element $`a`$ of $`U`$ is determined to be “small” or “large.” If $`a`$ is large, we save the leftmost element from $`B_2`$ in the current location of the hole and fill up the new hole in $`B_2`$ by $`a`$. Thus, $`A_2`$ and $`B_1`$ have been extended, while $`U`$ and $`B_2`$ have been reduced. If $`a`$ is small, we scan $`U`$ from left to right until we find the first element $`a^{}`$ that is large. All elements on the left of $`a^{}`$ become a part of $`A_1`$, without being moved. Since $`a`$ is a small element placed on the right of the position $`s/2`$, $`a^{}`$ must be found before we reach the position $`s/2+1`$, or else we would have more than $`s/2`$ small elements, which is a contradiction. Now we save the leftmost element from $`B_2`$ to the hole, fill up the hole in $`B_2`$ by $`a^{}`$, and move $`a`$ to the place released by $`a^{}`$. Then all necessary boundaries are updated.

This is repeated until we have transported exactly $`s/2`$ active large elements from $`\sigma _k`$ to $`\sigma ^{}`$. As a consequence, the remaining $`s/2`$ active elements of $`\sigma _k`$, placed on the left of $`B_1`$, must be all small, since the rank of $`a^{}`$ is $`s/2+1`$ and $`s`$ is odd, by (1).

Clearly, we have used at most $`3s`$ comparisons in total, and at most three moves per each large element moved from $`\sigma _k`$ to $`\sigma ^{}`$. This gives:

###### Lemma 5

Given a median $`a^{}`$, a segment of size $`s`$ can be halved with at most $`O(s)`$ comparisons and $`3/2s`$ moves.

2.11. Rebalancing at the frame level. This routine is activated by the procedure of Sect. 2, rebalancing a segment, when it finds out that, for some $`j`$, the $`j`$th frame block has become full, having absorbed $`r`$ active elements. As a side effect, the routine may increase the number of active blocks in the frame. The routine is based on a new variant of the well-known data structure (see ), used to maintain a set of elements in sorted order in a contiguous zone of memory.

For the purpose of keeping the frame memory balanced, the frame consisting of $`r_\mathrm{\#}`$ frame blocks is viewed, implicitly, as a complete binary tree with $`r_\mathrm{\#}=2^{r1}`$ leaves, and hence of (edge) height $`r1`$. We introduce the following numbering of levels: $`i=0`$ for the leaves, $`1`$ for their fathers, and so on, ending by $`i=r1`$ for the root. Each node of the tree is associated with a contiguous subarray of the frame blocks, and with a path leading to this node from the root, as follows.

The $`j`$th leaf, for any $`j`$ ranging between $`1`$ and $`2^{r1}`$, is associated with the $`j`$th frame block, i.e., with a subarray consisting of $`1=2^0`$ frame blocks, starting from the block position $`j`$. The corresponding path from the root to this leaf is represented by the number $`\stackrel{}{ȷ}=j1`$. It is easy to see that by reading the binary representation of $`\stackrel{}{ȷ}`$ from left to right (with leading zeros so that its length is $`r1`$) we get the branching sequence along this path; $`0`$ is interpreted as branching to the left, while $`1`$ as branching to the right.

Given a node $`v`$ at a level $`i`$, associated with a path number $`\stackrel{}{ȷ}`$ and with a subarray of length $`2^i`$ blocks, starting from a block position $`j`$, the father $`v^{}`$ of this node is associated with the path number $`\stackrel{}{ȷ}^{}=\stackrel{}{ȷ}/2`$, and with the subarray of length $`2^{i+1}`$, starting from the block position $`j^{}=j`$, if $`\stackrel{}{ȷ}`$ is even ($`v`$ is a left son of $`v^{}`$), but from $`j^{}=j2^i`$, if $`\stackrel{}{ȷ}`$ is odd (right son). Thus, the subarray for the father is obtained by concatenation of the two subarrays for its sons, while its path number by cutting off the last bit in the path number for any of its sons.

During the computation, the number of active elements in some local area of the frame may become too large. The purpose of rebalancing a subarray, associated with a node $`v`$ at a level $`i`$, for $`i>0`$, is to eliminate such local densities and redistribute active elements more evenly. More precisely, after rebalancing the subarray, the following two conditions will hold:

* The number of active elements, in any frame block belonging to the subarray associated with the given node $`v`$ at the level $`i`$, will not exceed the threshold $`\tau _i=ri`$.
* The frame memory will not contain any free blocks (without active elements) in between some active blocks.

Note that, if a node $`v`$ at a level $`i>0`$ is an ancestor of the $`j`$th leaf, the condition (i) ensures that the $`j`$th frame block is not full any longer. Neither is any other block within the subarray. Such redistribution of active elements is possible only if $`\alpha (v)`$, the total number of active elements in the subarray associated with $`v`$, is bounded by $`\alpha (v)\tau _i2^i`$. We say that the node $`v`$ overflows, if $`\alpha (v)>\tau _i2^i`$.

The condition (ii) is required only because of the procedure presented in Sect. 2, transporting active elements from the block $`A`$ to the structure. Recall that this procedure uses a binary search over the leftmost locations in the active frame blocks, and hence these blocks must form a contiguous zone.

Now we can describe the routine rebalancing the frame.

First, starting from the father of the frame block that is full, climb up and find the lowest ancestor $`v`$ that does not overflow, with $`\alpha (v)\tau _i2^i`$. The formulas for $`j`$ and $`\stackrel{}{ȷ}`$, presented above, give us a simple tool for computing the boundaries of the associated subarrays, along the path climbing towards the root. To compute the value of $`\alpha (v)`$, for the given ancestor $`v`$ at the given level $`i`$, simply scan all $`2^i`$ frame blocks forming the associated subarray and sum up the numbers of active elements in these blocks, using a binary search with the buffer separator $`b^{^{}}`$ over the $`r`$ locations in each block.

Second, move the $`\alpha (v)`$ active elements in the associated subarray of $`v`$ to the last $`\alpha (v)`$ locations. That is, processing all $`2^ir`$ locations in the subarray from the right to left, collect all elements smaller than $`b^{^{}}`$ to the right end. Before moving an active element from $`x_e`$ to $`x_e^{}`$, for some $`e<e^{}`$, the buffer element in the target position $`x_e^{}`$ is saved to the current location of the hole. Then move the associated pointer in the corresponding positions of the pointer memory, from $`\pi _e`$ to $`\pi _e^{}`$, by reading and clearing the bit value encoded in $`\pi _e`$ and encoding this value in $`\pi _e^{}`$.

Third, redistribute the $`\alpha (v)`$ active elements back, this time more evenly in the $`2^i`$ frame blocks of the subarray, moving also the pointers in the corresponding positions, as follows. Let $`\alpha _\mathrm{D}=\alpha (v)/2^i`$ and $`\alpha _\mathrm{M}=\alpha (v)mod2^i`$. Then put $`\alpha _\mathrm{D}+1`$ active elements in each of the first $`\alpha _\mathrm{M}`$ blocks, and $`\alpha _\mathrm{D}`$ active elements in each of the remaining $`2^i\alpha _\mathrm{M}`$ blocks. In each block, the active elements are concentrated in its left part.

Fourth, as a side effect of redistribution, the size of the active part in the frame memory may have been increased. This requires to update the value of $`g`$, the number of active frame blocks, kept in a separate global index variable. Let $`g^{}`$ be the block position of the rightmost frame block in the subarray of $`v`$. Then let $`g:=\mathrm{max}\{g,g^{}\}`$.

It should be pointed out that, for each leaf, the desired ancestor $`v`$ without overflow does exist. Using (6), (4), and $`\tau _i=ri`$, for the level $`i=r1`$, that is, for $`v`$ being the root node, we get $`\alpha (v)=fr_\mathrm{\#}=12^{r1}=\tau _{r1}2^{r1}`$, and hence at least the root node does not overflow. Therefore, in the first step, the loop climbing up towards the root must halt correctly.

Further, the redistribution of active elements, presented in the third step, is correct, since $`(\alpha _\mathrm{D}+1)\alpha _\mathrm{M}+\alpha _\mathrm{D}(2^i\alpha _\mathrm{M})=\alpha (v)`$. It is easy to see that the redistribution satisfies the condition (i) above, using the fact that the node $`v`$ does not overflow, and hence $`\alpha (v)\tau _i2^i`$. There are two cases to consider: For $`\alpha (v)\tau _i2^i1`$, we have $`\alpha _\mathrm{D}+1(\tau _i2^i1)/2^i+1(\tau _i1)+1=\tau _i`$, since $`\tau _i`$ is an integer. If $`\alpha (v)=\tau _i2^i`$, we get $`\alpha _\mathrm{M}=0`$, and hence all $`2^i`$ blocks are “remaining,” with only $`\alpha _\mathrm{D}`$ active elements in each. But here $`\alpha _\mathrm{D}=(\tau _i2^i)/2^i=\tau _i=\tau _i`$.

It is also easy to see that the redistribution satisfies the condition (ii). Since $`v`$ is the lowest ancestor that does not overflow, along the path from the full frame block towards the root, it must have a son that does overflow, with at least $`\tau _{i1}2^{i1}`$ active elements in its subarray. (As a special case, for $`i=1`$, we get $`\tau _02^0=(r0)1=r`$ active elements in the $`j`$th frame block that is full). The subarray of the son is a part of the subarray associated with $`v`$, and hence $`\alpha (v)\tau _{i1}2^{i1}22^{i1}`$, using the fact that $`i1r2`$. But then $`\alpha _\mathrm{D}=\alpha (v)/2^i1`$. This implies that each frame block in the subarray associated with $`v`$ contains at least one active element after redistribution, and hence the zone of active frame blocks will remain contiguous.

Consider now the cost of a single activation of the above routine, rebalancing a subarray for a node $`v`$ at a level $`i`$. Looking for the lowest ancestor without overflow requires to count the numbers of active elements in the associated subarrays along a path climbing up from a father of a leaf, for levels $`e=1,\mathrm{},i`$. In the $`e`$th level, $`2^e`$ blocks are examined, by a binary search over the $`r`$ locations of the block. By (4), this gives $`_{e=1}^i2^e(1+\mathrm{log}r)2^iO(\mathrm{log}\mathrm{log}m)`$ comparisons. The cost of the second step, collecting $`\alpha (v)`$ active elements to the right end, is $`2^ir`$ comparisons (one comparison with $`b^{^{}}`$ for each location in the subarray), plus $`\alpha (v)2+1`$ moves (two moves per each collected element). However, with each collected element, the corresponding pointer must also be transported, which gives additional $`\alpha (v)O(p)`$ comparisons and moves. Using $`\alpha (v)\tau _i2^ir2^i`$, together with (4) and (7), the cost of the second step can be bounded by $`2^iO(rp)2^iO((\mathrm{log}m)^2)`$ comparisons and moves. The same computational resources are sufficient in the third step, redistributing the same number of active elements back, but more evenly, together with their pointers. Again, this gives $`\alpha (v)O(p)`$ comparisons and moves, which can be bounded by $`2^iO((\mathrm{log}m)^2)`$. Finally, the fourth step does not require any element comparisons or moves, it just updates one index variable, in $`O(1)`$ time.

Summing up, the cost of a single activation is $`2^iO((\mathrm{log}m)^2)`$ comparisons and moves, for each node $`v`$ at the fixed level $`i>0`$. To get the total cost, we must take into account how frequently such rebalancing is activated.

When a rebalancing is activated, $`v`$ must have a son with at least $`\tau _{i1}2^{i1}`$ active elements, since $`v`$ is the lowest ancestor that does not overflow, along some path climbing up. Now, trace back the history of computation, to the moment when the entire subarray associated with $`v`$ was a subject of redistribution for the last time. This way we get a node $`v^{}`$, either an ancestor of $`v`$ or $`v`$ itself, at a level $`i^{}i`$, with the associated subarray containing the entire subarray for $`v`$. After the redistribution for $`v^{}`$, both sons of $`v`$ contained at most $`\tau _i^{}2^{i1}\tau _i2^{i1}`$ active elements. Thus, in the meantime, the number of active elements in one of the sons of $`v`$ has been increased by at least $`\tau _{i1}2^{i1}\tau _i2^{i1}=2^{i1}`$. Since other redistributions, taking place between the moments of rebalancing $`v^{}`$ and $`v`$, could not “import” any active elements to the subarray of $`v`$ from any other parts of the frame, the $`2^{i1}`$ additional active elements must have been inserted here. (See the procedure of Sect. 2, third step). Thus, there have to be at least $`2^{i1}`$ insertions in the associated subarray between any two redistributions for $`v`$. Note that, for the fixed level $`i`$, subarrays associated with different nodes $`v`$ do not overlap. Thus, we can charge the cost of each activation, for the given node $`v`$, to the $`2^{i1}`$ insertions preceding this activation in the given subarray, without charging the same insertion more than once. This gives $`2^iO((\mathrm{log}m)^2)/2^{i1}O((\mathrm{log}m)^2)`$ comparisons and moves, per a single insertion of an active element in the frame memory. Since, in the whole computation, there were only $`f_\mathrm{m}r_\mathrm{\#}O(m/(\mathrm{log}m)^4)`$ insertions, by (6) and (4), we get the cost $`O(m/(\mathrm{log}m)^2)`$ comparisons and moves, for rebalancing of all nodes at the fixed level $`i`$. By summing over all levels, using $`ir1\mathrm{log}m`$, by (4), we get the total cost:

###### Lemma 6

The total cost of keeping the frame memory balanced is $`O(m/\mathrm{log}m)`$ comparisons, together with the same number of moves.

2.12. Summary. By summing the bounds presented in Lems. 1 – 4 and 6 above, we get:

###### Theorem 7

The cost of sorting the given block $`A`$ of size $`m`$ is $`2m\mathrm{log}m+O(m(\mathrm{log}m)^{4/5})`$ comparisons and $`(11+\epsilon )m`$ moves, where $`\epsilon >0`$ is an arbitrarily small, but fixed, real constant, provided we can use additional buffer and pointer memories, of respective sizes $`3m1`$ and $`4m/(\mathrm{log}m)^2`$.

The algorithm presented above assumes that $`m`$ is “sufficiently large,” so that $`s`$, defined by (1), satisfies $`sm`$. This presupposition holds for each $`m>2^{16}=65536`$. Shorter blocks are handled in a different way, by the procedure described later, in Sect. 3. The bounds presented by Thm. 7 for the number of comparisons and moves will remain valid.

## 3 . In-Place Sorting

Now we can present an in-place algorithm sorting the given array $`𝒜`$ consisting of $`n`$ elements. If $`n2^{16}`$, the array is sorted directly, by the procedure of Sect. 3, described later. In the general case, for $`n>2^{16}`$, the task of the main program is to provide sufficiently large pointer and buffer memories for the procedure presented in Sect. 2.

3.1. Building a pointer memory. The size of the largest block ever sorted by the procedure of Sect. 2 will not exceed $`m=n/4`$. Using (8) and the fact that the function $`4x/(\mathrm{log}x)^2`$ is monotone increasing for $`x8`$, we see that the size of the pointer memory can be bounded by $`P=4(n/4)/(\mathrm{log}(n/4))^2=n/(\mathrm{log}(n/4))^2`$. This will suffice for all sorted blocks.

The pointer memory is built by collecting two contiguous blocks $`\mathrm{\Pi }_\mathrm{L}`$ and $`\mathrm{\Pi }_\mathrm{R}`$. The block $`\mathrm{\Pi }_\mathrm{L}`$, placed at the left end of $`𝒜`$, will contain the smallest $`P`$ elements of the array $`𝒜`$, while $`\mathrm{\Pi }_\mathrm{R}`$, placed at the right end, the largest $`P`$ elements.

The block $`\mathrm{\Pi }_\mathrm{R}`$ is created first, by the use of the heapsort with $`t`$ root nodes and internal nodes having $`t`$ sons. The detailed topology of edges connecting nodes in this kind of heap has been presented in Sect. 2, devoted to extracting sorted elements at the segment level.

However, there are some substantial differences from the generalized heapsort of Sect. 2. This time the branching degree is $`t=\mathrm{log}n`$. Therefore, the heap has $`q1+\mathrm{log}_tnO(\mathrm{log}n/\mathrm{log}\mathrm{log}n)`$ levels. Here we keep large elements at the root level, instead of small elements. That is, no node contains an element smaller than any of its sons. Unlike in Sect. 2, no buffer elements are used here to fill up the holes, the heap structure shrinks in the standard way, when the largest element is extracted.

The initial building of the heap structure is standard, and agrees with the heap building in Sect. 2. It is easy to see that, for a heap with $`n`$ elements, branching degree equal to $`t`$, and $`q`$ levels, the cost of the heap initialization can be bounded by $`t_{i=1}^{q1}n/t^i<nt/(t1)O(n)`$ comparisons and $`3_{i=1}^{q1}n/t^i<n3/(t1)O(n/\mathrm{log}n)`$ moves, using $`t\mathrm{log}n`$.

After building the heap, the routine extracts, $`P`$ times, the largest element from the heap in the standard way. That is, when the largest element is extracted, it replaces the element in the rightmost leaf, which in turn is inserted into the “proper” position along the so-called special path, starting from the position of the largest root (just being extracted) and branching always to the largest son.

The costs of the above routine are straightforward. The trajectory of the special path can be localized with $`q(t1)`$ comparisons, and the new position for the element in the rightmost leaf can be found by a binary search along this trajectory with $`1+\mathrm{log}q`$ comparisons. Summing up, an extraction of the largest element can be done with $`q(t1)+(1+\mathrm{log}q)`$ comparisons, together with $`q+2`$ moves. Using $`tO(\mathrm{log}n)`$ and $`qO(\mathrm{log}n/\mathrm{log}\mathrm{log}n)`$, we get, per a single extraction, at most $`O((\mathrm{log}n)^2/\mathrm{log}\mathrm{log}n)`$ comparisons, together with $`O(\mathrm{log}n/\mathrm{log}\mathrm{log}n)`$ moves.

If we let the above procedure run till the end, it would sort the entire array $`𝒜`$ in time $`O(n(\mathrm{log}n)^2/\mathrm{log}\mathrm{log}n)`$. However, the execution is aborted as soon as the largest $`P`$ elements are collected. Since $`PO(n/(\mathrm{log}n)^2)`$, the cost of building the heap becomes dominant, and hence the block $`\mathrm{\Pi }_\mathrm{R}`$ is created with $`O(n)`$ comparisons and $`O(n/\mathrm{log}n)`$ moves.

After $`\mathrm{\Pi }_\mathrm{R}`$, the block $`\mathrm{\Pi }_\mathrm{L}`$ is created in the same way, with the same computational needs of comparisons and moves. Instead of large elements, here we collect the smallest $`P`$ elements. In addition, since $`\mathrm{\Pi }_\mathrm{L}`$ should be created at the left end of $`𝒜`$, all indices are manipulated in a mirrorlike way, seeing the first position to the left of $`\mathrm{\Pi }_\mathrm{R}`$ as the beginning of the array.

###### Lemma 8

Building the pointer memory requires $`O(n)`$ comparisons and $`O(n/\mathrm{log}n)`$ moves.

Now the configuration of the array $`𝒜`$ has changed to $`\mathrm{\Pi }_\mathrm{L}𝒜^{}\mathrm{\Pi }_\mathrm{R}`$, where $`𝒜^{}`$ denotes the remaining elements, to be sorted. Before proceeding further, the algorithm verifies, with a single comparison, whether the largest (rightmost) element in $`\mathrm{\Pi }_\mathrm{L}`$ is strictly smaller than the smallest (leftmost) element in $`\mathrm{\Pi }_\mathrm{R}`$.

If this is not the case, all elements in $`𝒜^{}`$ must be equal to these two elements. Therefore, the algorithm terminates, the entire array $`𝒜`$ has already been sorted.

Conversely, if $`\mathrm{\Pi }_\mathrm{L}`$ and $`\mathrm{\Pi }_\mathrm{R}`$ pass the test above, they can be used to imitate a pointer memory consisting of $`P`$ bits.

3.2. Partition-based sorting. When the blocks $`\mathrm{\Pi }_\mathrm{L}`$ and $`\mathrm{\Pi }_\mathrm{R}`$ have been created, the zone $`𝒜^{}`$ is kept in the form $`𝒜_\mathrm{S}𝒜_\mathrm{U}`$, where $`𝒜_\mathrm{S}`$ and $`𝒜_\mathrm{U}`$ represent the sorted and unsorted parts of $`𝒜^{}`$, respectively. Each element in $`𝒜_\mathrm{S}`$ is strictly smaller than the smallest element of $`𝒜_\mathrm{U}`$. The routine described here is a partition-based loop. In the course of the $`i`$th iteration, the length of $`𝒜_\mathrm{U}`$ is $`n_i`$, with $`n_i<n_{i1}`$. Initially, for $`i=0`$, $`𝒜_\mathrm{S}`$ is empty, $`𝒜_\mathrm{U}=𝒜^{}`$, and $`n_0=n2P<n`$. The loop proceeds as follows.

First, find $`b^{^{}}`$, an element of rank $`n_i/4`$ in $`𝒜_\mathrm{U}`$. The selection procedure places this element at the right end of $`𝒜_\mathrm{U}`$, so the configuration of $`𝒜^{}`$ changes to $`𝒜_\mathrm{S}𝒜_\mathrm{U}^{}b^{^{}}`$. Here $`𝒜_\mathrm{U}^{}`$ denotes a mix of elements in $`𝒜_\mathrm{U}`$, of length $`n_i1`$.

Second, $`𝒜_\mathrm{U}^{}`$ is partitioned into two blocks $`A_<`$ and $`B_{}`$ consisting, respectively, of elements strictly smaller than $`b^{^{}}`$ and of those greater than or equal to $`b^{^{}}`$. The configuration of the array thus changes to $`𝒜_\mathrm{S}A_<B_{}b^{^{}}`$. The respective lengths of $`A_<`$ and $`B_{}`$ will be denoted here by $`n_{i,<}`$ and $`n_{i,}`$. Note that, even for a large block $`𝒜_\mathrm{U}`$, we may obtain a very short block $`A_<`$, since many elements may be equal to $`b^{^{}}`$. In fact, the block $`A_<`$ may even be empty, of length $`n_{i,<}=0`$.

Third, sort the block $`A_<`$ by the procedure described in Sect. 2, using some initial segments of $`\mathrm{\Pi }_\mathrm{L}`$ and $`\mathrm{\Pi }_\mathrm{R}`$ as a pointer memory and of $`B_{}`$ as a buffer memory, with $`b^{^{}}`$ as a buffer separator. This is possible, since $`b^{^{}}`$ has been selected as an element of rank $`n_i/4`$, and hence $`n_{i,<}n_i/41n_i/4`$, with $`n_{i,<}+n_{i,}+1=n_i`$. But the required size of buffer is only $`3n_{i,<}13/4n_i1=n_i1n_i/4n_i1n_{i,<}=n_{i,}`$. Therefore, the block $`B_{}`$ of length $`n_{i,}`$ is sufficiently long. Similarly, the required number of bits for pointers is $`4n_{i,<}/(\mathrm{log}n_{i,<})^24(n/4)/(\mathrm{log}(n/4))^2=P`$, and hence the pointer memory is also sufficiently large. (If $`n_{i,<}2^{16}`$, $`A_<`$ is sorted as a short block).

Fourth, restore the sorted order in $`\mathrm{\Pi }_\mathrm{L}`$ and $`\mathrm{\Pi }_\mathrm{R}`$, by clearing all bits of the pointer memory to zero. Among others, this is required because the procedure of Sect. 2 will also be used in subsequent iterations, when it assumes that all bits are initially cleared.

Fifth, after sorting $`A_<`$, the configuration of $`𝒜^{}`$ is $`𝒜_\mathrm{S}A_{<,\mathrm{S}}B_{}^{}b^{^{}}`$, where $`A_{<,\mathrm{S}}`$ denotes the sorted version of the block $`A_<`$ and $`B_{}^{}`$ a mixed up version of $`B_{}`$. Now put the first element in $`B_{}^{}`$ aside and move $`b^{^{}}`$ to the first position after $`A_{<,\mathrm{S}}`$. After that, collect all elements smaller than or equal to $`b^{^{}}`$ to the left part of $`B_{}^{}`$, processing also the element put aside. Since $`B_{}`$ did not contain elements strictly smaller than $`b^{^{}}`$, this actually partitions $`B_{}^{}`$ into two blocks $`A_=`$ and $`B_>`$ consisting, respectively, of elements equal to $`b^{^{}}`$ and of those strictly greater than $`b^{^{}}`$, of respective lengths $`n_{i,=}`$ and $`n_{i,>}`$. Clearly, $`n_{i,=}+n_{i,>}=n_{i,}`$. The configuration has changed to $`𝒜_\mathrm{S}A_{<,\mathrm{S}}b^{^{}}A_=B_>`$.

Sixth, observe that $`𝒜_\mathrm{S}A_{<,\mathrm{S}}b^{^{}}A_=`$ and $`B_>`$ can be viewed as “new” variants of blocks $`𝒜_\mathrm{S}`$ and $`𝒜_\mathrm{U}`$. Thus, we can start a new iteration, with $`B_>`$ as a new block $`𝒜_\mathrm{U}`$, of length $`n_{i+1}=n_{i,>}`$. The above process is iterated until the length of unsorted part drops to $`2^{16}`$, or below. This residue is then sorted as a short block, without using a buffer or pointers, which will be described later, in Sect. 3.

Now we can derive computational costs. First, recall that $`b^{^{}}`$ has been selected as an element of rank $`n_i/4`$, and hence $`n_{i+1}=n_{i,>}n_in_i/43/4n_i`$. Taking into account that $`n_0n`$, we get $`n_i(3/4)^in`$, for each $`i0`$. This gives that

$$\begin{array}{ccc}\hfill _{i=0}^1n_i& & 4n,\hfill \\ \hfill & & O(\mathrm{log}n),\hfill \end{array}$$
(9)
where $``$ denotes the number of iterations. Second, it is easy to see that

$$_{i=0}^1(n_{i,<}+1+n_{i,=})+n_{}n,$$
(10)
since, in different iterations, the final locations occupied by $`A_{<,\mathrm{S}}`$, $`b^{^{}}`$, and $`A_=`$, do not overlap. Here $`n_{}`$ denotes the length of the residual short block.

Let us now present the costs for the $`i`$th iteration. Selection of $`b^{^{}}`$, an element of the given rank in a block of length $`n_i`$, costs $`O(n_i)`$ comparisons and $`\epsilon n_i`$ moves, by . Partitioning of $`𝒜_\mathrm{U}^{}`$ into blocks $`A_<`$ and $`B_{}`$ can be done with $`n_i`$ comparisons and $`2n_{i,<}+1`$ moves, since the length of $`𝒜_\mathrm{U}^{}`$ is $`n_i1`$, and the number of collected elements, strictly smaller than $`b^{^{}}`$, is $`n_{i,<}`$. The cost of sorting the block $`A_<`$ is bounded by $`2n_{i,<}\mathrm{log}n_{i,<}+O(n_{i,<}(\mathrm{log}n_{i,<})^{4/5})2n_{i,<}\mathrm{log}n+O(n_{i,<}(\mathrm{log}n)^{4/5})`$ comparisons and $`(11+\epsilon )n_{i,<}`$ moves, by Thm. 7. Sorting of the block $`A_<`$ is followed by restoring the sorted order in $`\mathrm{\Pi }_\mathrm{L}`$ and $`\mathrm{\Pi }_\mathrm{R}`$, by clearing all bits, which costs $`O(P)O(n/(\mathrm{log}n)^2)`$ comparisons, together with the same number of moves. Positioning $`b^{^{}}`$ to the right of $`A_{<,\mathrm{S}}`$ requires only $`2`$ element moves. Finally, the $`i`$th iteration is concluded by partitioning $`B_{}^{}`$ into blocks $`A_=`$ and $`B_>`$, with at most $`n_{i,}n_i`$ comparisons and $`2n_{i,=}+1`$ moves, since the length of $`B_{}^{}`$ is $`n_{i,}`$, and the number of collected elements, equal to $`b^{^{}}`$, is $`n_{i,=}`$. The cost of sorting the residual short block does not exceed the bounds for the standard case; $`2n_{}\mathrm{log}n_{}+6.25n_{}2n_{}\mathrm{log}n+O(n_{}(\mathrm{log}n)^{4/5})`$ comparisons and $`9.75n_{}(11+\epsilon )n_{}`$ moves. (See Sect. 3 below).

Now we can sum the above costs over all iterations, using (9) and (10). For the number of comparisons, this gives

$`C(n)`$ $``$ $`_{i=0}^1n_iO(1)+_{i=0}^1n_{i,<}(2\mathrm{log}n+O((\mathrm{log}n)^{4/5}))+_{i=0}^1O(n/(\mathrm{log}n)^2)`$
$`+n_{}(2\mathrm{log}n+O((\mathrm{log}n)^{4/5}))`$
$``$ $`O(n)+(_{i=0}^1(n_{i,<}+1+n_{i,=})+n_{})(2\mathrm{log}n+O((\mathrm{log}n)^{4/5}))+O(n/\mathrm{log}n)`$
$``$ $`O(n)+n(2\mathrm{log}n+O((\mathrm{log}n)^{4/5}))+O(n/\mathrm{log}n)`$
$``$ $`2n\mathrm{log}n+O(n(\mathrm{log}n)^{4/5}).`$
For the number of moves, we get

$`M(n)`$ $``$ $`_{i=0}^1\epsilon n_i+_{i=0}^1(13+\epsilon )n_{i,<}+_{i=0}^12n_{i,=}+_{i=0}^1O(n/(\mathrm{log}n)^2)`$
$`+(11+\epsilon )n_{}`$
$``$ $`\epsilon n+(_{i=0}^1(n_{i,<}+1+n_{i,=})+n_{})(13+\epsilon )+O(n/\mathrm{log}n)`$
$``$ $`\epsilon n+n(13+\epsilon )+O(n/\mathrm{log}n)`$
$``$ $`(13+\epsilon )n,`$
where $`\epsilon >0`$ is an arbitrarily small, but fixed, real constant. The above analysis did not include the costs of the initial building of pointer memory. However, by Lem. 8, this can be done with only $`O(n)`$ comparisons and $`O(n/\mathrm{log}n)`$ moves, and hence the bounds displayed above represent the total computational costs of the algorithm.

###### Theorem 9

The given array, consisting of $`n`$ elements, can be sorted in-place by performing at most $`2n\mathrm{log}n+o(n\mathrm{log}n)`$ comparisons and $`(13+\epsilon )n`$ element moves, where $`\epsilon >0`$ denotes an arbitrarily small, but fixed, real constant. The number of auxiliary arithmetic operations with indices is bounded by $`O(n\mathrm{log}n)`$.

3.3. Handling short blocks. The algorithm presented above needs a procedure capable of sorting blocks of small lengths, namely, with $`m2^{16}=65536`$. This is required, among others, to sort blocks $`A_<`$ that are short. We could sweep the problem under the rug by saying that “short” blocks can, “somehow,” be sorted with $`O(1)`$ comparisons and moves, since they are of constant lengths. However, the upper bounds presented by Thm. 7 in Sect. 2 require some more details, especially for $`(11+\epsilon )m`$, the number of moves. Last but not least, these lengths are important in practice.

One of the possible simple solutions is to use our version of heapsort, with $`5`$ roots and internal nodes having $`5`$ sons. Using the analysis presented in Sect. 3, devoted to building a pointer memory, for $`t=5`$, $`m2^{16}`$, and hence for at most $`q1+\mathrm{log}_tm7`$ levels, one can easily verify that we shall never use more than $`2m\mathrm{log}m+6.25m`$ comparisons or $`9.75m`$ moves. (These bounds are not tight, we leave further improvement to the reader).

3.4. An alternative solution. As pointed out at the end of Sect. 2, devoted to extracting sorted elements from segments, we could use a heap structure with four levels, instead of five, in a segment. This slightly reduces the number of moves, but increases the number of comparisons. The detailed argument parallels the proof of Thm. 9, and hence it is left to the reader.

###### Corollary 10

The given array, consisting of $`n`$ elements, can be sorted in-place by performing at most $`6n\mathrm{log}n+o(n\mathrm{log}n)`$ comparisons and $`(12+\epsilon )n`$ element moves, where $`\epsilon >0`$ denotes an arbitrarily small, but fixed, real constant.

## 4 . Concluding Remarks

We have described the first in-place sorting algorithm performing $`O(n\mathrm{log}n)`$ comparisons and $`O(n)`$ element moves in the worst case, which closes a long-standing open problem.

However, the algorithms presented in Thm. 9 and Cor. 10 do not sort stably, since the order of buffer elements may change. If some elements used in buffers are equal, their original order cannot be recovered. This leaves us with a fascinating question:

> Does there exist an algorithm operating in-place and performing, in the worst case, at most $`O(n\mathrm{log}n)`$ comparisons, $`O(n)`$ moves, $`O(n\mathrm{log}n)`$ arithmetic operations, and, at the same time, sorting elements stably, so that the relative order of equal elements is preserved?

At the present time, we dare not formulate any conjectures about this problem. The best known algorithm for stable in-place sorting with $`O(n)`$ moves is still the one presented in , performing $`O(n^{1+\epsilon })`$ comparisons in the worst case.

We are also firmly convinced that the upper bounds of Thm. 9 and Cor. 10 are not optimal and can be improved, which is left as another open problem.


format: text

text:
# ANOMALOUS ABSORPTION IN CYCLIC C3H RADICAL

## 1 introduction

Discovery of hydrocarbon ring molecules in interstellar medium has provided an important tool to investigate the medium. Cyclopropenylidene (cyclic C3H2, denoted as $`c`$-C3H2) was the first hydrocarbon ring molecule discovered in astronomical objects by Thaddeus et al. (1985b). This molecule is ubiquitous in the galactic objects (Matthews and Irvine, 1985), and except in one case, its line $`2_{11}2_{20}`$ at 21.59 GHz has always been found in absorption against the cosmic 2.7 K background (also called the cosmic microwave background, denoted as CMB) (Madden et al., 1989; Chandra & Kegel, 2001). Cox et al. (1987), however, reported this line in emission in the Planetary Nebula NGC 7027. The second hydrocarbon ring molecule, cyclopropynylidyne (cyclic C3H, denoted as $`c`$-C3H) was first detected by Yamamoto et al. (1987) in TMC-1 through its transition $`2_{12}1_{11}`$ at 91.5 GHz. Mangum and Wootten (1990) detected $`c`$-C3H through the transition $`1_{10}1_{11}`$ in 12 additional galactic objects. The $`l`$-C3H (linear C3H) also has been identified in astronomical objects (Thaddeus et al., 1985a). The most probable production mechanism of C3H and C3H2 in dark clouds is a common dissociation reaction of C3H$`{}_{}{}^{+}{}_{3}{}^{}`$ ion (Adams & Smith, 1987):

| C3H$`{}_{}{}^{+}{}_{3}{}^{}`$ \+ e | $``$ C3H + H + H or C3H + H2 |
| --- | --- |
|  | $``$ C3H2 \+ H |

Although $`c`$-C3H is 0.8 eV less stable than $`l`$-C3H, the column density of $`c`$-C3H is found almost comparable to that of $`l`$-C3H in TMC-1. Such a metastable isomer can exit in the interstellar condition. For example, the HNC, the isomer of HCN, is detected in various molecular clouds. In the preset $`Letter`$, we propose to identify $`c`$-C3H in cosmic objects through $`3_{31}3_{30}`$ transition in absorption against the CMB.

## 2 molecular data & calculations

The $`c`$-C3H is an $`a`$-type asymmetric top molecule with electric dipole moment $`\mu `$ = 2.4 Debye. Rotational levels in an asymmetric top molecule are specified as $`J_{k_a,k_c}`$, where $`J`$ is the rotational quantum number, and $`k_a`$ and $`k_c`$ the projections of $`J`$ on the axis of symmetry in case of prolate and oblate symmetric tops, respectively. In $`a`$-type asymmetric top molecule, rotational radiative transitions are governed by the selection rules:

| $`J:`$ | $`\mathrm{\Delta }J=0\pm 1`$ |
| --- | --- |
| $`k_a,k_c:`$ | even, odd $``$ even, even |
| : | odd, even $``$ odd, odd |

Owing to the nuclear symmetry in $`c`$-C3H, rotational levels with even value of $`k_a`$ are missing in its spectra. As the temperature in dark molecular clouds is not large, in the present investigation we accounted for the rotational levels up to 77 cm-1 for $`c`$-C3H (Table 1). The Einstein $`A`$-coefficient for rotational transitions between the levels are calculated following the method discussed by Chandra & Sahu (1993) and Chandra & Rashmi (1998). These 37 rotational levels are connected through 121 radiative transitions. For the calculations, the required molecular and distortional constants are taken from Yamamoto and Saito (1994). We have not, however, accounted for fine structure and hyperfine structure splittings of the levels.

NLTE occupation numbers of energy levels are calculated in an $`on`$-$`the`$-$`spot`$ approximation discussed by Rausch et al. (1996), where the external radiation field, impinging on a volume element generating the lines, is the CMB only. Besides the radiative transition probabilities for radiatively allowed transitions between the rotational levels, data required for the present investigation are the rate coefficients for collisional transitions between the levels due to collisions with H2 molecules. Collisional rate coefficients are not available in literature. Therefore, the rate coefficients for downward transitions $`J_{k_a^{}k_c^{}}^{}J_{k_ak_c}`$ at a kinetic temperature $`T`$ are taken as (Sharma & Chandra, 2001)

$`C(J_{k_a^{}k_c^{}}^{}J_{k_ak_c})=1\text{x}10^{11}\sqrt{T/30}/(2J^{}+1)`$
For upward collisional rate coefficients, we accounted for the fact that downward and upward collisional rate coefficients are related through the detailed equilibrium (Chandra & Kegel, 2000).

## 3 results & discussion

In order to include a large number of cosmic objects where $`c`$-C3H may be found, numerical calculations are carried out for wide ranges of physical parametres. The molecular hydrogen density $`n_{H_2}`$ has been varied over the range from 103 cm-3 to 107 cm-3, and calculations are performed for three kinetic temperatures 10, 20 and 30 K. In the calculations, free parametres are the molecular hydrogen density $`n_{H_2}`$, and $`\gamma n_{mol}/(dv_r/dr)`$, where $`n_{mol}`$ is density of the $`c`$-C3H molecule, and $`dv_r/dr`$ the velocity gradient. As we have used scaled values of collisional rates, our results are qualitative in nature.

### 3.1 anomalous absorption in $`c`$-C3H radical

Out of a number of lines of $`c`$-C3H found in absorption against the CMB, the transition $`3_{31}3_{30}`$ at 3.4 GHz has shown reasonably good absorption phenomenon. The Einstein $`A`$-coefficient for this line is 1.9 x 10-9 s-1, and the radiative lifetimes of the upper and lower levels $`3_{30}`$ and $`3_{31}`$ are 2.3 x 104 s and 4.4 x 104 s, respectively. Observation of a spectral line in absorption against the CMB is an unusual phenomenon. The intensity, $`I_\nu `$, of a line generated in an interstellar cloud, with homogeneous excitation conditions, is given by

$`I_\nu I_{\nu ,bg}=(S_\nu I_{\nu ,bg})(1e^{\tau _\nu })`$
where $`I_{\nu ,bg}`$ is the intensity of the continuum against which the line is observed, $`\tau _\nu `$ the optical depth of the line, and $`S_\nu `$ the source function. For positive optical depth, observation of an interstellar line in absorption against the CMB, obviously, implies the excitation temperature of the line, $`T_{ex}`$, to be less than the CMB temperature 2.7 K, but positive. It requires rather peculiar conditions in the molecule generating the line.

Variation of line-intensity against the CMB in the units of the Planck’s function at the kinetic temperature of $`T`$(K), i.e., $`(I_\nu I_{\nu ,bg})/B_\nu (T)`$, for the line $`3_{31}3_{30}`$ for three kinetic temperatures 10, 20 and 30 K (written on the top of the column) is shown in Figure 1 (first row) for $`\gamma =`$ 10-6 and 10-5 cm-3 (km/s)-1 pc. The line $`3_{31}3_{30}`$ shows the absorption against the CMB. The excitation temperature of the line reduces up to 0.27 K which 10% of the CMB temperature, and its absorption intensity is comparable to the absorption intensity of the line $`2_{11}2_{20}`$ of $`c`$-C3H2 calculated by Chandra & Kegel (2001). It supports the idea of detection of the line $`3_{31}3_{30}`$ of $`c`$-C3H in absorption against the CMB.

### 3.2 effect of a background source

In the above discussion, we considered the external radiation field, impinging on a volume element generating the lines, to be the CMB only. In order to see the effect of a background source, we repeated the calculations for the background intensity

$`I_{\nu ,bg}=(1f)B_\nu (2.7K)+fB_\nu (2000K)`$
$`f`$ is the dilution factor, representing the distance of the background source from the cosmic object, and the effect of the atmosphere between the source and the object. Here, the temperature of the background source is assumed to be 2000 K. For other values of the temperature of the source, the effect can be scaled down with the help of the dilution factor $`f`$. The case $`f=0`$ corresponds to the CMB only. With the increase of the value of $`f`$ the peak of the absorption line decreases. However, at low densities, the intensity increases. Hence, in the low density regions, a background source can help in detection of the line. Figure 1 shows the absorption intensities for $`f`$ = 0, 10-4 and 10-3. When $`f`$ is large than 10-3, the absorption feature disappears.

### 3.3 anomalous absorption in $`c`$-C3D radical

We repeated the calculations for $`c`$-C3D radical, where we accounted for the rotational levels given in Table 1. Note that the sequence of some higher levels of $`c`$-C3D is changed relative to those of the $`c`$-C3H. This molecule also is an $`a`$-type asymmetric top molecule with electric dipole moment $`\mu `$ = 2.4 Debye. The required molecular and distortional constants are taken from Yamamoto and Saito (1994). Here, also the line $`3_{31}3_{30}`$ at 0.9 GHz is found in absorption against the CMB. Variation of line-intensity against the CMB in the units of Planck’s function at the kinetic temperature of $`T`$(K), i.e., $`(I_\nu I_{\nu ,bg})/B_\nu (T)`$, for the line for three kinetic temperatures 10, 20 and 30 K and the dilution factor $`f`$ = 0, 10-4 and 10-3 is shown in Figure 2 for $`\gamma =`$ 10-6 and 10-5 cm-3 (km/s)-1 pc. The line $`3_{31}3_{30}`$ of $`c`$-C3D is found to be weaker than that of the $`c`$-C3DH. However, the features of the two are very similar.

We re grateful to Prof. J.V. Narlikar, Prof. S.A. Suryawanshi, and Prof. Dr. W.H. Kegel for encouragement.