8

GitHub - scandum/rotate: A collection of array rotation algorithms.

 1 year ago
source link: https://github.com/scandum/rotate
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

The most commonly used rotation algorithms (aka block swaps) were documented around 1981 and haven't notably changed since.

Below I'll describe several variants, notably the conjoined triple reversal, followed by a benchmark graph.

Introduction to rotating

A rotation is to swap the left side of an array with the right side.

┌──────────────────────────┬─────────────────┐
│ 1  2  3  4  5  6  7  8  9│10 11 12 13 14 15│
└──────────────────────────┴─────────────────┘

It's a common operation in a variety of sorting algorithms. After the rotation the data is as following.

┌─────────────────┬──────────────────────────┐
│10 11 12 13 14 15│ 1  2  3  4  5  6  7  8  9│
└─────────────────┴──────────────────────────┘

Auxiliary Rotation

This is an easy and fast way to rotate, but since it requires auxiliary memory it is of little interest to in-place algorithms.

Typically the smaller half is copied to swap memory, the larger half is moved, and the swap memory is copied back to the main array.

┌──────────────────────────┬─────────────────┐
│ 1  2  3  4  5  6  7  8  9│10 11 12 13 14 15│
└──────────────────────────┴─────────────────┘
                             └──┴──┴──┴──┴──┴────┬──┬──┬──┬──┬──┐
┌──────────────────────────┬─────────────────┐ ┌─────────────────┐
│ 1  2  3  4  5  6  7  8  9│                 │ │10 11 12 13 14 15│
└──────────────────────────┴─────────────────┘ └─────────────────┘
  └──┴──┴──┴──┴──┴──┼──┼──┼──┬──┬──┬──┬──┬──┐
┌─────────────────┬──────────────────────────┐ ┌─────────────────┐
│                 │ 1  2  3  4  5  6  7  8  9│ │10 11 12 13 14 15│
└─────────────────┴──────────────────────────┘ └─────────────────┘
  ┌──┬──┬──┬──┬──┬───────────────────────────────┴──┴──┴──┴──┴──┘
┌─────────────────┬──────────────────────────┐
│10 11 12 13 14 15│ 1  2  3  4  5  6  7  8  9│
└─────────────────┴──────────────────────────┘

Bridge Rotation

This is a slightly more complex auxiliary rotation that reduces the maximum auxiliary memory requirement from 50% to 33%.

If the overlap between the two halves is smaller than the halves themselves it copies the overlap to swap memory instead.

┌──────────────────────────┬─────────────────┐
│ 1  2  3  4  5  6  7  8  9│10 11 12 13 14 15│
└──────────────────────────┴─────────────────┘
                    └──┴──┴──────────────────────┬──┬──┐
┌─────────────────┬────────┬─────────────────┐ ┌────────┐
│ 1  2  3  4  5  6│        │10 11 12 13 14 15│ │ 7  8  9│
└─────────────────┴────────┴─────────────────┘ └────────┘
  ├─────────────────┬────────┘
┌─────────────────┬────────┬─────────────────┐ ┌────────┐
│10  2  3  4  5  6│ 1      │   11 12 13 14 15│ │ 7  8  9│
└─────────────────┴────────┴─────────────────┘ └────────┘
     ├─────────────────┬────────┘
┌─────────────────┬────────┬─────────────────┐ ┌────────┐
│10 11  3  4  5  6│ 1  2   │      12 13 14 15│ │ 7  8  9│
└─────────────────┴────────┴─────────────────┘ └────────┘
        ├─────────────────┬────────┘
┌─────────────────┬────────┬─────────────────┐ ┌────────┐
│10 11 12  4  5  6│ 1  2  3│         13 14 15│ │ 7  8  9│
└─────────────────┴────────┴─────────────────┘ └────────┘
           ├─────────────────┬────────┘
┌─────────────────┬────────┬─────────────────┐ ┌────────┐
│10 11 12 13  5  6│ 1  2  3│ 4          14 15│ │ 7  8  9│
└─────────────────┴────────┴─────────────────┘ └────────┘
              ├─────────────────┬────────┘
