$ sudo teach IT
МОДУЛЬ 5 · УРОК 4

Многомерные массивы: таблицы и матрицы

Массивы массивов — создаём таблицы, работаем с матрицами и реализуем игру «Морской бой»

~30 минут Для новичков Java Массивы

Что такое многомерные массивы?

До этого мы работали с одномерными массивами — линейными списками элементов. Но реальный мир часто устроен не линейно. Шахматная доска — это не список, а таблица 8×8. Расписание занятий — это таблица с днями недели и парами. Экран компьютера — это таблица пикселей шириной тысячи столбцов и высотой тысячи строк.

Двумерный массив (или массив массивов) — это структура данных, которая представляет собой таблицу с строками и столбцами. Каждый элемент массива определяется двумя индексами: номером строки и номером столбца.

Аналогия: Одномерный массив — это ряд шкафчиков в коридоре (номер 1, 2, 3...). Двумерный массив — это стеллаж с полками: чтобы найти нужную вещь, нужно сказать «3-я полка, 2-й шкафчик». Два числа определяют положение.

Визуально двумерный массив выглядит как таблица:

    Столбец 0   Столбец 1   Столбец 2   Столбец 3
Строка 0  [  1  ]     [  2  ]     [  3  ]     [  4  ]
Строка 1  [  5  ]     [  6  ]     [  7  ]     [  8  ]
Строка 2  [  9  ]     [ 10  ]     [ 11  ]     [ 12  ]

Обратите внимание: сначала идёт индекс строки (горизонтально), потом — столбца (вертикально). Это как координаты: (строка, столбец).

Такие структуры используются повсюду:

  • Таблицы данных — оценки учеников (строки — ученики, столбцы — предметы)
  • Изображения — каждый пиксель задан координатами (x, y)
  • Карты — игровые карты, географические координаты
  • Матрицы — математические операции, 3D-графика
  • Таблицы БД — строки и столбцы реляционных баз данных

Создание двумерных массивов

Способ 1: Указание размера

Создаём таблицу с фиксированным количеством строк и столбцов:

int[][] matrix = new int[3][4];  // 3 строки, 4 столбца
double[][] grid = new double[5][5];  // квадратная матрица 5×5
String[][] table = new String[2][3];  // таблица строк 2×3

Все элементы автоматически заполняются значениями по умолчанию: 0 для чисел, null для ссылок, false для boolean.

int[][] matrix = new int[2][3];
System.out.println(matrix[0][0]);  // 0
System.out.println(matrix[0][1]);  // 0
System.out.println(matrix[1][2]);  // 0

Способ 2: Литерал (сразу с данными)

Можно сразу задать значения через вложенные фигурные скобки:

int[][] matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

System.out.println(matrix[0][0]);  // 1
System.out.println(matrix[1][2]);  // 6
System.out.println(matrix[2][1]);  // 8

Каждая внутренняя строка {...} — это одна строка таблицы. Java автоматически определяет количество строк и столбцов.

Пример: Таблица умножения:

