14. Разделяй и владей - циклично преместване на елементите на масив [7.10]

* Пример за циклично преместване на k позиции елементите на масив m от n елемента.
n = 6, k = 2   0 1 2 3 4 5   ->  2 3 4 5 0 1

* Алгоритъм 1:
- копираме първите k елемента на m в друг масив x;
- преместваме n-k елемента на m на k позиции вляво;
- копираме елементите на x обратно в m на последните k позиции.
Сложност O(n), допълнителна памет k елемента.

* Алгоритъм 2:
Описания алгоритъм за k = 1. За да осъществим циклично преместване с k елемента, е необходимо да приложим този алгоритъм  k пъти.
Сложност O(n2), допълнителна памет 1 елемент.

* Алгоритъм 3:
- копираме m[0] в x;
- преместваме m[k] в m[0], преместваме m[2k%n] в m[k], преместваме m[3k%n] в m[2k%n] и т.н. докато стигнем последния непреместен елемент, който го заменяме с x (Пример А). Ако достигнем до преместен елемент, спираме и започваме отначало по същия начин, като заменяме k с k+1 (Пример Б).
 

Пример А    n = 11, k = 3: 
0 1 2 3 4 5 6 7 8 9 10  ->  3 4 5 6 7 8 9 10 0 1 2
m01: 0 1 2 3 4 5 6 7 8 9 10 | x:
m02: 0 1 2 3 4 5 6 7 8 9 10 | x: 0
m03: 3 1 2 3 4 5 6 7 8 9 10 | x: 0; 
m04: 3 1 2 6 4 5 6 7 8 9 10 | x: 0; 
m05: 3 1 2 6 4 5 9 7 8 9 10 | x: 0; 
m06: 3 1 2 6 4 5 9 7 8 1 10 | x: 0; 
m07: 3 4 2 6 4 5 9 7 8 1 10 | x: 0; 
m08: 3 4 2 6 7 5 9 7 8 1 10 | x: 0; 
m9: 3 4 2 6 7 5 9 10 8 1 10 | x: 0; 
m10: 3 4 2 6 7 5 9 10 8 1 2 | x: 0; 
m11: 3 4 5 6 7 5 9 10 8 1 2 | x: 0; 
m12: 3 4 5 6 7 8 9 10 8 1 2 | x: 0
m13: 3 4 5 6 7 8 9 10 0 1 2 | x: 0; 
Пример Б   n = 9, k = 3: 
0 1 2 3 4 5 6 7 8  ->  3 4 5 6 7 8 0 1 2
m01: 0 1 2 3 4 5 6 7 8 | x:
m02: 3 1 2 3 4 5 6 7 8 | x: 0
m03: 3 1 2 6 4 5 6 7 8 | x: 0
m04: 3 1 2 6 4 5 0 7 8 | x: 0
m05: 3 1 2 6 4 5 0 7 8 | x: 1; 
m06: 3 4 2 6 4 5 0 7 8 | x: 1; 
m07: 3 4 2 6 7 5 0 7 8 | x: 1
m08: 3 4 2 6 7 5 0 1 8 | x: 1
m09: 3 4 2 6 7 5 0 1 8 | x: 2; 
m10: 3 4 5 6 7 5 0 1 8 | x: 2; 
m11: 3 4 5 6 7 8 0 1 8 | x: 2
m12: 3 4 5 6 7 8 0 1 2 | x: 2

Сложност O(n), допълнителна памет 1 елемент.

* Алгоритъм 4 (разделяй и владей):
- разделяме масива на 2 части: A - първите k елемента и B - последните n - k елемента;
- разделяме масива B на 2 части, Br - последните k елемента, Bl - останалите елементи;
- разменяме местата на елементите на A и Br;
- прилагаме същия алгоритъм за масива B.

Пример.
n = 11, k = 3:
0 1 2 3 4 5 6 7 8 9 10  ->  3 4 5 6 7 8 9 10 0 1 2
[0 1 2 3 4 5 6 7 8 9 10]
A = [0 1 2], Bl = [3 4 5 6 7], Br = [8 9 10]
[8 9 10 3 4 5 6 7 0 1 2 ]

[8 9 10 3 4 5 6 7 ]  [0 1 2]
A = [8 9 10], Bl =[3 4], Br = [5 6 7]
[5 6 7 3 4 8 9 10]

[5 6 7 3 4]  [8 9 10 0 1 2]
A = [5 6 7], Bl = [], Br = [3 4 5]
[ 3 4 5 6 7]

[3 4 5 6 7 8 9 10 0 1 2]

Сложност O(n), допълнителна памет - константа.

* Алгоритъм 5 (Кърниган и Плоджер):

BA = (AR BR)R
Тук с AR е означен масив, получен от елементите на A в обратен ред. С 3 извиквания на функция reverse за обръщане на елементите на масив в обратен ред решаваме задачата за циклично преместване на елементите на масив.

Пример.
n = 11, k = 3:
0 1 2 3 4 5 6 7 8 9 10  ->  3 4 5 6 7 8 9 10 0 1 2

A = [0 1 2], B = [3 4 5 6 7 8 9 10], AB = [0 1 2 3 4 5 6 7 8 9 10]
AR = [2 1 0],  BR = [10 9 8 7 6 5 4 3],  AR BR = [2 1 0 10 9 8 7 6 5 4 3]
(AR BR)R = [3 4 5 6 7 8 9 10 0 1 2]

// shift3.c
#include <stdio.h>
#define N 11

int m[N];

void reverse(unsigned a, unsigned b)
{ unsigned i, j, k, c;
  int tmp;
  for (c=(b-a)/2, k=a, j=b, i=0; i<c; i++, j--, k++)
  { tmp = m[k];
    m[k] = m[j];
    m[j] = tmp;
  }
}

void shift3(unsigned k)
{ reverse(0, k-1);
  reverse(k, N-1);
  reverse(0, N-1);
}

int main()
{
 int i;
 for (i=0; i<N; i++) m[i]=i;
 shift3(3);
 for (i=0; i<N; i++) printf("%d ", m[i]);
 printf("\n");
 return 0;
}
Сложност O(n), допълнителна памет - константа.