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

Вложенные циклы — таблица умножения, матрицы, сложность O(n×m), паттерны, вложенные for+if

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

~30 минут Для новичков Java

Что такое вложенные циклы?

Вложенные циклы — это когда один цикл находится внутри другого. Внешний цикл определяет «строки», а внутренний — «столбцы». Это как таблица: строки × столбцы.

Аналогия: Представьте таблицу умножения. Внешний цикл перебирает строки (1, 2, 3...), а внутренний — столбцы (×1, ×2, ×3...). Каждая ячейка таблицы — это результат одной итерации внутреннего цикла.

for (внешний_счётчик) {
    for (внутренний_счётчик) {
        // тело внутреннего цикла
    }
    // код после внутреннего цикла (выполняется после каждой строки)
}

Важно понимать: на каждую итерацию внешнего цикла внутренний цикл выполняется полностью.

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

Выведем таблицу умножения для чисел 1–5 на 1–5:

for (int i = 1; i <= 5; i++) {
    for (int j = 1; j <= 5; j++) {
        System.out.print(i * j + "\t");
    }
    System.out.println();
}

Вывод:

1	2	3	4	5	
2	4	6	8	10	
3	6	9	12	15	
4	8	12	16	20	
5	10	15	20	25	

\t — это символ табуляции (отступ). Он выравнивает столбцы.

Как это работает:

  1. Внешний цикл: i = 1
  2. Внутренний цикл полностью: j = 1, 2, 3, 4, 5 → печатаем 1, 2, 3, 4, 5
  3. System.out.println() — перевод строки
  4. Внешний цикл: i = 2
  5. Внутренний: j = 1, 2, 3, 4, 5 → 2, 4, 6, 8, 10
  6. ... и так далее

Производительность: O(n × m)

Временная сложность вложенных циклов — O(n × m), где n — количество итераций внешнего цикла, m — внутреннего.

В примере выше: 5 × 5 = 25 итераций. Если внешний цикл — 1000, внутренний — 1000, то всего 1 000 000 итераций. Это быстро для компьютера, но заметьте: при 10 000 × 10 000 будет 100 000 000 итераций — уже может быть медленно.

Правило: вложенные циклы — мощный инструмент, но будьте осторожны с объёмом данных. Если n и m велики — ищите способ сделать задачу эффективнее.

Практика: матрица (двумерный массив)

Вложенные циклы идеально подходят для работы с матрицами — двумерными структурами данных:

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

for (int i = 0; i < matrix.length; i++) {
    for (int j = 0; j < matrix[i].length; j++) {
        System.out.print(matrix[i][j] + "\t");
    }
    System.out.println();
}

Вывод:

1	2	3	
4	5	6	
7	8	9	

matrix[i][j] — обращение к элементу в строке i, столбце j.

Практика: сумма элементов матрицы

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

int sum = 0;

for (int i = 0; i < matrix.length; i++) {
    for (int j = 0; j < matrix[i].length; j++) {
        sum += matrix[i][j];
    }
}

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

Вывод:

Сумма всех элементов: 45

Практика: поиск максимума в матрице

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

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

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

System.out.println("Максимум: " + max);
System.out.println("На позиции: строка " + maxRow + ", столбец " + maxCol);

Вывод:

Максимум: 9
На позиции: строка 1, столбец 2

Паттерны из символов

Вложенные циклы позволяют создавать «картинки» из символов. Давайте создадим несколько паттернов:

Прямоугольник:

for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 8; j++) {
        System.out.print("* ");
    }
    System.out.println();
}
* * * * * * * * 
* * * * * * * * 
* * * * * * * * 
* * * * * * * * 

Паттерн: лесенка

for (int i = 1; i <= 5; i++) {
    for (int j = 1; j <= i; j++) {
        System.out.print("* ");
    }
    System.out.println();
}
* 
* * 
* * * 
* * * * 
* * * * * 

Ключевая идея: внутренний цикл выполняется i раз. На первой строке (i=1) — 1 звёздочка, на второй (i=2) — 2, и т.д.

Паттерн: перевёрнутая лесенка

for (int i = 5; i >= 1; i--) {
    for (int j = 1; j <= i; j++) {
        System.out.print("* ");
    }
    System.out.println();
}
* * * * * 
* * * * 
* * * 
* * 
* 

Паттерн: цифровой прямоугольник

for (int i = 1; i <= 4; i++) {
    for (int j = 1; j <= 5; j++) {
        System.out.print(i + " ");
    }
    System.out.println();
}
1 1 1 1 1 
2 2 2 2 2 
3 3 3 3 3 
4 4 4 4 4 

Паттерн: шахматная доска