int[][] multiplication = {
    {1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
    {2, 4, 6, 8, 10, 12, 14, 16, 18, 20},
    {3, 6, 9, 12, 15, 18, 21, 24, 27, 30}
};

System.out.println(multiplication[2][4]);  // 15 (3 × 5)

Способ 3: new + инициализация

int[][] arr;
arr = new int[][] {
    {10, 20},
    {30, 40}
};

Как устроено в памяти: Двумерный массив в Java — это массив ссылок. Внешний массив содержит ссылки на внутренние массивы (строки). Каждый внутренний массив — это отдельный объект в памяти. Именно поэтому строки могут быть разной длины!

Доступ к элементам: два индекса

Чтобы обратиться к элементу двумерного массива, нужно указать оба индекса: matrix[строка][столбец]. Индексы, как и в одномерных массивах, начинаются с 0.

int[][] scores = {
    {5, 4, 3},   // оценки Анны
    {4, 5, 5},   // оценки Бориса
    {3, 3, 4}    // оценки Виктора
};

// Анна (строка 0), математика (столбец 0)
System.out.println("Оценка Анны по математике: " + scores[0][0]);  // 5

// Борис (строка 1), физика (столбец 1)
System.out.println("Оценка Бориса по физике: " + scores[1][1]);  // 5

// Виктор (строка 2), русский (столбец 2)
System.out.println("Оценка Виктора по русскому: " + scores[2][2]);  // 4

Можно изменять элементы через индексы:

int[][] grid = new int[3][3];

grid[0][0] = 1;
grid[0][1] = 2;
grid[0][2] = 3;
grid[1][0] = 4;
grid[1][1] = 5;
grid[1][2] = 6;
grid[2][0] = 7;
grid[2][1] = 8;
grid[2][2] = 9;

System.out.println(grid[1][1]);  // 5

Свойства двумерного массива:

int[][] arr = new int[3][5];

System.out.println(arr.length);       // 3 — количество строк
System.out.println(arr[0].length);    // 5 — количество столбцов в строке 0
System.out.println(arr[2].length);    // 5 — количество столбцов в строке 2

Ошибка новичков: Путают порядок индексов. Помните: matrix[строка][столбец]. Первый индекс — строка (горизонталь), второй — столбец (вертикаль). Если написать наоборот — получите неверный элемент или ошибку.

Перебор двумерных массивов

Для обхода всех элементов двумерного массива используются вложенные циклы: внешний проходит по строкам, внутренний — по столбцам.

int[][] matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

for (int row = 0; row < matrix.length; row++) {
    for (int col = 0; col < matrix[row].length; col++) {
        System.out.print(matrix[row][col] + " ");
    }
    System.out.println();
}
// Вывод:
// 1 2 3
// 4 5 6
// 7 8 9

Как это работает пошагово:

  • Внешний цикл: row = 0 → внутренний цикл перебирает все столбцы строки 0
  • Внешний цикл: row = 1 → внутренний цикл перебирает все столбцы строки 1
  • Внешний цикл: row = 2 → внутренний цикл перебирает все столбцы строки 2

Вариант с for-each (вложенными):

int[][] matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

for (int[] row : matrix) {       // row — это массив (строка)
    for (int cell : row) {       // cell — это элемент (ячейка)
        System.out.print(cell + " ");
    }
    System.out.println();
}
// Вывод:
// 1 2 3
// 4 5 6
// 7 8 9

Пример: Подсчёт суммы всех элементов:

int[][] data = {
    {10, 20, 30},
    {40, 50, 60}
};

int total = 0;
for (int[] row : data) {
    for (int val : row) {
        total += val;
    }
}

System.out.println("Сумма всех элементов: " + total);  // 210

Пример: Поиск максимального элемента:

int[][] matrix = {
    {3, 7, 2},
    {9, 1, 5},
    {4, 8, 6}
};

int max = matrix[0][0];
int maxRow = 0;
int maxCol = 0;

for (int row = 0; row < matrix.length; row++) {
    for (int col = 0; col < matrix[row].length; col++) {
        if (matrix[row][col] > max) {
            max = matrix[row][col];
            maxRow = row;
            maxCol = col;
        }
    }
}

System.out.println("Максимум: " + max + " на позиции [" + maxRow + "][" + maxCol + "]");
// Вывод: Максимум: 9 на позиции [1][0]

Неравномерные (jagged) массивы

В Java строки двумерного массива могут быть разной длины! Это называется «зубчатый массив» (jagged array). Такая особенность связана с тем, как устроены массивы в памяти: внешний массив хранит ссылки на внутренние массивы, и каждый из них — отдельный объект.

int[][] jagged = new int[3][];
jagged[0] = new int[2];  // строка 0: 2 столбца
jagged[1] = new int[4];  // строка 1: 4 столбца
jagged[2] = new int[1];  // строка 2: 1 столбец

jagged[0][0] = 10;
jagged[0][1] = 20;
jagged[1][0] = 30;
jagged[1][1] = 40;
jagged[1][2] = 50;
jagged[1][3] = 60;
jagged[2][0] = 70;

System.out.println(jagged[0].length);  // 2
System.out.println(jagged[1].length);  // 4
System.out.println(jagged[2].length);  // 1

Визуально:

jagged[0]: [10, 20]
jagged[1]: [30, 40, 50, 60]
jagged[2]: [70]

Литеральная запись зубчатого массива:

int[][] pyramid = {
    {1},
    {2, 3},
    {4, 5, 6},
    {7, 8, 9, 10}
};

for (int row = 0; row < pyramid.length; row++) {
    for (int col = 0; col < pyramid[row].length; col++) {
        System.out.print(pyramid[row][col] + " ");
    }
    System.out.println();
}
// Вывод:
// 1
// 2 3
// 4 5 6
// 7 8 9 10

Практический пример: Расписание — у разных групп разное количество пар:

String[][] schedule = {
    {"Математика", "Физика", "Информатика"},          // группа А: 3 пары
    {"Русский язык", "Литература"},                    // группа Б: 2 пары
    {"История", "Английский", "Химия", "Биология"}    // группа В: 4 пары
};

for (int group = 0; group < schedule.length; group++) {
    System.out.print("Группа " + (group + 1) + ": ");
    for (int lesson = 0; lesson < schedule[group].length; lesson++) {
        System.out.print(schedule[group][lesson]);
        if (lesson < schedule[group].length - 1) {
            System.out.print(", ");
        }
    }
    System.out.println();
}
// Вывод:
// Группа 1: Математика, Физика, Информатика
// Группа 2: Русский язык, Литература
// Группа 3: История, Английский, Химия, Биология

Совет: При переборе зубчатого массива всегда используйте matrix[row].length для внутреннего цикла, а не фиксированное число. Это гарантирует, что вы обработаете все элементы независимо от длины строки.

Вывод двумерных массивов

Для красивого вывода двумерного массива используется метод Arrays.deepToString():

import java.util.Arrays;

int[][] matrix = {
    {1, 2, 3},
    {4, 5, 6}
};

System.out.println(Arrays.deepToString(matrix));
// Вывод: [[1, 2, 3], [4, 5, 6]]

Для форматированного вывода в виде таблицы используйте цикл:

int[][] matrix = {
    {10, 20, 30},
    {40, 50, 60},
    {70, 80, 90}
};

for (int[] row : matrix) {
    for (int val : row) {
        System.out.printf("%4d", val);
    }
    System.out.println();
}
// Вывод:
//   10  20  30
//   40  50  60
//   70  80  90

Вывод с номерами строк и столбцов:

int[][] matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

System.out.print("    ");
for (int col = 0; col < matrix[0].length; col++) {
    System.out.printf("  [%d]", col);
}
System.out.println();

for (int row = 0; row < matrix.length; row++) {
    System.out.printf("[%d] ", row);
    for (int col = 0; col < matrix[row].length; col++) {
        System.out.printf(" %3d ", matrix[row][col]);
    }
    System.out.println();
}
// Вывод:
//        [0]   [1]   [2]
// [0]    1     2     3
// [1]    4     5     6
// [2]    7     8     9

Операции с матрицами

Сложение двух матриц

Чтобы сложить две матрицы, нужно просто прибавить элементы с соответствующими индексами. Матрицы должны быть одного размера!

import java.util.Arrays;

int[][] a = {
    {1, 2, 3},
    {4, 5, 6}
};

int[][] b = {
    {10, 20, 30},
    {40, 50, 60}
};

int[][] sum = new int[a.length][a[0].length];

for (int row = 0; row < a.length; row++) {
    for (int col = 0; col < a[0].length; col++) {
        sum[row][col] = a[row][col] + b[row][col];
    }
}

System.out.println("A: " + Arrays.deepToString(a));
System.out.println("B: " + Arrays.deepToString(b));
System.out.println("A+B: " + Arrays.deepToString(sum));
// Вывод:
// A: [[1, 2, 3], [4, 5, 6]]
// B: [[10, 20, 30], [40, 50, 60]]
// A+B: [[11, 22, 33], [44, 55, 66]]

Умножение матрицы на число

import java.util.Arrays;

int[][] matrix = {
    {1, 2, 3},
    {4, 5, 6}
};

int scalar = 3;

int[][] result = new int[matrix.length][matrix[0].length];

for (int row = 0; row < matrix.length; row++) {
    for (int col = 0; col < matrix[0].length; col++) {
        result[row][col] = matrix[row][col] * scalar;
    }
}

System.out.println(Arrays.deepToString(result));
// Вывод: [[3, 6, 9], [12, 15, 18]]

Умножение матриц

Умножение матриц — одна из ключевых операций в математике и программировании. Правило: элемент результата [i][j] равен сумме произведений элементов i-й строки первой матрицы на j-й столбец второй матрицы.

Формула: C[i][j] = Σ (A[i][k] * B[k][j]) для всех k

import java.util.Arrays;

int[][] A = {
    {1, 2},
    {3, 4}
};

int[][] B = {
    {5, 6},
    {7, 8}
};

int[][] C = new int[2][2];

for (int i = 0; i < 2; i++) {
    for (int j = 0; j < 2; j++) {
        for (int k = 0; k < 2; k++) {
            C[i][j] += A[i][k] * B[k][j];
        }
    }
}

System.out.println("A: " + Arrays.deepToString(A));
System.out.println("B: " + Arrays.deepToString(B));
System.out.println("A×B: " + Arrays.deepToString(C));
// Вывод:
// A: [[1, 2], [3, 4]]
// B: [[5, 6], [7, 8]]
// A×B: [[19, 22], [43, 50]]

Разберём C[0][0] = 19:

  • A[0][0] × B[0][0] = 1 × 5 = 5
  • A[0][1] × B[1][0] = 2 × 7 = 14
  • 5 + 14 = 19

Разберём C[1][0] = 43:

  • A[1][0] × B[0][0] = 3 × 5 = 15
  • A[1][1] × B[1][0] = 4 × 7 = 28
  • 15 + 28 = 43

Транспонирование матрицы

Транспонирование — замена строк на столбцы и наоборот:

import java.util.Arrays;

int[][] matrix = {
    {1, 2, 3},
    {4, 5, 6}
};

int[][] transposed = new int[matrix[0].length][matrix.length];

for (int row = 0; row < matrix.length; row++) {
    for (int col = 0; col < matrix[row].length; col++) {
        transposed[col][row] = matrix[row][col];
    }
}

System.out.println("Оригинал: " + Arrays.deepToString(matrix));
System.out.println("Транспонированная: " + Arrays.deepToString(transposed));
// Вывод:
// Оригинал: [[1, 2, 3], [4, 5, 6]]
// Транспонированная: [[1, 4], [2, 5], [3, 6]]

Практический проект: Морской бой (упрощённый)

Давайте применим знания о двумерных массивах на практике, создав упрощённую игру «Морской бой». Это отличная задача для закрепления навыков работы с таблицами.

Шаг 1: Создание игрового поля

Поле — это квадрат 10×10. Используем символы:

  • '~' — вода (пустая клетка)
  • 'S' — корабль
  • 'X' — попадание
  • 'O' — промах
char[][] field = new char[10][10];

for (int row = 0; row < 10; row++) {
    for (int col = 0; col < 10; col++) {
        field[row][col] = '~';
    }
}

Шаг 2: Размещение кораблей

void placeShip(char[][] field, int row, int col, int size, boolean horizontal) {
    for (int i = 0; i < size; i++) {
        if (horizontal) {
            field[row][col + i] = 'S';
        } else {
            field[row + i][col] = 'S';
        }
    }
}

placeShip(field, 0, 0, 3, true);   // горизонтальный корабль длины 3
placeShip(field, 2, 5, 2, false);  // вертикальный корабль длины 2
placeShip(field, 5, 1, 4, true);   // горизонтальный корабль длины 4

Шаг 3: Вывод поля

void printField(char[][] field) {
    System.out.print("  ");
    for (int col = 0; col < field[0].length; col++) {
        System.out.print(col + " ");
    }
    System.out.println();

    for (int row = 0; row < field.length; row++) {
        System.out.print((char)('A' + row) + " ");
        for (int col = 0; col < field[row].length; col++) {
            System.out.print(field[row][col] + " ");
        }
        System.out.println();
    }
}

printField(field);
// Вывод:
//   0 1 2 3 4 5 6 7 8 9
// A S S S ~ ~ ~ ~ ~ ~
// B ~ ~ ~ ~ ~ ~ ~ ~ ~
// C ~ ~ ~ ~ ~ S S ~ ~
// D ~ ~ ~ ~ ~ S ~ ~ ~
// E ~ ~ ~ ~ ~ ~ ~ ~ ~
// F ~ S S S S ~ ~ ~ ~
// G ~ ~ ~ ~ ~ ~ ~ ~ ~
// H ~ ~ ~ ~ ~ ~ ~ ~ ~
// I ~ ~ ~ ~ ~ ~ ~ ~ ~
// J ~ ~ ~ ~ ~ ~ ~ ~ ~

Шаг 4: Обработка выстрела

String shoot(char[][] field, int row, int col) {
    if (field[row][col] == 'S') {
        field[row][col] = 'X';
        return "Попадание!";
    } else if (field[row][col] == '~') {
        field[row][col] = 'O';
        return "Промах!";
    } else {
        return "Вы уже стреляли в эту клетку!";
    }
}

System.out.println(shoot(field, 0, 0));  // Попадание!
System.out.println(shoot(field, 1, 1));  // Промах!
System.out.println(shoot(field, 0, 0));  // Вы уже стреляли!

Шаг 5: Проверка победы

boolean allShipsDestroyed(char[][] field) {
    for (int row = 0; row < field.length; row++) {
        for (int col = 0; col < field[row].length; col++) {
            if (field[row][col] == 'S') {
                return false;
            }
        }
    }
    return true;
}

System.out.println("Все корабли потоплены: " + allShipsDestroyed(field));  // false
placeShip(field, 5, 1, 4, true);
for (int i = 1; i <= 4; i++) {
    field[5][i] = 'X';
}
System.out.println("Все корабли потоплены: " + allShipsDestroyed(field));  // true

Практические задачи

Задача 1: Повернуть матрицу на 90° по часовой стрелке

import java.util.Arrays;

int[][] matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

int n = matrix.length;
int[][] rotated = new int[n][n];

for (int row = 0; row < n; row++) {
    for (int col = 0; col < n; col++) {
        rotated[col][n - 1 - row] = matrix[row][col];
    }
}

System.out.println("Исходная: " + Arrays.deepToString(matrix));
System.out.println("Повёрнутая: " + Arrays.deepToString(rotated));
// Вывод:
// Исходная: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
// Повёрнутая: [[7, 4, 1], [8, 5, 2], [9, 6, 3]]

Задача 2: Найти сумму по диагоналям

import java.util.Arrays;

int[][] matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

int mainDiagSum = 0;
int antiDiagSum = 0;

for (int i = 0; i < matrix.length; i++) {
    mainDiagSum += matrix[i][i];
    antiDiagSum += matrix[i][matrix.length - 1 - i];
}

System.out.println("Главная диагональ: " + mainDiagSum);    // 15 (1+5+9)
System.out.println("Побочная диагональ: " + antiDiagSum);   // 15 (3+5+7)

Задача 3: Подсчитать количество вхождений числа

import java.util.Arrays;

int[][] matrix = {
    {1, 2, 3, 2},
    {4, 2, 6, 2},
    {7, 8, 2, 10}
};

int target = 2;
int count = 0;

for (int[] row : matrix) {
    for (int val : row) {
        if (val == target) {
            count++;
        }
    }
}

System.out.println("Число " + target + " встречается " + count + " раз");
// Вывод: Число 2 встречается 4 раз

Задача 4: Создать единичную матрицу

import java.util.Arrays;

int n = 5;
int[][] identity = new int[n][n];

for (int i = 0; i < n; i++) {
    identity[i][i] = 1;
}

System.out.println(Arrays.deepToString(identity));
// Вывод: [[1, 0, 0, 0, 0], [0, 1, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, 1, 0], [0, 0, 0, 0, 1]]

Задача 5: Найти строку с наибольшей суммой

import java.util.Arrays;

int[][] table = {
    {10, 20, 30},
    {50, 5, 10},
    {25, 25, 25}
};

int maxSum = 0;
int maxRowIndex = 0;

for (int row = 0; row < table.length; row++) {
    int sum = 0;
    for (int val : table[row]) {
        sum += val;
    }
    if (sum > maxSum) {
        maxSum = sum;
        maxRowIndex = row;
    }
}

System.out.println("Строка " + maxRowIndex + " имеет максимальную сумму: " + maxSum);
// Вывод: Строка 1 имеет максимальную сумму: 65

Массивы с бо́льшей размерностью

В Java поддерживается любое количество измерений. Трёхмерный массив — это «куб» данных, четырёхмерный — и так далее. На практике чаще всего используются одномерные и двумерные, но полезно знать о возможности большей размерности.

int[][][] cube = new int[3][4][5];  // 3 слоя, 4 строки, 5 столбцов

cube[0][0][0] = 1;
cube[1][2][3] = 42;
cube[2][3][4] = 100;

System.out.println(cube[0][0][0]);  // 1
System.out.println(cube[1][2][3]);  // 42
System.out.println(cube[2][3][4]);  // 100

Применение трёхмерных массивов:

  • 3D-графика — трёхмерные модели (x, y, z)
  • Видео — трёхмерный массив (кадр, строка, столбец)
  • Временные ряды данных — (измерение, день, час)
  • Игровые карты с уровнями — (уровень, строка, столбец)

Совет: Если размерность массива превышает 3, его становится очень сложно визуализировать и отлаживать. На практике для сложных структур данных используют объектные классы (класс Point, класс Cell, класс Layer) вместо многомерных массивов.

Типичные ошибки при работе с двумерными массивами

Ошибка 1: Путаница строк и столбцов

int[][] arr = new int[3][5];  // 3 строки, 5 столбцов

// ❌ Неправильно: arr[5][3] — IndexError!
// arr[столбец][строка]

// ✅ Правильно: arr[строка][столбец]
arr[0][0] = 1;  // строка 0, столбец 0
arr[2][4] = 2;  // строка 2, столбец 4

Ошибка 2: Фиксированная длина столбцов для зубчатого массива

int[][] jagged = new int[3][];
jagged[0] = new int[2];
jagged[1] = new int[5];

// ❌ Ошибка: предполагаем все строки длины 2
for (int col = 0; col < 2; col++) {
    System.out.println(jagged[1][col]);  // OK для col 0,1
}
// ❌ А если попробовать col = 2? Для jagged[1] это ОК, но не для jagged[0]

// ✅ Правильно:
for (int col = 0; col < jagged[1].length; col++) {
    System.out.println(jagged[1][col]);
}

Ошибка 3: Забыть импорт для deepToString

int[][] arr = {{1, 2}, {3, 4}};

// ❌ Без импорта:
System.out.println(Arrays.deepToString(arr));  // Error

// ✅ С импортом:
import java.util.Arrays;
System.out.println(Arrays.deepToString(arr));  // [[1, 2], [3, 4]]

Ошибка 4: Сравнение двумерных массивов через equals

int[][] a = {{1, 2}, {3, 4}};
int[][] b = {{1, 2}, {3, 4}};

// ❌ Неправильно:
System.out.println(a.equals(b));       // false

// ✅ Правильно:
System.out.println(Arrays.deepEquals(a, b));  // true

Детальное изучение умножения матриц

Умножение матриц — одна из фундаментальных операций в линейной алгебре, которая используется повсюду: от компьютерной графики до машинного обучения. Давайте разберём её подробнее.

Правило размерностей: Чтобы умножить матрицу A размером m×n на матрицу B размером n×p, нужно чтобы количество столбцов A совпадало с количеством строк B. Результат будет иметь размерность m×p.

import java.util.Arrays;

// A: 2×3, B: 3×2 → результат: 2×2
int[][] A = {
    {1, 2, 3},
    {4, 5, 6}
};

int[][] B = {
    {7, 8},
    {9, 10},
    {11, 12}
};

int[][] C = new int[2][2];

for (int i = 0; i < 2; i++) {
    for (int j = 0; j < 2; j++) {
        for (int k = 0; k < 3; k++) {
            C[i][j] += A[i][k] * B[k][j];
        }
    }
}

System.out.println("A (2×3): " + Arrays.deepToString(A));
System.out.println("B (3×2): " + Arrays.deepToString(B));
System.out.println("A×B (2×2): " + Arrays.deepToString(C));
// Вывод:
// A (2×3): [[1, 2, 3], [4, 5, 6]]
// B (3×2): [[7, 8], [9, 10], [11, 12]]
// A×B (2×2): [[58, 64], [139, 154]]

Разберём C[0][0] = 58:

  • A[0][0] × B[0][0] = 1 × 7 = 7
  • A[0][1] × B[1][0] = 2 × 9 = 18
  • A[0][2] × B[2][0] = 3 × 11 = 33
  • 7 + 18 + 33 = 58

Разберём C[0][1] = 64:

  • A[0][0] × B[0][1] = 1 × 8 = 8
  • A[0][1] × B[1][1] = 2 × 10 = 20
  • A[0][2] × B[2][1] = 3 × 12 = 36
  • 8 + 20 + 36 = 64

Важно: Умножение матриц НЕ коммутативно: A×B ≠ B×A в общем случае. Порядок сомножителей имеет значение! Это отличается от умножения обычных чисел.

Проверка: B×A — невозможно, потому что B имеет размерность 3×2, а A — 2×3. Количество столбцов B (2) не равно количеству строк A (2)... Подождите, 2=2, так что B×A возможно! Результат будет 3×3:

int[][] BA = new int[3][3];
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        for (int k = 0; k < 2; k++) {
            BA[i][j] += B[i][k] * A[k][j];
        }
    }
}
System.out.println("B×A: " + Arrays.deepToString(BA));
// B×A: [[39, 54, 69], [49, 68, 87], [59, 82, 105]]

