1
$\begingroup$

I've found a simple algorithm to interleave two halves of an array in place. It involves swapping the first 1/2 of the items into the correct place, then unscrambling the permutation of the 1/4 of the items that got moved, then repeating for the remaining 1/2 array.

Unscrambling the permutation uses the fact that left items move into the right side with an alternating "add to end, swap oldest" pattern. We can find the i'th index in this permutation with this this rule:

For even i, the end was at i/2.
For odd i, the oldest was added to the end at step (i-1)/2

This gives this O(1) amortized oracle function:

$$ next(i) := \begin{cases} i/2 & \text{if $i$ is even;}\\ next( (i-1)/2) & \text{otherwise.} \end{cases} $$

(This is A025480)

The other complication in unscrambling is that while putting items back into place, sometimes the source index has already been used as a destination. Traditionally, this is where you would use an algorithm to find cycle leaders, guaranteeing minimal data movement at the cost of additional space or time. This algorithm just advances through the permutation, swapping every item into place, and running the next_index calculation again whenever it encounters a source which has already been swapped elsewhere. See the 'while' loop below.

Given a sequence $A$ of length $n$, consider it as two parts: $L = A_0 \ldots A_{m-1}$ and $R = A_m \ldots A_{n-1}$ where $m$ is $\lfloor n/2 \rfloor$. Then interleave($A$) will produce $R_0, L_0, R_1, L_1, \ldots$

$$ interleave(A) := {\\ m \leftarrow \lfloor n/2 \rfloor \\ \text{let }L = A_0 \ldots A_{m-1} \\ \text{let }R = A_m \ldots A_n \\ \text{for } i \text{ in } 0 \ldots (m-1): { j \leftarrow next(i) \\ \operatorname{exchange} L_i, R_j }\\ \text{for } j\text{ in } 0 \ldots ( \lfloor m/2 \rfloor -2): {\\ k \leftarrow next(\lfloor m/2 \rfloor + j) \\ \text{while } k < j : k \leftarrow next(\lfloor m/2 \rfloor + k) \\ \operatorname{exchange} R_j, R_k} \\ \text{if } n - m > 1 : { \\ b \leftarrow 1 \text{ if $m$ is odd; } 0 \text{ otherwise} \\ \operatorname{interleave} R_b \ldots R_{n-m-1} } } $$

The number of swaps is strictly 1.5 N. The question I have is: What's the big O of the index calculation in the unscramble step?

I think, in the hypothetical worst case, where every item is one slot above its destination, you would do 1+2+3+...+N/4 calls to next_index in the first pass. That works out to N^2/32 which is still O(N^2).

But the placement pattern never gets close to that worst case. Half the items are guaranteed to only need one lookup: the last half of the permutation contains every other item in increasing order - these will be swapped directly into place. And the remaining number of lookups depends on the number of cycles in the permutation - which seems to follow A006694.

Empirically it seems to be significantly less than N lg N - Here's a graph of calls to next_index vs N, with N lg lg N overlaid. graph of calls to next_index vs N

I'd like to learn how to calculate the actual growth.


C implementation.

int next_index(int n) {
  while (n & 1) { n = n >> 1; }
  return n >> 1;
}

void interleave(value_t A[], int n) {
  int i, j, k;
  int m = n/2;
  value_t* L = A;
  value_t* R = A+m;

  // Place 1st half
  for (i=0; i < m; i++) {
    j = next_index(i);
    SWAP( L+i, R+j);
  }

  //unscramble step
  for (j=0; j < m/2 -1; j++) {
    k = next_index(m/2 + j);
    while (k < j) {
      k = next_index(m/2 + k);
    }
    SWAP( R+j, R+k);
  }
  if (n-m > 1) {
    int b = (m&1) ? 1 : 0;
    interleave(R+b, n-m-b);
  }
}
$\endgroup$
  • $\begingroup$ Can you rewrite your C as pseudocode? Not everybody can read C. For example, for your next_index function, you have: while $i$ is odd, $i \gets (i-1)/2$; return $i/2$. $\endgroup$ – Yuval Filmus Mar 15 at 8:45
  • $\begingroup$ done. I would appreciate feedback on its clarity. $\endgroup$ – AShelly Mar 15 at 20:14
  • $\begingroup$ This looks great, though doesn't seem to conform to the C code. For example, $k$ is being assigned $next(i/2+j)$ and $next(i/2+k)$, whereas your pseudocode has $next(j)$ and $next(k)$. $\endgroup$ – Yuval Filmus Mar 15 at 20:39
  • $\begingroup$ Thanks. I missed the i/2 term when transforming to a recursive version. $\endgroup$ – AShelly Mar 15 at 23:51
  • $\begingroup$ Are you sure the pseudocode is now correct? Please double check. Nobody likes solving the wrong problem. $\endgroup$ – Yuval Filmus Mar 16 at 0:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.