for (int i = 0; i < 8; i++) {
    for (int j = 0; j < 8; j++) {
        if ((i + j) % 2 == 0) {
            System.out.print("█ ");
        } else {
            System.out.print("░ ");
        }
    }
    System.out.println();
}
█ ░ █ ░ █ ░ █ ░ 
░ █ ░ █ ░ █ ░ █ 
█ ░ █ ░ █ ░ █ ░ 
░ █ ░ █ ░ █ ░ █ 
█ ░ █ ░ █ ░ █ ░ 
░ █ ░ █ ░ █ ░ █ 
█ ░ █ ░ █ ░ █ ░ 
░ █ ░ █ ░ █ ░ █ 

Здесь используется if внутри вложенного цикла: если сумма индексов чётная — один символ, если нечётная — другой.

Паттерн: ромб

int n = 5;
for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= n - i; j++) {
        System.out.print(" ");
    }
    for (int j = 1; j <= 2 * i - 1; j++) {
        System.out.print("*");
    }
    System.out.println();
}

for (int i = n - 1; i >= 1; i--) {
    for (int j = 1; j <= n - i; j++) {
        System.out.print(" ");
    }
    for (int j = 1; j <= 2 * i - 1; j++) {
        System.out.print("*");
    }
    System.out.println();
}
    *
   ***
  *****
 *******
*********
 *******
  *****
   ***
    *

Здесь используется комбинация вложенных циклов и if: сначала печатаем пробелы для выравнивания, потом звёздочки.

Паттерн: цифровой треугольник

for (int i = 1; i <= 5; i++) {
    for (int j = 1; j <= i; j++) {
        System.out.print(j + " ");
    }
    System.out.println();
}
1 
1 2 
1 2 3 
1 2 3 4 
1 2 3 4 5 

Вложенные циклы с break и continue

break и continue работают только с внутренним циклом:

for (int i = 1; i <= 3; i++) {
    for (int j = 1; j <= 5; j++) {
        if (j == 3) {
            break;
        }
        System.out.println("i=" + i + ", j=" + j);
    }
    System.out.println("--- конец строки " + i + " ---");
}

Вывод:

i=1, j=1
i=1, j=2
--- конец строки 1 ---
i=2, j=1
i=2, j=2
--- конец строки 2 ---
i=3, j=1
i=3, j=2
--- конец строки 3 ---

break прерывает только внутренний цикл. Внешний продолжает работу.

Метки с вложенными циклами

outer:
for (int i = 1; i <= 3; i++) {
    for (int j = 1; j <= 3; j++) {
        if (i == 2 && j == 2) {
            break outer;
        }
        System.out.println("i=" + i + ", j=" + j);
    }
}

Вывод:

i=1, j=1
i=1, j=2
i=1, j=3
i=2, j=1

break outer прерывает оба цикла.

Вложенные while и for

Вложенные циклы могут быть разных типов:

int i = 1;
while (i <= 3) {
    for (int j = 1; j <= 4; j++) {
        System.out.print(i * j + "\t");
    }
    System.out.println();
    i++;
}

Вывод:

1	2	3	4	
2	4	6	8	
3	6	9	12	

Внешний — while, внутренний — for. Всё работает!

Практика: таблица умножения от 1 до 10

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

printf("%4d", ...) — форматированный вывод: число в поле шириной 4 символа. Это выравнивает столбцы.

Практика: транспонирование матрицы

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

System.out.println("Исходная матрица:");
for (int i = 0; i < matrix.length; i++) {
    for (int j = 0; j < matrix[i].length; j++) {
        System.out.print(matrix[i][j] + "\t");
    }
    System.out.println();
}

System.out.println("\nТранспонированная:");
for (int j = 0; j < matrix[0].length; j++) {
    for (int i = 0; i < matrix.length; i++) {
        System.out.print(matrix[i][j] + "\t");
    }
    System.out.println();
}

Вывод:

Исходная матрица:
1	2	3	
4	5	6	

Транспонированная:
1	4	
2	5	
3	6	

Совет: при работе с вложенными циклами начинайте с маленького размера (2×2, 3×3), убедитесь, что код работает, а потом увеличивайте.

Практика: перемножение двух матриц

Умножение матриц — классическая задача с тройными вложенными циклами:

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

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

int[][] result = 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++) {
            result[i][j] += a[i][k] * b[k][j];
        }
    }
}

System.out.println("Результат умножения:");
for (int i = 0; i < 2; i++) {
    for (int j = 0; j < 2; j++) {
        System.out.print(result[i][j] + "\t");
    }
    System.out.println();
}

Вывод:

Результат умножения:
19	22
43	50

Практика: проверка симметричности матрицы

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

boolean symmetric = true;

for (int i = 0; i < matrix.length; i++) {
    for (int j = 0; j < matrix[i].length; j++) {
        if (matrix[i][j] != matrix[j][i]) {
            symmetric = false;
            break;
        }
    }
    if (!symmetric) break;
}