Визуализация данных с помощью двумерных массивов

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

Пример 1: Гистограмма (горизонтальные бары)

int[] data = {5, 12, 8, 15, 3, 10, 7};

for (int i = 0; i < data.length; i++) {
    System.out.printf("Позиция %d: ", i);
    for (int j = 0; j < data[i]; j++) {
        System.out.print("█");
    }
    System.out.println(" (" + data[i] + ")");
}
// Вывод:
// Позиция 0: █████ (5)
// Позиция 1: ████████████ (12)
// Позиция 2: ████████ (8)
// Позиция 3: ███████████████ (15)
// Позиция 4: ███ (3)
// Позиция 5: ██████████ (10)
// Позиция 6: ███████ (7)

Пример 2: Таблица умножения

int n = 10;
int[][] table = new int[n][n];

for (int i = 0; i < n; i++) {
    for (int j = 0; j < n; j++) {
        table[i][j] = (i + 1) * (j + 1);
    }
}

System.out.print("    ");
for (int j = 1; j <= n; j++) {
    System.out.printf("%4d", j);
}
System.out.println();

for (int i = 0; i < n; i++) {
    System.out.printf("%2d |", i + 1);
    for (int j = 0; j < n; j++) {
        System.out.printf("%4d", table[i][j]);
    }
    System.out.println();
}

