Циклический сдвиг массива вправо c

Циклический сдвиг массива вправо c

Сайт о том, как стать программистом и как с этим жить потом

Не так давно стартовал очередной курс Java на одном небезызвестном образовательном портале. И вот, моим студентам досталась задача по работе с массивами. Статья в первую очередь для них, но и для интересующихся, конечно же 🙂 Отдельное спасибо alexandr.baykov@gmail.com за комментарий по поводу массива с чётным количеством элементов и чётным размером сдвига. Я переписал алгоритм и обновил статью.

Итак, задача сформулирована следующим образом

Написать метод, которому на вход подается одномерный массив и число n (может быть положительным, или отрицательным), при этом метод должен сместить все элементы массива на n позиций. Для усложнения задачи нельзя пользоваться вспомогательными массивами.

Усложняющее условие было введено потому, что можно решить задачу при помощи разделения массивов. В таком случае решение состоит в том, чтобы «отрезать» от массива кусок длиной n справа или слева (в зависимости от того, как n сравнивается с 0) и «прикрепить» отрезанную часть обратно с другой стороны. Это довольно дешёвый алгоритм, который имеет сложность O(n), но он будет требовать дополнительной памяти для хранения временного массива размера n. Поскольку такое решение самое простое, да и не особенно подходит под условие задачи, то мы перейдём сразу к более сложной алгоритмизации.

Разумеется, после первых подходов многие студенты находят следующее довольно логичное и вполне корректное решение. Оно весьма популярно на различных сайтах, описывающих циклический сдвиг. Суть его в том, что любой сдвиг можно представить в качестве n сдвигов на 1 ячейку. Сдвиг на 1 ячейку довольно просто реализуется циклом. В итоге, если изобразить это Java-методом, получается примерно такой метод:

public static int [ ] shiftArray ( int [ ] incomingArray, int shift ) <
if ( shift != 0 ) <
// Любой сдвиг больше длины массива можно оптимизировать до меньшего сдвига
// через деление по модулю

int finalShift ;
if ( shift > incomingArray. length ) <
shift = Math . abs ( shift % incomingArray. length ) ;
>
else <
finalShift = shift ;
>

// в зависимости от знака сдвига движение будет происходить
// слева направо при положительном сдвиге
// справа налево при отрицательном
if ( shift > 0 ) <
for ( int n = 0 ; n shift ; n ++ ) <
// убираем первый элемент в буфер, а на его место ставим хвостовой элемент
int buffer = incomingArray [ 0 ] ;
myArray [ 0 ] = incomingArray [ incomingArray. length — 1 ] ;

// циклично сдвигаем весь массив
for ( int j = 1 ; j incomingArray. length — 1 ; j ++ ) <
incomingArray [ incomingArray. length — j ] = incomingArray [ incomingArray. length — j — 1 ] ;
>

// ставим буферный элемент в 1 ячейку
incomingArray [ 1 ] = buffer ;
>
>
else if ( shift 0 ) <
for ( int i = 0 ; i > shift ; n — ) <
int buffer = incomingArray [ incomingArray. length — 1 ] ;
incomingArray [ incomingArray. length — 1 ] = incomingArray [ 0 ] ;

for ( int j = 1 ; j incomingArray. length — 1 ; j ++ ) <
incomingArray [ j — 1 ] = incomingArray [ j ] ;
>

incomingArray [ incomingArray. length — 2 ] = buffer ;
>
>
>

Хочу отметить, что такое решение имеет место, и оно применимо. Но мы же решаем алгоритмическую задачу, поэтому неплохо бы поговорить о том, какую сложность будет иметь приведенный алгоритм. Если опустить процесс вычислений буферных элементов и обмена ими между ячейками, то сложность алгоритма сводится к O(N * M), где N — размер входного массива, а M — величина сдвига. Очевидно, что для |M| = 1 (т.е. M = -1 или M = 1) сложность снизится до O(N). Это частный случай.

Читайте также:  Nokia lumia 435 характеристики

Теперь давайте подумаем, что же тут не так. На самом деле, если мы знаем величину сдвига (а мы её знаем), то вполне можно вычислить, какой элемент будет следующим. Это говорит о том, что можно создать жонглирующий алгоритм следующего вида:

  1. Получаем нулевой элемент и кладем его в буфер
  2. Начинаем цикл от 0 до длины массива включительно (это обусловлено тем, что в момент, когда мы дойдём до последнего элемента, он будет помещен в буфер, из которого его надо восстановить на месте нулевого элемента, чем полностью закольцевать сдвиг)
  3. Дальше мы меняем местами элементы, исходя из размера шага. Например, для шага 3 и массива длиной 7 мы сначала поменяем элементы 0, 3, 6. Затем — 1, 4, 2. И так далее.