System.out.println("Матрица " + (symmetric ? "симметрична" : "не симметрична"));

Вывод:

Матрица симметрична

Практика: рисуем рамку

int width = 10;
int height = 5;

for (int i = 0; i < height; i++) {
    for (int j = 0; j < width; j++) {
        if (i == 0 || i == height - 1 || j == 0 || j == width - 1) {
            System.out.print("* ");
        } else {
            System.out.print("  ");
        }
    }
    System.out.println();
}

Вывод:

* * * * * * * * * * 
*                 * 
*                 * 
*                 * 
* * * * * * * * * * 

Практика: рисуем пустой ромб

int n = 5;

for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= n - i; j++) {
        System.out.print(" ");
    }
    for (int j = 1; j <= 2 * i - 1; j++) {
        if (j == 1 || j == 2 * i - 1) {
            System.out.print("*");
        } else {
            System.out.print(" ");
        }
    }
    System.out.println();
}

for (int i = n - 1; i >= 1; i--) {
    for (int j = 1; j <= n - i; j++) {
        System.out.print(" ");
    }
    for (int j = 1; j <= 2 * i - 1; j++) {
        if (j == 1 || j == 2 * i - 1) {
            System.out.print("*");
        } else {
            System.out.print(" ");
        }
    }
    System.out.println();
}

Вывод:

    *
   * *
  *   *
 *     *
*       *
 *     *
  *   *
   * *
    *

Практика: таблица квадратов и кубов

System.out.println("Число\tКвадрат\tКуб");
System.out.println("─────────────────────");

for (int i = 1; i <= 10; i++) {
    System.out.printf("%d\t%d\t%d%n", i, i * i, i * i * i);
}

Вывод:

Число	Квадрат	Куб
─────────────────────
1	1	1
2	4	8
3	9	27
4	16	64
5	25	125
6	36	216
7	49	343
8	64	512
9	81	729
10	100	1000

Практика: генерация таблицы логарифмов

System.out.println("Число\tlog₂\tlog₁₀\tln");
System.out.println("─────────────────────────────");

for (int i = 1; i <= 10; i++) {
    System.out.printf("%d\t%.3f\t%.3f\t%.3f%n",
        i,
        Math.log(i) / Math.log(2),
        Math.log10(i),
        Math.log(i));
}

Практика: создание двумерного массива с данными

int rows = 4;
int cols = 5;
int[][] data = new int[rows][cols];

int counter = 1;
for (int i = 0; i < rows; i++) {
    for (int j = 0; j < cols; j++) {
        data[i][j] = counter++;
    }
}

System.out.println("Заполненная матрица:");
for (int i = 0; i < rows; i++) {
    for (int j = 0; j < cols; j++) {
        System.out.printf("%3d", data[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

Практика: рисуем пирамиду с числами

int n = 5;

for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= n - i; j++) {
        System.out.print(" ");
    }
    for (int j = 1; j <= 2 * i - 1; j++) {
        System.out.print(j);
    }
    System.out.println();
}

Вывод:

    1
   123
  12345
 1234567
123456789

Практика: рисуем ёлочку

int height = 6;

for (int i = 1; i <= height; i++) {
    for (int j = 1; j <= height - i; j++) {
        System.out.print(" ");
    }
    for (int j = 1; j <= 2 * i - 1; j++) {
        System.out.print("*");
    }
    System.out.println();
}

for (int i = 1; i <= 2; i++) {
    for (int j = 1; j <= height - 2; j++) {
        System.out.print(" ");
    }
    System.out.println("|");
}

Вывод:

     *
    ***
   *****
  *******
 *********
***********
     |
     |

Совет: при создании паттернов начинайте с рисунка на бумаге. Определите закономерность: сколько пробелов, сколько символов, как зависит от номера строки. Затем перенесите это в код.

Итоги урока

  • Вложенные циклы — это циклы внутри циклов
  • На каждую итерацию внешнего цикла внутренний выполняется полностью
  • Сложность: O(n × m), где n — внешний, m — внутренний
  • Идеально подходят для таблиц, матриц и паттернов
  • break и continue работают только с внутренним циклом (без метки)
  • Для прерыва внешнего цикла используйте метки: break outer
  • Можно комбинировать разные типы циклов: for внутри while и наоборот
  • Комбинация for + if внутри вложенных циклов позволяет создавать сложные паттерны
  • Паттерны создаются комбинацией пробелов и символов в правильных пропорциях

Следующий урок

В следующем уроке мы закрепим все знания о циклах на практических алгоритмах: поиск максимума/минимума, подсчёт элементов, проверка на простоту, Фибоначчи, факториал, разворот числа.

Урок 6: Циклы на практике →

Тест по вложенным циклам

1 вопрос

Звёздный треугольник

Premium