Заполнение двумерной матрицы по спирали
Привет, мой читатель.
На днях встретил простую на вид задачу. Как оказалось, не легко решить такую задачу за пять, и даже за 50 минут. Здесь пришлось подумать и поэкспериментировать.
Дана матрица, или, на нашем языке, двумерный массив. Его размеры не могут превышать 10х10. Они задаются пользователем и это может быть не только квадрат, но и прямоугольник. Обозначим длины сторон через N и M. Нам необходимо заполнить эту матрицу числами от 1 и по возрастающей до M*N. Прежде, чем привести код целиком, мне хотелось бы изложить ход мыслей, чтобы стало понятно как все работает. Если же тебе просто нужно решение, то ты можешь пролистать ниже, скопировать его и закрыть страницу как больше не нужную.
Стандартно, нам нужен сам массив и переменные для хранения длин сторон прямоугольного (двумерного) массива.
Также мы будем действовать по слогике, что при заполнении мы очерчиваем прямоугольники, каждый их которых на единицу меньше с каждой стороны. Если смотреть на эти прямоугольники в декартовой системе координат, то начало каждой из сторон сдвигается на 1 вправо или вниз, а конец влево или вверх. Договоримся, что оси направлены вправо и вниз от точки [0,0].
Таким образом нам нужно знать точки начала и конца очерчиваемого прямоугольника. Это и будут точки излома (поворота). Но я еще и решил пойти следующим путем. Точки конца сторон будут равняться длине стороны первого прямоугольника минус длине текущего прямоугольника.
Обозначим их следующим образом:
Ну, и, нам нужна переменная, значением которой мы будем заполнять массив, пока она не достигнет значения M*N
В цикле начинаем заполнять массив. Сначала точке a[i][j] присваиваем значение k. Это удобно тем, что если длина сторон равна 0, то мы не войдем в массив. Иначе в точку a[i][j] положим значение k, в конце же цикла инкреминируем его.
Далее вычисляем следующий шаг
- Если у нас верхняя сторона прямоугольника и мы не достигла правой стороны, то двигаемся вправо: ++j
- Если мы на правой стороне прямоугольника и не достигли нижней стороны, то двигаемся вниз: ++i
- Если мы на нижней стороне прямоугольника и не достигли левой стороны, то двигаемся влево: —j
- Иначе двигаемся вверх: —i
В конце же каждого прохода проверяем, завершился ли прямоугольник и стои ли начинать прочерчивать новый — меньший:
- Если мы находимся на второй строке
- Если мы находимся на первом столбце
- И, в случае, если чертим не прямоугльник, а вертикальную линию, если первая строка не равна последней. (этот пункт самый сложный во всем алгоритме. Его я достиг путем экспериментов)
Тогда увеличиваем отступы от краев первого прямоугольника:
Собственно это весь алгоритм. А ниже код всей программы:
Я видел более изящные решения данной задачи, наполненные математикой и побитовыми операциями. Но для понимания того, как последовательно наполняется пассив по спирали, достаточно данного алгоритма. Буду рад, если вы оставите в комментариях свои решения и поделитесь мыслями.
Источник
Алгоритм спирального заполнения массива
Внимание! Этот документ ещё не опубликован.
Задача:
Заполнить квадратную матрицу произвольного размера элементами, которые вводит пользователь. Заполнение должно производится по спирали, слева — направо — сверху — вниз.
Для реализации данной задачи использовать язык программирования Java.
Начнем с вопроса о том, чем вообще является массив данных.
В любом языке программирования используются массивы, удобные для работы с большим количеством однотипных данных. Если вам нужно обработать сотни переменных, то вызывать каждую по отдельности становится достаточно трудоемким занятием. В таких случаях проще применить массив. Массивы в Java, как и во многих других языках программирования, обозначаются квадратными скобками. Эти скобки могут располагаться справа от имени массива или от типа объектов, из которых составлен массив.
Рассмотрим пример квадратной матрицы (квадратная таблица, состоящая из строк и столбцов на пересечении которых находятся её элементы). Количество строк и столбцов матрицы задают ее размер. Общий вид матрицы размером n x n ( n — количество строк, количество столбцов), выглядит следующим образом:
Каждый элемент матрицы имеет свой индекс, где первая цифра обозначает номер строки на которой находится элемент, а вторая — номер столбца.
В задачах по программированию очень часто встречается необходимость заполнить массив данными и вывести их потом на экран. В первоначальной задаче необходимо заполнить массив следующим образом: начиная с правого верхнего угла, двигаясь по спирали присвоить каждому элементу массива значение, которое ввел пользователь.
Схематически это выглядит так:
То есть, в каждом «квадрате» матрицы нам нужно проходить 4 шага, заполняя 4 стороны «квадрата».
Создадим класс, в котором реализуем 2 метода: само заполнение матрицы и ее вывод. Объявим переменную, в которой будет содержаться количество строк(столбцов) матрицы.
Займемся написанием кода метода заполнения матрицы. Он должен возвращать массив String. Объявление метода будет выглядеть следующим образом:
По мере прохождения сторон «квадрата» мы проходим 4 итерации, в ходе которых проходим N элементов нашего массива. Это повторяется, пока не закончатся «квадраты», то есть N/2 (при этом, если N нечетное, то округление должно производиться в большую сторону) раз. Следовательно, можно выделить 3 цикла:
При условии того, что:
Так как N была объявлена как финальная переменная и не может быть изменена.
Теперь, в зависимости от номера итерации (стороны «квадрата»), нужно определять индексы элемента массива для записи. Введем дополнительную переменную типа int, которая будет увеличиваться с каждым пройденным «квадратом». Создадим объект класса Scanner, подключив нужную библиотеку.
Сейчас код выглядит так:
Все индексы посчитаны, осталось написать метод для вывода матрицы. Для упрощения воспользуемся стандартным методом для вывода одномерного массива. Для этого подключим требуемую библиотеку:
Код данного метода будет выглядеть следующим образом:
Он принимает массив String и выводит в консоль по каждой строке все элементы массива, ничего не возвращая.
Теперь осталось только использовать данный класс и испытать его методы. Для этого создадим главный класс, в котором объявим переменную типа int для хранения размера массива,который задаст пользователь и считаем ее.
Создадим объект класса FillMatrix и вызовем созданные методы. Полностью код главного класса выглядит так:
Результат выполнения программы можно увидеть ниже:
Источник
Заполнить массив спирально(начиная от центра по часовой стрелке) [дубликат]
Написала программу, которая заполняет массив спирально, но против часовой стрелки. Размер массива всегда нечетный. Не могу довести до ума.
Скажите что нужно поменять?
То есть должно получиться вот так:
2 ответа 2
В этом аналогичном вопросе есть мой ответ.
Если оставить в стороне присутствующие там мои рассуждения и обратиться только к коду, то код написан таким образом, что он легко позволяет заполнить массив по спирали независимо от количества элементов (четное или нечетное), легко читаем и легко модифицируем.
Этот код заполняет массив, начиная с 1 по внешнему «кругу» массива. То есть значения, которыми заполняются элементы массива увеличиваются от 1 до N * N.
Из этого кода получить вашу спираль очень просто!
Достаточно изменять исходное значение инициализатора от N * N до 1, а заполнять элементы массива точно также, начиная с «внешнего круга» массива.
Если в том примере заполнение идет от точки с координатами [0][0] в направлении вправо, то чтобы получить вашу спираль, нужно идти от точки с координатами [0][N-1] в направлении влево.
Только вместо массива я использовал в демонстрационной программе вектор, так как C++ не поддерживает массивы с переменной длиной, которые вы в своей программе используете.
Вот как может выглядеть программа
Ее вывод на консоль следующий:
Что касается вашего кода, то помимо того, что не следует использовать массивы с переменным размером, так как они не совместимы со стандартом C++, то я вижу уже во втором case ошибку. Как я понимаю, в этом case индекс x должен быть увеличен, а не уменьшен так как следующий выводимый элемент должен быть расположен в строке ниже исходной строки. К тому же в вашем подходе можно запутаться, когда имеешь дело не только с нечетным числом элементов массива, но и с четным числом элементов массива.
Источник
Требуется вывести двумерный массив спиралью
Доброго времени суток, мне очень нужна помощь. Я вообще не понимаю как работать с двумерными массивами, а именно решать задачи. Вот одна из них:
Дано число n. Создайте массив A[2*n+1][2*n+1] и заполните его по спирали, начиная с числа 0 в центральной клетке A[n+1][n+1]. Спираль выходит вверх, далее закручивается против часовой стрелки.
Входные данные
Программа получает на вход одно число n.
Выходные данные
Программа должна вывести полученный массив, отводя на вывод каждого числа ровно 3 символа.
Примеры
входные данные
2
выходные данные
12 11 10 9 24
13 2 1 8 23
14 3 0 7 22
15 4 5 6 21
16 17 18 19 20
Пожалуйста, можете доходчиво объяснить первокурснику, как это делается? Может посоветуете, где можно учить язык? (видео, учебники). Через 19 дней экзамен, а я не только стэки и дэки не знаю, но и двумерные массивы. Буду благодарна за помощь!
Помощь в написании контрольных, курсовых и дипломных работ здесь.
Как в С++ создать и вывести двумерный массив размером N на N из нулей и единиц, заполненый спиралью
Подскажите пожалуйста, как на языке С++ создать и вывести двумерный массив размером N на N.
Двумерный массив, заполнение спиралью
Вечер добрый. Нужна немного помощь по коду и хотя бы простое объяснение по нему же. Пытаюсь.
Заполнить двумерный массив спиралью
Всем доброго времени суток. Решал задачи на двумерные массивы, как вдруг столкнулся с этой. Мой код.
Требуется повернуть двумерный массив на 90 градусов
Здравствуйте, у меня проблема. Программа поворачивает массив, но не вывод последнюю строку. (.
Источник
Альтернативный способ заполнения «спиральной матрицы»
В процессе изучения основ алгоритмизации и программирования в качестве студента еще в середине 2000х мне попалась довольно известная всем задача по заполнению «спиральной» матрицы. Суть состоит в том, чтобы начиная с позиции [1, 1], продвигаясь по часовой стрелке, заполнить квадратную матрицу заданной величины числами в возрастающем порядке. На ее решение было потрачено около двух часов.
Собственно алгоритм заполнения был тривиален, циклы, в общей сложности состоящие из N 2 итерации, предполагали прохождение по всем элементам массива в требуемом порядке, при этом увеличивая значение итератора на 1 и заполняя им текущий элемент матрицы. Маршрут начинался с элемента [1, 1], далее продвигается по горизонтали до правого верхнего элемента [1, N], после – вниз до нижнего правого угла [N, N], затем до левого нижнего угла [N, 1] и завершал первый круг на столбец ниже отправной точки [2, 1]. В дальнейшем, такое же круговое движение происходило уже в следующем внутреннем круге, и так далее до центра матрицы.
Интернет, в лице различных форумов, сообществ, сайтов по посвященных данному направлению изобилует конкретными решениями на различных языках программирования, коих человечество за полвека изобрело немало. В то же время, представленный выше механизм заполнения матрицы является основным и наиболее эффективным как с точки зрения человека, так и точки зрения вычислительной машины (если она имеет точку зрения).
Через некоторое время после успешного решения задачи, на волне переоценки собственных способностей, я задумался: а возможно ли разработать универсальную формулу, позволяющей на основании размера матрицы и координат ячейки вычислить ее значение согласно условию задачи – то есть в конечном итоге заполнить массив, перебирая элементы «традиционным» путем двумя вложенными циклами от 1 до N без использования счетчика.
Но, как вы могли догадаться, ничего путного у меня не вышло, и данная затея была заброшена в пользу других, более насущных проблем в изучении программирования.
Спустя эти самые 18 лет, перебирая наиболее интересные задачи и пути их решения для обучения уже следующего поколения представителей неординарной профессии «программист», меня заинтересовала одна статья на ресурсе «Хабр» (https://habr.com/ru/post/261773), описывающая процесс создания формулы для вычисления количества дней в заданном месяце без использования каких-либо условных операторов, циклов и заранее подготовленных данных.
Автору респект, способ решения данной задачи был настолько интересен и оригинален, что мы изучили его с группой моих студентов на одном из занятий.
Именно после этого я вспомнил ту самую спиральную матрицу и решился на очередную попытку выведения универсальной формулы.
(Следует отметить, что на одном из сайтов я все же обнаружил достаточно короткое решение в виде функции, которое, однако, содержало два условных перехода).
Итак, задача: разработать алгоритм для вычисления значения элемента спиральной матрицы на основании его координат [i, j] и размера самой матрицы, пользуясь только простыми арифметическими вычислениями. Исключение составляет модуль — абсолютное значение числа.
Условие: никаких условных (прошу прощения за тавтологию) переходов или заготовленных данных в виде массивов, словарей и т.д.
Практическая полезность: никакая. Традиционным способом данная задача решается гораздо быстрее, при этом мозг программиста в процессе разработки функции не травмируется.
Очевидно, такую непростую задачу необходимо разделить на несколько этапов, или, если угодно, выделить основные элементы.
1) Найти закономерность между «координатами» ячейки и ее порядковым номером в первом внешнем «кольце», и вывести соответствующую форму.
2) Найти связь между, опять же, координатами ячейки и номером кольца, в котором она находится. Составить формулу.
3) Связать между собой два алгоритма для выведения общей формулы вычисления значения элемента массива.
Входные данные: N – размер квадратной матрицы (массива).
1 этап. Заполнение внешнего кольца. Для начала попытаемся вывести формулу вычисления значения ячейки для внешнего кольца массива, в котором отсчет начинается с ячейки [1, 1] и двигается по часовой стрелке поворачивая на углах [1, N], [N, N]. Заканчивается на строку ниже начальной точки, т.е. [2, 1].
Математический аппарат будет разрабатываться параллельно с написанием кода на языке программирования, в качестве которого выбираем C++, как наиболее распространенный и удобный язык программирования (здесь конечно читатели могут поспорить, но классика есть классика). Но, в принципе, выведенная формула не должна иметь привязки языке.
Для наглядного изучения процесса напишем соответствующий скрипт, объявив в нем массив размером 5×5 (назовем его “a”), элементы которого будем перебирать традиционным способом двумя вложенными циклами от 1 до N. На данном этапе будем работать только с внешним кольцом.
Очевидно, что, по крайней мере, до правого нижнего угла внешнего кольца суммарное значение координат (i + j) увеличивается ровно на 1 с каждым шагом. Однако первый элемент в таком случае равняется 2, E1,2 = 3 и т.д. Поэтому необходимо уменьшить значение суммы (i + j) на один. В результате введем переменную Xs = i + j — 1, которая часто будет использоваться в дальнейших вычислениях. Пишем код:
В результате запуска первого скрипта получаем массив:
Как видим заполнение от начала до правого нижнего угла произведено корректно. Однако далее у нас происходит уменьшение шага, вместо увеличения.
Очевидно a[ik][jk] = Xs нуждается в дополнении, при котором все его значения до [5, 5] останутся неизменными, но после этой точки начнут увеличиваться на 1.
Но для начала постараемся привести в норму вторую часть кольца, которая заполнялась бы с ячейки (i = 5, j = 4) начиная со значения 10. В данном случае это легко сделать, лишь вычитая от общего количества элементов первого кольца увеличенного на два (равняется периметру первого кольца N * 4 — 4 = 16 плюс 2) значение Xs.
То есть a1,2 = 4N – 4 + 2 – Xs = 4N – Xs — 2.
Однако теперь у нас правильно заполнилась только левая и нижняя стороны кольца, а противоположенные элементы – нет.
На текущем этапе у нас имеются две формулы, пригодные только для определенных участков массива.
1. ai,j = Xs = i + j — 1; действует от [1, 1] до [N, N].
2. ai,j = 4*N – 2 — X; действует от [N, N] до [2, 1].
Самым простым решением было бы использование условного перехода, однако это не соответствует нашей начальной установке. Здесь необходимо дополнительно отметить, что из всех стандартных кусочно-заданных функций, как отмечено выше, мы будем использовать только модуль (y = |x|) и формулы собственной разработки.
В этой связи, необходимо привести наше уравнение в вид:
Здесь функция F1 принимает значение 1 при i, j между [1, 1] … [N, N] , в остальных случаях – 0. В свою очередь, F2 , наоборот, принимает значение 1 когда ячейка находится в диапазоне [N, N — 1] … [2, 1], и 0 между [1, 1] … [N, N].
В качестве аргумента функции используется переменная switcher, вычисляемая на основе значении i и j, и опять же, принимающая различные значения в вышеуказанных диапазонах.
Неплохим вариантом выглядит идея получения значений -1, 0 и/или 1 от манипуляций с координатами элемента. Тогда F1 и F2 содержали бы простые арифметические операции.
Итак, мы уже использовали сложение i и j, но оно всегда дает положительное число. Почему бы сейчас не попробовать вычитание?
Заменим в нашем предыдущем листинге значение a[ik][jk].
Наиболее легким вариантом видится целочисленное деление на самого себя, но в таком случае мы столкнемся с ошибкой деления на 0, поскольку а[1][1] и а[N][N] уже содержат 0. В данном случае можно прибавить ко всем значениям N и осуществить целочисленное деление на N. Введенную переменную назовем switcher.
Теперь осталось немногое для завершения данного этапа, а именно создать функции F1 и F2. F1 должна возвращать такое же значение, какое ей передали в качестве аргумента, т.е. F1 (switcher) = switcher. В таком случае F1 (switcher) * Xs работает только для диапазона от [1, 1] до [N, N], в остальных случаях равняется нулю. Вторая часть уравнения, должна действовать наоборот. В таком случае она должна возвращать значение по модулю switcher – 1, т.е. F2.(switcher) = |switcher – 1|.
Отлично, этот этап завершен успешно, мы получили требуемые значения для внешнего кольца массива.
2 этап. Альтернативная система координат.
Нам удалось заполнить внешнее первое кольцо требуемыми данными. Однако, что произойдет, если мы снимем фильтр, и попытаемся вычислить данные для остальных элементов массива? Для этого необходимо удалить участок кода if (!(ik == 0 || ik == N — 1 || jk == 0 || jk == N — 1)) continue;
Чего и следовало ожидать, следующее кольцо матрицы представлено неверными значениями, что, однако, не скрывает некую системность в числах. Так, по крайней мере, сохранен возрастающий порядок. Более того, прирост значений составляет 1, за исключением стыка между нижним правым элементом и следующей ячейкой. Данный признак является благоприятным знаком того, что мы находимся на верном пути.
Очевидно, в ячейке [2, 2] вместо 03 должно находиться число 17, следующее значение после конечного элемента внешнего круга. Небольшое размышление показывает, что необходимо ввести определенный коэффициент, выправляющий значение ячейки в зависимости от «глубины залегания» кольца в котором он находится. Т.е. требуется вычислить степень удаленности элемента массива от центра (или наоборот) на основании номера строки и номера столбца.
Для этого, введем альтернативную систему координат. Так, обычная нумерация ячеек в матрице начинается с левого верхнего угла и аналогична двухмерной системе координат, с центром в указанном месте. Для тех, кто еще помнит древний Turbo Pascal с синим экраном – графики в нем чертились именно таким образом (да и в большинстве современных графических сред разработки ПО принята подобная система координат при работе с визуальными компонентами).
Мы же, перенесем начало координат в центр нашей матрицы следующим образом:
Поскольку нам требуется только расстояние ячейки от центра, мы берем только абсолютные значения.
Сейчас элементы массива заполнены номерами строк относительно его центра. Если мы проделаем ту же самую операцию со стоблцами, мы получим такой же массив, только с вертикальным распределением значений.
Введем две новые переменные Ic, Jc (c обозначает center).
Теперь необходимо определить расстояние ячейки от центра, независимо в каком направлении она удалена.
Опять же, самый простой способ что-то узнать – попробовать вывести суммы номеров строк и столбцов.
Пока ничего путного не выходит, но проявляется закономерность – чем дальше от центра, тем больше сумма. В дальнейшем это пригодится. Теперь рассмотрим, что нам даст разница между Ic и Jc.
Пока также неопределенно. Но если изучить расположение чисел, и сложить поэлементно их абсолютные значения с предыдущим массивом, то получим матрицу с уникальными числами для каждого кольца.
Уже проявляется верное направление. Только наши числа оказались вдвое больше и расположены в порядке возрастания с центра до краев, а не наоборот, как было бы удобно для проведения основных вычислений. К счастью решается эта проблема легко, путем целочисленного деления на два и инверсии. Проделанные вычисления запишем в новую переменную Ring.
Замечательно. Однако, при размере матрицы N = 6 данная формула работает не совсем корректно, так она в качестве центра считает только один элемент (что является справедливым для матриц с нечетной размерностью, как в предыдущих примерах).
При четном размере центральный квадрат из четырех элементов должен считаться центром матрицы. Возникает вопрос: как это реализовать? Здесь к нам опять на помощь приходит целочисленное деление и кусочно-заданная функция.
Но для этого вернемся немного назад, к вычислению Ic и Jc. Попробуем запустить наш скрипт при N = 6, и посмотрим значения Ic = |i — N / 2 — 1|.
В данный момент требуется привести массив в симметричную форму. Легче всего это сделать, увеличив значение на 1 всех строк после 3-й (то есть вторую половину).
Вторая часть данного выражения работает только при четном N, и приводит номера строк в симметричный вид по горизонтали.
Тоже самое проделываем и Jc.
В итоге получаем симметричную матрицу уже по вертикали. Теперь проверяем значение Ring для матрицы с размером 6.
Все работает нормально. Второй этап завершен.
3 этап. Соединение. На данном этапе мы объединим полученные данные и выведем искомую матрицу (через формулу вычисления значения заданной ячейки).
Но для начала, вернемся к первому этапу и выведем на экран:
Дальнейший порядок действий представляется следующим образом: привести значения элементов внутренних колец к «спиральному» виду (т.е. заполнить их начинания с верхнего левого угла по спирали возрастающими значениями от 1), далее вычислить коэффициент прироста, которой обеспечит нормальный переход от одного кольца к нижестоящему (в нашем примере от ячейки [1,2] к ячейке [2,2], при этом меняя значение с 16 на 17)
Описывать естественным языком или даже академическим стилем математические вычисления крайне тяжело, но еще сложнее понимать чьи-то не всегда ясные наработки изложенные подобным образом. Поэтому если вы столкнулись с определенными трудностями в понимании материала, просто читайте дальше и следите за вычислениями – скоро все будет ясно.
Поскольку при работе с кольцами нижнего уровня примерный алгоритм вычислений сохраняется, мы можем уменьшить значение Xs (содержит сумму номера строки и столбца) следующим образом, в соответствии с номером кольца в котором находится очередная ячейка.
Xs = (i – Ring) + (j – Ring) – 1.
Теперь попробуем вывести
Как вы могли заметить верхние и правые элементы внутренних колец пришли в требуемое значение. Однако нижние и левые стороны приняли гораздо большие значения, чем ожидалось. Это связано с тем, что выражение 4 * N — 2 — Xs во второй части функции вычисляет значения исходя из размера внешнего кольца, которое нужно уменьшить, заменив на N – 2 * Ring. То есть формула будет работать в соответствии с размером текущего кольца.
Все правильно, с первой задачей данного этапа мы справились . Остается последний шаг – вычислить коэффициент прироста, о котором говорилось выше, и связать между собой все кольца.
Как можно заметить, очередное кольцо начинается со значения равного количеству ячеек всех вышестоящих колец плюс один. Делается это очень простым способом:
Coef = N 2 – (N – 2Ring) 2
Воспользовавшись правилами разложения квадратов разницы элементов ((a−b) 2 =a 2 −2ab+b 2 ), можно сократить до 4Ring(N — Ring).
Теперь этот коэффициент нужно добавить к нашей основной формуле.
Полный код участка вычисления значений представлены следующим образом:
Можно конечно попробовать развернуть единую формулу, заменив дополнительные переменные выражениями, основанными только на i, j и N, и попытаться сократить ее математическими методами. Но поверьте мне, я пытался, получилось нечто такое невообразимое (на половину страницы), что я решил оставить все как есть.
(PS. Не работает только при N = 1, так как возникает ошибка деления на ноль. Но как говорится, «чуть-чуть – не считается»).
Источник