Пример 3: Карта расстояний (матрица попарных расстояний)

String[] cities = {"Москва", "СПб", "Новосибирск", "Екатеринбург"};
int[][] distances = {
    {0, 650, 3350, 1770},
    {650, 0, 3500, 2100},
    {3350, 3500, 0, 1800},
    {1770, 2100, 1800, 0}
};

System.out.println("Матрица расстояний (км):");
System.out.print("         ");
for (String city : cities) {
    System.out.printf("%12s", city);
}
System.out.println();

for (int i = 0; i < cities.length; i++) {
    System.out.printf("%10s", cities[i]);
    for (int j = 0; j < cities.length; j++) {
        System.out.printf("%12d", distances[i][j]);
    }
    System.out.println();
}

Пример 4: Тепловая карта оценок

String[] students = {"Анна", "Борис", "Виктор", "Галина"};
String[] subjects = {"Математика", "Физика", "Информатика"};
int[][] grades = {
    {5, 4, 5},
    {4, 5, 4},
    {3, 4, 5},
    {5, 5, 5}
};

System.out.print("           ");
for (String sub : subjects) {
    System.out.printf("%12s", sub);
}
System.out.println();

for (int i = 0; i < students.length; i++) {
    System.out.printf("%10s ", students[i]);
    for (int j = 0; j < subjects.length; j++) {
        String bar = "";
        for (int k = 0; k < grades[i][j]; k++) {
            bar += "●";
        }
        System.out.printf("%12s", bar);
    }
    System.out.println();
}