Если рассмотреть наборы изменяемых элементов, то мы увидим, что количество таких наборов, внутри которых мы будем итеративно менять местами элементы, будет равно наименьшему общему делителю для размера массива и размера сдвига. К примеру, для массива из 12 элементов при сдвиге 3 мы будем иметь три набора смещаемых элементов.

1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12

— 1, 4, 7, 10
— 2, 5, 8, 11
— 3, 6, 9, 12

4 5 6 7 8 9 10 11 12 1 2 3

Поскольку эти наборы независимы, то при перестановке в них элементов местами мы не нарушим общий порядок следования элементов.

В реализации нужно учитывать и то, что сдвиг может идти как влево, так и вправо.

Теперь мы можем написать реализацию нашего алгоритма

public class moves <

public static void main ( String [ ] args ) <
int [ ] in1 = < 5 , 3 , 7 , 2 , 9 , 13 , 42 >;
int [ ] out1 = moves ( in1, — 2 ) ;
printOut ( out1 ) ;

int [ ] in2 = < 5 , 3 , 7 , 2 , 9 , 13 , 42 , 43 , 45 , 46 >;
int [ ] out2 = moves ( in2, 2 ) ;
printOut ( out2 ) ;

int [ ] in3 = < 5 , 3 , 7 , 2 , 9 , 13 , 42 , 143 >;
int [ ] out3 = moves ( in3, 5 ) ;
printOut ( out3 ) ;

int [ ] in4 = < 5 , 3 , 7 , 2 , 9 , 42 , 43 , 45 , 46 >;
int [ ] out4 = moves ( in4, 0 ) ;
printOut ( out4 ) ;
>

/**
* Main method, shifts array
* @param incoming — user array
* @param delta — shift value
* @return int[] result array
*/
static int [ ] moves ( int [ ] incoming, int delta ) <
int currentIndex, movedIndex, buffer ;
for ( int i = 0 ; i greatestCommonDivisor ( delta, incoming. length ) ; i ++ ) <
buffer = incoming [ i ] ;

if ( delta > 0 ) <
while ( true ) <
movedIndex = currentIndex + delta ;
if ( movedIndex >= incoming. length )
movedIndex = movedIndex — incoming. length ;
if ( movedIndex == i )
break ;
incoming [ currentIndex ] = incoming [ movedIndex ] ;
currentIndex = movedIndex ;
>
>
else if ( delta 0 ) <
while ( true ) <
movedIndex = currentIndex + delta ;
if ( movedIndex 0 )
movedIndex = incoming. length + movedIndex ;
if ( movedIndex == i )
break ;

incoming [ currentIndex ] = incoming [ movedIndex ] ;
currentIndex = movedIndex ;
>
>

incoming [ currentIndex ] = buffer ;
>

/**
* Simple printout
* @param incomingArray user array
*/
public static void printOut ( int [ ] incomingArray ) <
for ( int item : incomingArray ) <
System . out . print ( item + " " ) ;
>

Читайте также:  Компьютер не видит микрофон wo mic

/**
* Finding the GCD in recoursive function
* @param a — first element
* @param b — second element
* @return int GCD
*/
static int greatestCommonDivisor ( int a, int b )
<
if ( b == 0 )
return a ;
else
return greatestCommonDivisor ( b, a % b ) ;
>
>

Как и в прошлый раз, мы можем пренебречь расчетами индексов. И в конечном итоге мы получаем сложность алгоритма, которая зависит исключительно от длины массива, т.е. O(N). И эта сложность не будет меняться в зависимости от исключительных ситуаций, что как минимум не хуже предыдущего алгоритма, а для сдвигов, больших, чем единица, лучше.

Надеюсь, приведенные измышления будут для Вас полезны. Буду рад критике и комментариям!

Дочитавшим до конца, картинка де мотиватор &#128578;

Под циклическим сдвигом понимается перестановка значений всех элементов массива на одну или несколько позиций влево или вправо. При этом значение первого (последнего) элемента массива заносится в последний (первый) элемент массива в зависимости от направления сдвига.

Схематичное изображение циклического сдвига элементов массива вправо на одну позицию, выполняемого в три этапа, показано на рис. 5.4.1. Блок-схема алгоритма, выполняющего такой сдвиг, показана на рис. 5.4.2.