┌─────────────────┬────────┬─────────────────┐ ┌────────┐
│10 11 12 13 14  6│ 1  2  3│ 4  5          15│ │ 7  8  9│
└─────────────────┴────────┴─────────────────┘ └────────┘
                 ├─────────────────┬────────┘
┌─────────────────┬────────┬─────────────────┐ ┌────────┐
│10 11 12 13 14 15│ 1  2  3│ 4  5  6         │ │ 7  8  9│
└─────────────────┴────────┴─────────────────┘ └────────┘
                                      ┌──┬──┬────┴──┴──┘
┌─────────────────┬──────────────────────────┐
│10 11 12 13 14 15│ 1  2  3  4  5  6  7  8  9│
└─────────────────┴──────────────────────────┘

Bentley's Juggling Rotation

Also known as the dolphin algorithm. This is a relatively complex and cache inefficient way to rotate in-place, though it does so in the minimal amount of moves. Its first known publication was in 1965.

It computes the greatest common divisor and uses a loop to create a chain of consecutive swaps.

┌──────────────────────────┬─────────────────┐
│ 1  2  3  4  5  6  7  8  9│10 11 12 13 14 15│
└──────────────────────────┴─────────────────┘
  ↓        ↓        ↓        ↓        ↓
┌──────────────────────────┬─────────────────┐
│10  2  3 13  5  6  1  8  9│ 4 11 12  7 14 15│
└──────────────────────────┴─────────────────┘
     ↓        ↓        ↓        ↓        ↓
┌──────────────────────────┬─────────────────┐
│10 11  3 13 14  6  1  2  9│ 4  5 12  7  8 15│
└──────────────────────────┴─────────────────┘
        ↓        ↓        ↓        ↓        ↓
┌─────────────────┬──────────────────────────┐
│10 11 12 13 14 15│ 1  2  3  4  5  6  7  8  9│
└─────────────────┴──────────────────────────┘

Triple Reversal Rotation

This is an easy and reliable way to rotate in-place. You reverse the left side, next you reverse the right side, next you reverse the entire array. Upon completion the left and right block will be swapped.

┌──────────────────────────┬─────────────────┐
│ 1  2  3  4  5  6  7  8  9│10 11 12 13 14 15│
└──────────────────────────┴─────────────────┘
  ↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓
┌──────────────────────────┬─────────────────┐
│ 9  8  7  6  5  4  3  2  1│10 11 12 13 14 15│
└──────────────────────────┴─────────────────┘
                             ↓  ↓  ↓  ↓  ↓  ↓
┌──────────────────────────┬─────────────────┐
│ 9  8  7  6  5  4  3  2  1│15 14 13 12 11 10│
└──────────────────────────┴─────────────────┘
  ↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓
┌─────────────────┬──────────────────────────┐
│10 11 12 13 14 15│ 1  2  3  4  5  6  7  8  9│
└─────────────────┴──────────────────────────┘

Gries-Mills Rotation

Its first known publication was in 1981. You swap the smallest array to its proper location, since it's in its proper location you can forget about it. The larger array is now divided in two parts, which you swap in a similar manner, until the smallest side shrinks to 0 elements.

┌──────────────────────────┬─────────────────┐
│ 1  2  3  4  5  6  7  8  9│10 11 12 13 14 15│
└──────────────────────────┴─────────────────┘
  ┌──┬──┬──┬──┬──┬───────────┴──┴──┴──┴──┴──┘
┌─────────────────┬────────┬─────────────────┐
│10 11 12 13 14 15│ 7  8  9│ 1  2  3  4  5  6│
└─────────────────┴────────┴─────────────────┘
                    └──┴──┴───────────┬──┬──┐
┌─────────────────┬────────┬────────┬────────┐
│10 11 12 13 14 15│ 4  5  6│ 1  2  3│ 7  8  9│
└─────────────────┴────────┴────────┴────────┘
                    ┌──┬──┬──┴──┴──┘
┌─────────────────┬──────────────────────────┐
│10 11 12 13 14 15│ 1  2  3  4  5  6  7  8  9│
└─────────────────┴──────────────────────────┘

Grail Rotation

