Многомерные массивы
После знакомства с одномерными массивами пришло время познакомиться с многомерными. В математических вычислениях часто используются матрицы — двумерные массивы. Также можно представить кубические (трехмерные) массивы. Но на самом деле это представление не совсем верное с точки зрения организации массивов в Java. Для начала познакомимся с формой записи двумерного массива а потом уже более глубоко покопаемся в том, как на самом деле устроен двумерный массив. Итак, вариант записи достаточно простой:
1 |
char[][] graph = new char[10][10]; |
Мы объявили двумерный массив символов и создали объект, который можно использовать. Как видите он не сильно отличается от того, что мы видели раньше при знакомстве с одномерными массивами. Прежде чем мы углубимся (как я обещал) в устройство двумерного массива, напишем несложный пример, который заполнит массив символов определенным рисунком, а потом выведет его на экран.
Для начала сделаем простое заполнение массива одним символом — например ‘#’.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package edu.javacourse.array; public class MultiArray { public static void main(String[] args) { // Объявим коснтанту для размера массива int SIZE = 10; // Создаем двумерный массив char[][] graph = new char[SIZE][SIZE]; // Цикл по первой размерности (первые квадратные скобки) for (int i = 0; i < SIZE; i++) { // Цикл по второй размерности (вторые квадратные скобки) for (int j = 0; j < SIZE; j++) { graph[i][j] = '#'; } } // Теперь выводим массив на экран // Цикл по первой размерности выводит строки for (int i = 0; i < SIZE; i++) { // Цикл по второй размерности выводит колонки - вывод одной строки for (int j = 0; j < SIZE; j++) { // Используем оператор print - без перехода на следующую строку System.out.print(graph[i][j]); } // Переход на следующую строку System.out.println(); } } } |
Рассмотрим подробнее два двойных цикла (мы кстати сними уже встречались при рисовании фигур в разделе Управление порядком операций. Первый двойной цикл позволяет нам с помощью переменных i и j пройти по всем индексам массива graph и каждому его элементы присвоить значение ‘#’. Вот и вся его задача — пройтись по каждому элементу путем обращения к нужному индексу. Никаких облегчающих конструкций в Java пока нет (есть такое понятие как «замыкание», но они ожидаются в Java 8 в середине 2013 года. Вполне допускаю, что я не успею закончить курс до ее выхода и Вы сможете сказать о моих статьях что-то вроде «старье» :)).
Второй цикл предназначен для вывода массива на экран. Эту конструкцию мы тоже уже видели — в нем первый цикл отвечает просто за переход на следующую строку. А внутренний печатает одну строку без каких-либо переходов. Думаю. что Вы сможете разобраться.
Следующий пример заполняет массив символов более сложной фигурой — квадратом с пустым содержанием. Символ ‘#’ встречается здесь только по самому краю. Весь алгоритм заполнения содержится в первом цикле. условие достаточно простое — если индекс является либо первым равным 0) либо последним (равен SIZE-1), то используем символ ‘#’. Иначе — символ пробела ‘ ‘.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
package edu.javacourse.array; public class MultiArray { public static void main(String[] args) { // Объявим коснтанту для размера массива int SIZE = 10; // Создаем двумерный массив char[][] graph = new char[SIZE][SIZE]; // Цикл по первой размерности (первые квадратные скобки) for (int i = 0; i < SIZE; i++) { // Цикл по второй размерности (вторые квадратные скобки) for (int j = 0; j < SIZE; j++) { if (i == 0 || i == SIZE - 1 || j == 0 || j == SIZE - 1) { graph[i][j] = '#'; } else { graph[i][j] = ' '; } } } // Теперь выводим массив на экран // Цикл по первой размерности выводит строки for (int i = 0; i < SIZE; i++) { // Цикл по второй размерности выводит колонки - вывод одной строки for (int j = 0; j < SIZE; j++) { // Используем оператор print - без перехода на следующую строку System.out.print(graph[i][j]); } // Переход на следующую строку System.out.println(); } } } |
Думаю, что Вы сможете разобраться в этом примере самостоятельно. А после этого примера предлагаю Вам попробовать «нарисовать» фигуры, которые Вы возможно уже пробовали создавать — треугольники, ромбы и прочая. Я же хотел предложить Вам посмотреть на многомерные массивы более глубоко.
Двумерность как массивы массивов
Надеясь на то, что Вы попробовали порисовать фигуры и получили четкое понимание, что сами массивы вещь достаточно несложная — сложны именно алгоритмы, которые используют массивы для своей реализации, предлагаю посмотреть как устроены многомерные массивы.На самом деле двумерный массив не является матрицей — это массив массивов. Т.е. Вы создаете массив, внутри которого находятся указатели на одномерные массивы — массив массивов. Я могу конечно еще раз повторить эти слова, но думаю, что это вряд ли поможет, если Вы еще не поняли эту идею. Попробуйте рассмотреть это на примере кода, где написаны комментарии.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
package edu.javacourse.array; public class MultiArray { public static void main(String[] args) { // Объявим конcтанту для размера массива int SIZE = 5; // Создаем массив, в котором есть другие массивы // Причем массивы не создаются - они равны NULL char[][] graph = new char[SIZE][]; // Цикл по элементам массива - все они пока равны NULL for (int i = 0; i < graph.length; i++) { // Проверяем равенство NULL - это правда System.out.println(graph[i] == null); } for (int i = 0; i < graph.length; i++) { // Создаем случайное число от 25 до 75 для указания размера массива int size = (int)(Math.round(Math.random()*50) + 25); // Теперь создаем массив нужного размера graph[i] = new char[size]; } // Цикл по элементам массива - все они теперь проинициализированы for (int i = 0; i < graph.length; i++) { // Выводим размеры массивов, которые мы создали System.out.println(graph[i].length); } } } |
Как видите в начале програмы мы создаем массив массивов — он имеет размер SIZE, но внутри его элементы указывают на null. Первый цикл нам это может продемонстрировать — каждый элемент graph[i]равен null.
Второй цикл создает массивы случайной размерности от 25 до 75 элементов. И вот уже эти элементы и есть символы. Для создания случайного числа используется специализированный класс Math. Метод random() создает случайное число от 0 до 1, которые мы умножаем на 50 (получаем случайное число от 0 до 50) и округляем его с помощью второй функции round. К полученному целому числу прибавляем 25, тем самым сдвигая диапазон на 25 — от 25 до 75. Последним шагом является приведение полученного числа из типа long к типу int — размер массива использует тип int. Вторая строка уже создает массив случайного размера. Третий цикл печатает размеры массивов. Таким образом на самом деле мы создаем не матрицу, а массив массивов.
После недолгих размышлений Вы поймете, что трехмерный массив на самом деле это массив массивов массивов — т.е. массив, который содержит ссылки на массивы, которые уже в свою очередь содержат настоящие элементы (только учтите, что если элементами массивов являются классы, то конечные элементы без инициализации будут тоже ссылки на null).
Упрощенная конструкция инициализации массива
В заключении я хочу показать Вам конструкцию, которая позволяет присвоить элементам массива значения без сложных циклов. Не буду придумывать слова для объяснения — просто приведу пример.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
package edu.javacourse.array; public class ArrayInit { public static void main(String[] args) { // Создание одномерного массива чисел int[] a = {1, 2, 56, 78, 34, 12, 89}; // Создание двумерного массива символов // Обратите внимание на фигурные скобки для выделения // массивов внутри массива. Второй массив - пустой char[][] graph = {{'1', 'R', 'H', '&', '5', '@'}, {}, {'L', '0', 'I'}}; // Печать массива символов через конструкцию foraech // Проходим по элементам массива массивов - т.е. получаем // одномерный массив при каждом цикле for(char[] g1 : graph) { for(char g2 : g1) { // Печатаем строку из массивов System.out.print(g2); } // Переходим на следующую строку System.out.println(); } } } |
Как видите, все достаточно просто — вы перечисляете внутри фигурных скобок нужные Вам значения. В случае, если нужен многомерный массив, для каждого массива внутри массива тоже надо открыть (и потом закрыть) фигурные скобки. В случае, если нужны объекты, то Вы можете вызывать конструкторы прямо внутри скобок. Например для создания массива из трех роботов можно сделать так:
1 |
Robot[] robots = {new Robot(0,0), new Robot(10, 10), new Robot(5,24)}; |
На этом мы можем закончить знакомство с массивами. Я показал Вам вполне достаточно для того, чтобы Вы могли использовать этот инструмент в своих программах. Теперь дело за малым — надо начать писать программы и принимать решение надо ли Вам использовать массив в данной программе или нет. И если Вы сочтете это нужным сделать — небольшой справочник у Вас уже есть. Примеры из данной статьи доступны для скачивания: MultiArray1, MultiArray2, MultiArray3, ArrayInit
Самостоятельно
В качестве самостоятельной работы можете попробовать сделать несколько заданий, которые когда-то я сам делал и мне они понравились. Для их выполнения я хочу предложить вам познакомится с конструкцией, которая позволяет выводить на экран числа в определенном формате. Если вас заинтересует более подробная информация по способам форматирования, можете посмотреть их сами (правда на английском языке) прямо в документации по Java — Formatter
Например, я хочу вывести число 12 и чтобы оно занимало не 2 позиции, а 4.
Т.е. не так
1 |
12 |
А вот так
1 |
12 |
Как видите перед 12 есть еще 2 пробела. Для такого вывода используется конструкция
1 |
System.out.format("%4d\r\n", 12); |
Эта конструкция содержит внутри скобок два аргумента, Второй — это наше число 12. А вот первый гораздо интереснее — эта строка говорит о том, как именно я хочу вывести число 12 (а точнее, аргумент, который идет после строки форматирования).
Сначала идет «%4d» — это значит, что я вывожу целое число и это число (даже если оно занимает 2 символа) будет как минимум занимать 4 символа с ведущими пробелами в случае, если число цифр в нем меньше четырех.
Набор «\r\n» означает, что курсор перейдет на следующую строку. Хотя часто достаточно либо \r либо \n — это уже зависит от операционной системы, под которой вы работаете.
Что еще более интересно, вы можете использовать несколько аргументов (Java с 1.5 позволяет это). Мы пока не рассматривали эту возможность. но обязательно упомянем о ней. Для печати нескольких чисел можно написать так
1 |
System.out.format("%4d и %4d\r\n", 99, 88); |
Думаю, что вы догадались, что вывод будет вот такой:
99 и 88
Думаю, что этой информации будет достаточно.
1. В качестве тренировки создайте массив 5 на 5, заполните его числами от 1 до 25 и выведите его на экрана чтобы каждое число занимало 3 позиции. Если разобрались с выводом, то тогда задания посложнее.
2. Заполните массив числами, которые увеличиваются на 1 по спирали. Для примера массив 4 на 4 должен выглядеть вот так
1 2 3 4 |
1 2 3 4 12 13 14 5 11 16 15 6 10 9 8 7 |
3. Заполните массив «ходом коня» — надеюсь вы знаете как ходит конь в шахматах (буквой Г). Так вот существует простой алгоритм, который позволяет гарантированно заполнить доску ходом коня размерами от 5 до 70 (квадратную конечно). Т.е. сначала заполните массив числом 0, а потом на первой клетке (элементе массива) ставится число 1, на следующей, на которую прыгает конь — 2 и так до тех пор, пока не останется клеток, на которые конь не ступал. Если остались нулевые значения — значит что-то не так.
Алгоритм достаточно простой — надо прыгать на ту клетку, с которой будет меньше всего ходов.
В следующих статьях мы продолжим изучение конструкций для языка Java.
Один из ответов (на вопрос 2) — Спасибо пользователю Grif.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
/* Заполнение по спирали двумерного массива со сторонами А Х В любого размера при условии, что А = В */ public class SpiralArray { public static void main(String[] args) { // Выбираем размер стороны квадрата и рассчитываем общее количество элементов массива int Size = 8; int Max = Size * Size; /* координаты и рамки заполнения массива x[0],y[0] – нижние динамические границы x[1],y[1] – верхние динамические границы x[2],y[2] – текущие координаты x[3],y[3] – вектор смещения */ int RC = 4; int[] x = new int[RC]; int[] y = new int[RC]; // Объявляем массив для заполнения int[][] Sp = new int[Size][Size]; // Задаём начальные значения координат и рамок for (int i = 0; i < RC; i++) { x[i] = y[i] = 0; } x[1] = y[1] = Size - 1; x[3] = 1; // Заполняем массив for (int i = 0; i < Max; i++) { //Используем массив как координатное поле Sp[x[2]][y[2]] = i + 1; // Рассчитываем текущие координаты x[2] = x[2] + x[3]; y[2] = y[2] + y[3]; // Рассчёт вектора смещения if (y[3] == 0) { if ((x[2] > x[0]) && (x[3] > 0)) { x[3] = 1; } if ((x[2] < x[1]) && (x[3] < 0)) { x[3] = -1; } if ((x[2] == x[1]) && (x[3] > 0)) { x[3] = 0; x[1]--; y[3] = 1; } if ((x[2] == x[0]) && (x[3] < 0)) { x[3] = 0; x[0]++; y[3] = -1; } } if (x[3] == 0) { if ((y[2] > y[0]) && (y[3] > 0)) { y[3] = 1; } if ((y[2] < y[1]) && (y[3] < 0)) { y[3] = -1; } if ((y[2] == y[1]) && (y[3] > 0)) { y[3] = 0; y[1]--; x[3] = -1; } if ((y[2] == x[0]) && (y[3] < 0)) { y[3] = 0; y[0]++; x[3] = 1; } } } // Печать массива заполненного данными по спирали System.out.println(); for (int i = 0; i < Size; i++) { for (int j = 0; j < Size; j++) { System.out.format("%4d", Sp[j][i]); } System.out.println(); } } } |
И теперь нас ждет следующая статья: Абстрактные классы.