Практика: генерация простого лабиринта

Двумерный массив — идеальная структура для представления лабиринта. Давайте создадим простой лабиринт, где 0 — проход, а 1 — стена.

import java.util.Arrays;

int[][] maze = {
    {1, 1, 1, 1, 1, 1, 1},
    {1, 0, 0, 0, 1, 0, 1},
    {1, 0, 1, 0, 1, 0, 1},
    {1, 0, 1, 0, 0, 0, 1},
    {1, 0, 1, 1, 1, 0, 1},
    {1, 0, 0, 0, 0, 0, 1},
    {1, 1, 1, 1, 1, 1, 1}
};

for (int row = 0; row < maze.length; row++) {
    for (int col = 0; col < maze[row].length; col++) {
        if (maze[row][col] == 1) {
            System.out.print("██");
        } else {
            System.out.print("  ");
        }
    }
    System.out.println();
}
// Вывод:
// ██████████████
// ██        ██
// ██  ██  ██
// ██  ██      ██
// ██  ██████  ██
// ██        ██
// ██████████████

Пример: Поиск пути в лабиринте (упрощённый BFS):

import java.util.Arrays;

int[][] maze = {
    {1, 1, 1, 1, 1},
    {1, 0, 0, 0, 1},
    {1, 0, 1, 0, 1},
    {1, 0, 0, 0, 1},
    {1, 1, 1, 1, 1}
};