The grail rotation from the Holy Grail Sort Project is Gries-Mills derived and tries to improve locality by shifting memory either left or right depending on which side it's swapped from. In addition it performs an auxiliary rotation on stack memory when the smallest side reaches a size of 1 element.

Beaker Rotation

The beaker rotation has similarities with the grail rotation but will swap multiple blocks at once when possible. The two nested loops to swap multiple blocks can be merged into a single loop, significantly improving performance when the relative size difference between the two halves is large. The mechanism to merge two loops is somewhat counter-intuitive and might be novel.

┌──────────────────────────┬─────────────────┐
│ 1  2  3  4  5  6  7  8  9│10 11 12 13 14 15│
└──────────────────────────┴─────────────────┘
           ┌──┬──┬──┬──┬──┬──┴──┴──┴──┴──┴──┘
┌────────┬─────────────────┬─────────────────┐
│ 1  2  3│10 11 12 13 14 15│ 4  5  6  7  8  9│
└────────┴─────────────────┴─────────────────┘
  └──┴──┴──┬──┬──┐
           └──┴──┴──┬──┬──┐
┌─────────────────┬──────────────────────────┐
│10 11 12 13 14 15│ 1  2  3  4  5  6  7  8  9│
└─────────────────┴──────────────────────────┘

Conjoined Triple Reversal Rotation

The conjoined triple reversal rotation (aka trinity rotation) is derived from the triple reversal rotation. Rather than three seperate reversals it conjoins the three reversals, improving locality and reducing the number of moves. Optionally, if one side is smaller than 8 elements, it skips the trinity rotation and performs an auxiliary rotation on stack memory.

┌──────────────────────────┬─────────────────┐
│ 1  2  3  4  5  6  7  8  9│10 11 12 13 14 15│
└──────────────────────────┴─────────────────┘
  ↓                       ↓  ↓              ↓
┌──────────────────────────┬─────────────────┐
│10  2  3  4  5  6  7  8  1│15 11 12 13 14  9│
└──────────────────────────┴─────────────────┘
     ↓                 ↓        ↓        ↓
┌──────────────────────────┬─────────────────┐
│10 11  3  4  5  6  7  2  1│15 14 12 13  8  9│
└──────────────────────────┴─────────────────┘
        ↓           ↓              ↓  ↓
┌──────────────────────────┬─────────────────┐
│10 11 12  4  5  6  3  2  1│15 14 13  7  8  9│
└──────────────────────────┴─────────────────┘
           ↓     ↓                 ↓
┌──────────────────────────┬─────────────────┐
│10 11 12 13  5  4  3  2  1│15 14  6  7  8  9│
└──────────────────────────┴─────────────────┘
              ↓                 ↓
┌──────────────────────────┬─────────────────┐
│10 11 12 13 14  4  3  2  1│15  5  6  7  8  9│
└──────────────────────────┴─────────────────┘
                 ↓           ↓
┌──────────────────────────┬─────────────────┐
│10 11 12 13 14 15  3  2  1│ 4  5  6  7  8  9│
└──────────────────────────┴─────────────────┘
                    ↓     ↓
┌─────────────────┬──────────────────────────┐
│10 11 12 13 14 15│ 1  2  3  4  5  6  7  8  9│
└─────────────────┴──────────────────────────┘

Benchmarks

Since the juggling rotation is rather slow and the auxiliary/bridge rotations are fairly similar I've omitted the juggling and auxiliary rotations from the benchmark graph. Similarly the grail rotation has been omitted since it's fundamentally slower than the beaker rotation.

While performance may vary depending on the specific implemention, from worst to best the order is:

  • Bentley's Juggling Rotation
  • Triple Reversal Rotation (reversal)
  • Grail Rotation
  • Beaker Rotation (beaker)
  • Auxiliary Rotation
  • Bridge Rotation (bridge)
  • Conjoined Triple Reversal Rotation (trinity)

It should be noted that the auxiliary Rotation performs better for smaller arrays and when the relative size difference between the two halves is large.

The following benchmark was on WSL 2 gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04). The source code was compiled using gcc -O3 bench.c. Each test was ran 1,000 times with the time (in seconds) reported of the best and average run.

rotation graph

data table


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK