Alex's Anthology of Algorithms Common Code for Contests in Concise C++
Elementary Algorithms / Array Transformations

1.1.2 Array Rotation

1-Elementary-Algorithms/1.1.2_Array_Rotation.cpp

View on GitHub

These functions are equivalent to std::rotate(), taking three iterators lo, mid, and hi (lo $\leq$ mid $\leq$ hi) to perform a left rotation on the range [lo, hi). After the function call, [lo, hi) will consist of the concatenation of elements originally in [mid, hi) + [lo, mid). That is, the range [lo, hi) will be rearranged in such a way that the element at mid becomes the first element of the new range, and the element before mid becomes the last element, all while preserving the relative ordering of elements within the two rotated subarrays.

All three versions below achieve the same result using in-place algorithms.

  • rotate1(lo, mid, hi) uses a straightforward swapping algorithm requiring ForwardIterators.
  • rotate2(lo, mid, hi) requires BidirectionalIterators, employing a well-known trick with three simple inversions.
  • rotate3(lo, mid, hi) requires random-access iterators, applying a juggling algorithm which first divides the range into gcd(hi - lo, mid - lo) sets and then rotates the corresponding elements in each set.

Implementation

#include <algorithm>
#include <numeric>

template<class It>
void rotate1(It lo, It mid, It hi) {
  if (lo == mid || mid == hi) {
    return;
  }
  It next = mid;
  while (lo != next) {
    std::iter_swap(lo++, next++);
    if (next == hi) {
      next = mid;
    } else if (lo == mid) {
      mid = next;
    }
  }
}

template<class It>
void rotate2(It lo, It mid, It hi) {
  if (lo == mid || mid == hi) {
    return;
  }
  std::reverse(lo, mid);
  std::reverse(mid, hi);
  std::reverse(lo, hi);
}

template<class It>
void rotate3(It lo, It mid, It hi) {
  if (lo == mid || mid == hi) {
    return;
  }
  int n = hi - lo, jump = mid - lo;
  int g = std::gcd(jump, n), cycle = n / g;
  for (int i = 0; i < g; i++) {
    int curr = i, next;
    for (int j = 0; j < cycle - 1; j++) {
      next = curr + jump;
      if (next >= n) {
        next -= n;
      }
      std::iter_swap(lo + curr, lo + next);
      curr = next;
    }
  }
}

Example Usage

#include <algorithm>
#include <cassert>
#include <iostream>
#include <vector>
using namespace std;

int main() {
  vector<int> a0, a1, a2, a3;
  for (int i = 0; i < 10000; i++) {
    a0.push_back(i);
  }
  a1 = a2 = a3 = a0;
  int mid = 5678;
  std::rotate(a0.begin(), a0.begin() + mid, a0.end());
  rotate1(a1.begin(), a1.begin() + mid, a1.end());
  rotate2(a2.begin(), a2.begin() + mid, a2.end());
  rotate3(a3.begin(), a3.begin() + mid, a3.end());
  assert(a0 == a1 && a0 == a2 && a0 == a3);

  // Example from: http://en.cppreference.com/w/cpp/algorithm/rotate
  vector<int> a{2, 4, 2, 0, 5, 10, 7, 3, 7, 1};
  cout << "before sort:  ";
  for (const auto &x : a) {
    cout << x << " ";
  }
  cout << endl;

  // Insertion sort.
  for (auto i = a.begin(); i != a.end(); ++i) {
    rotate1(std::upper_bound(a.begin(), i, *i), i, i + 1);
  }
  cout << "after sort:   ";
  for (const auto &x : a) {
    cout << x << " ";
  }
  cout << endl;

  // Simple rotation to the left.
  rotate2(a.begin(), a.begin() + 1, a.end());
  cout << "rotate left:  ";
  for (const auto &x : a) {
    cout << x << " ";
  }
  cout << endl;

  // Simple rotation to the right.
  rotate3(a.rbegin(), a.rbegin() + 1, a.rend());
  cout << "rotate right: ";
  for (const auto &x : a) {
    cout << x << " ";
  }
  cout << endl;
  return 0;
}

Example Output

before sort:  2 4 2 0 5 10 7 3 7 1
after sort:   0 1 2 2 3 4 5 7 7 10
rotate left:  1 2 2 3 4 5 7 7 10 0
rotate right: 0 1 2 2 3 4 5 7 7 10