int startRow = 1, startCol = 1;
int endRow = 3, endCol = 3;

int[][] visited = new int[maze.length][maze[0].length];

boolean found = findPath(maze, visited, startRow, startCol, endRow, endCol);

if (found) {
    System.out.println("Путь найден!");
    for (int[] row : visited) {
        System.out.println(Arrays.toString(row));
    }
} else {
    System.out.println("Путь не найден!");
}

static boolean findPath(int[][] maze, int[][] visited, int row, int col, int endRow, int endCol) {
    if (row == endRow && col == endCol) {
        visited[row][col] = 1;
        return true;
    }

    if (row < 0 || row >= maze.length || col < 0 || col >= maze[0].length) return false;
    if (maze[row][col] == 1 || visited[row][col] == 1) return false;

    visited[row][col] = 1;

    if (findPath(maze, visited, row + 1, col, endRow, endCol)) return true;
    if (findPath(maze, visited, row, col + 1, endRow, endCol)) return true;
    if (findPath(maze, visited, row - 1, col, endRow, endCol)) return true;
    if (findPath(maze, visited, row, col - 1, endRow, endCol)) return true;

    visited[row][col] = 0;
    return false;
}

Итоги урока

  • Двумерный массив — это таблица с индексами [строка][столбец]
  • Создание: new int[строки][столбцы] или {{...}, {...}}
  • Количество строк: arr.length, столбцов: arr[0].length
  • Перебор — два вложенных цикла: внешний по строкам, внутренний по столбцам
  • Зубчатые (jagged) массивы — строки разной длины
  • Операции: сложение, умножение на число, умножение матриц, транспонирование
  • Умножение матриц: C[i][j] = Σ A[i][k] × B[k][j], требует 3 вложенных цикла
  • Вывод: Arrays.deepToString() для краткой формы, циклы — для красивой таблицы
  • Сравнение: Arrays.deepEquals() вместо equals()
  • Практическое применение: Морской бой, таблицы данных, лабиринты, визуализация

Тест: Многомерные массивы

8 вопросов

Двумерный массив

Premium