На 1-м этапе значение последнего элемента массива XN заносится в дополнительную переменную А (блок 1). На 2-м этапе организовывается цикл «Для» на основе блока модификации (блок 2), который перебирает элементы массива Х в обратном порядке, т.е. с правого края (шаг -1), начиная с N-го и заканчивая 2-м элементом. На каждом шаге цикла текущему элементу Xi присваивается значение предыдущего элемента Xi-1 (блок 3). В результате выполнения 2-го этапа значение предварительно сохраненного последнего элемента массива будет удалено. Значения остальных элементов массива будут смещены на одну позицию вправо. Значение первого элемента массива будет продублировано во втором элементе. На 3-м этапе алгоритма значение дополнительной переменной А, в которой хранится последний элемент исходного массива, будет занесено в первый элемент массива X1. В итоге будет выполнен циклический сдвиг элементов массива Х на одну позицию вправо.

Следует обратить внимание на граничные значения параметра цикла i, который изменяется от N до 2. При подстановке этих значений в формулу сдвига не должно происходить обращение к несуществующим элементам массива. При i =N формула сдвига принимает вид XN =XN 1, при i =2 – X2 =X1. В случае если бы правая граница параметра i была равна 1, то в формуле сдвига происходило бы обращение к несуществующему элементу массива с индексом 0: X1=X, что недопустимо.

Схематичное изображение циклического сдвига элементов массива влево на одну позицию, также выполняемого в три этапа, показано на рис. 5.4.3. Блок-схема алгоритма, выполняющего такой сдвиг, показана на рис. 5.4.4.

На 1-м этапе значение первого элемента массива X1 заносится в дополнительную переменную А (блок 1). На 2-м этапе организовывается цикл «Для» (блок 2), который перебирает элементы массива Х в прямом порядке, т.е. с левого края (шаг +1), начиная с 1-го и заканчивая N-1-м элементом. На каждом шаге цикла текущему элементу Xi присваивается значение последующего элемента массива Xi+1 (блок 3). В результате выполнения 2-го этапа значение предварительно сохраненного первого элемента массива будет удалено. Значения остальных элементов массива будут смещены на одну позицию влево. Значение последнего элемента массива будет продублировано в предпоследнем элементе. На 3-м этапе алгоритма значение дополнительной переменной А, в которой хранится первый элемент исходного массива, будет занесено в последний элемент массива XN. В итоге будет выполнен циклический сдвиг элементов массива Х на одну позицию влево.

Читайте также:  Выбор сим карты при исходящем вызове

Для выполнения циклического сдвига элементов массива на k позиций достаточно k раз выполнить алгоритм сдвига на одну позицию, т.е. добавить внешний цикл, который будет требуемое количество раз повторять сдвиг на одну позицию. Пример блок-схемы алгоритма, выполняющего циклический сдвиг элементов массива на k позиций влево, представлен на рис. 5.4.5.

Сдвиг элементов массива находит применение при удалении или добавлении элементов в массив, а также в других задачах.

Не нашли то, что искали? Воспользуйтесь поиском:

Задача

Написать функцию, которая циклически сдвигает одномерный массив вправо или влево на указанное число позиций. Сдвиг также должен быть кольцевым, то есть те элементы, которые уходят вправо или влево за пределы массива, должны помещаться с другого его конца.

Например, дан массив:
1 2 3 4 5 6
Кольцевой сдвиг вправо на 2 единицы:
5 6 1 2 3 4

Решение

  • a, m — массив (m — в функции);
  • q, p — количество единичных сдвигов (p — в функции);
  • b — переменная для хранения первого или последнего элемента массива.

Алгоритм решения задачи:

Если переменной p будет присвоено отрицательное значение, то пусть сдвиг будет выполняться влево. Если положительное, то вправо.

Абсолютное значение переменной p — это количество шагов сдвига. Если за одну итерацию внешнего цикла выполняется сдвиг на один шаг, то значение p определяет количество итераций внешнего цикла.

Далее идет описание одного шага внешнего цикла:

Если сдвиг происходит влево, то на место первого элемента становится второй, на место второго — третий и так далее. При кольцевом сдвиге на место последнего должен стать первый. Так как на место очередного элемента записывается следующий за ним, то первый элемент затрется. Поэтому его надо сохранить отдельно до выполнения внутреннего цикла. Внутренний цикл выполняется до предпоследнего элемента включительно. На предпоследнем элементе на его место записывается последний. После внутреннего цикла на место последнего элемента записывается ранее сохраненный первый элемент.

Если же сдвиг происходит вправо, то первый элемент уйдет на место второго, второй сдвинется на место третьего и т. д. В данном случае, чтобы не затирать элементы, лучше сдвигать с конца. На место последнего записать предпоследний, потом на место предпоследнего — тот что перед ним и т.д. В таком случае затирается только последний элемент, его следует сохранить до выполнения внутреннего цикла. Цикл будет выполняться от последнего элемента до второго. На последней итерации внутреннего цикла на место второго элемента запишется первый элемент. После цикла следует на место первого элемента записать ранее сохраненный последний.

Ссылка на основную публикацию
Adblock detector