Урок 4.3 — Вложенные циклы
Цикл внутри цикла — табуляция умножения, матрицы и оценка сложности.
Один цикл решает много задач. Но иногда нужно внутри одной итерации внешнего цикла выполнить целый внутренний цикл. Это называется вложенные циклы. Представь себе расписание: для каждого дня недели (внешний цикл) перебираются все уроки (внутренний цикл).
Синтаксис: цикл внутри цикла
Вложенные циклы — это когда один цикл находится внутри тела другого. Для каждой итерации внешнего цикла полностью выполняется внутренний цикл.
Общая схема:
int i = 1;
while (i <= 3) // внешний цикл
{
int j = 1;
while (j <= 2) // внутренний цикл
{
Console.WriteLine($"i={i}, j={j}");
j++;
}
i++;
}
Давай разберём этот пример. Внешний цикл выполняется 3 раза (i = 1, 2, 3). Для каждого i внутренний цикл выполняется 2 раза (j = 1, 2). Всего итераций: 3 × 2 = 6.
int i = 1;
while (i <= 3)
{
Console.Write($"Строка {i}: ");
int j = 1;
while (j <= 5)
{
Console.Write(j + " ");
j++;
}
Console.WriteLine(); // перенос строки
i++;
}
// Вывод:
// Строка 1: 1 2 3 4 5
// Строка 2: 1 2 3 4 5
// Строка 3: 1 2 3 4 5
Таблица умножения — классика
Классический пример вложенных циклов — вывод таблицы умножения. Внешний цикл — первый множитель, внутренний — второй.
Console.Write(" |"); // заголовок столбцов
int col = 1;
while (col <= 9)
{
Console.Write(col.ToString().PadLeft(3));
col++;
}
Console.WriteLine();
// Разделитель
Console.WriteLine("---+" + new string('-', 27));
int row = 1;
while (row <= 9)
{
Console.Write(row.ToString().PadLeft(2) + " |"); // номер строки
int colInner = 1;
while (colInner <= 9)
{
int product = row * colInner;
Console.Write(product.ToString().PadLeft(3));
colInner++;
}
Console.WriteLine();
row++;
}
💡 PadLeft(3) — метод выравнивания. Дополняет строку пробелами слева до указанной длины. Так наша таблица получается аккуратной, с ровными столбцами. new string('-', 27) — создаёт строку из 27 дефисов.
Результат — красивая таблица умножения 9×9, где каждая ячейка — это произведение номера строки на номер столбца. Таблица умножения — идеальный пример, потому что наглядно показывает связь между внешним и внутренним циклами.
Как считаются итерации: внешние и внутренние
Важно понимать, сколько раз выполняется каждая часть вложенных циклов. Пусть внешний цикл выполняется N раз, а внутренний — M раз на каждую итерацию внешнего цикла.
// Подсчитаем количество итераций
int outerCount = 0;
int innerTotal = 0;
int i = 1;
while (i <= 4) // внешний: 4 итерации
{
outerCount++;
int j = 1;
while (j <= 3) // внутренний: 3 итерации на каждую внешнюю
{
innerTotal++;
j++;
}
i++;
}
Console.WriteLine($"Внешних итераций: {outerCount}"); // 4
Console.WriteLine($"Внутренних итераций: {innerTotal}"); // 12 (4 × 3)
Break и continue во вложенных циклах
Важно запомнить: break и continue работают только с ближайшим (самым внутренним) циклом. Они не затрагивают внешний цикл.
❌ Распространённое заблуждение
while (условие) // внешний
{
while (условие) // внутренний
{
break; // выходит ТОЛЬКО
} // из внутреннего!
// внешний продолжается!
}
✅ Как выйти из внешнего
bool found = false;
while (условие && !found)
{
while (условие)
{
if (нашли)
{
found = true;
break; // внутр.
}
}
}
// Поиск первой пары чисел, произведение которых = 24
int a = 1;
bool found = false;
while (a <= 9 && !found)
{
int b = 1;
while (b <= 9)
{
if (a * b == 24)
{
Console.WriteLine($"Нашли: {a} × {b} = 24");
found = true;
break; // выходим из внутреннего
}
b++;
}
a++; // выполняется, даже если break во внутреннем!
// Но found == true, и внешний цикл проверит условие
}
// Результат: Нашли: 3 × 8 = 24 (или 4 × 6, зависит от порядка)
⚠️ Запомни: break внутри внутреннего цикла не прерывает внешний. Чтобы прервать внешний, нужно использовать флаг (bool-переменную) или вынести логику в метод и использовать return.
Временная сложность O(n × m)
Количество операций во вложенных циклах равно произведению количества итераций. Это записывают как O(n × m) — «О от n на m». Если оба цикла проходят по одному и тому же диапазону n, то сложность O(n²) — квадратичная.
⚠️ Практический совет: если у тебя три вложенных цикла (O(n³)) и n = 1000, программа будет выполнять миллиард операций. Это может занять минуты или даже часы. Всегда спрашивай себя: «Можно ли обойтись без этого уровня вложенности?»
Практические примеры
Рассмотрим два реальных примера: работа с матрицами и перебор комбинаций.
📊 Матрица 4×4 — заполнение и вывод:
// Выводим матрицу 4×4, где каждый элемент = row × col
int rows = 4;
int cols = 4;
int row = 1;
while (row <= rows)
{
int col = 1;
while (col <= cols)
{
int value = row * col;
Console.Write(value.ToString().PadLeft(4));
col++;
}
Console.WriteLine();
row++;
}
// Вывод:
// 1 2 3 4
// 2 4 6 8
// 3 6 9 12
// 4 8 12 16
🔀 Перебор всех комбинаций (например, броски двух кубиков):
// Выводим все возможные комбинации двух кубиков
int dice1 = 1;
int count = 0;
while (dice1 <= 6)
{
int dice2 = 1;
while (dice2 <= 6)
{
Console.WriteLine($"Кубик 1: {dice1}, Кубик 2: {dice2}, Сумма: {dice1 + dice2}");
count++;
dice2++;
}
dice1++;
}
Console.WriteLine($"\nВсего комбинаций: {count}"); // 36
Сводный пример — рисование треугольника
Используем вложенные циклы для рисования фигур в консоли. Нарисуем прямоугольный треугольник из звёздочек:
// Треугольник 5×5 (по левому краю)
int size = 5;
int row = 1;
while (row <= size)
{
int star = 1;
while (star <= row) // количество звёзд = номер строки
{
Console.Write("*");
star++;
}
Console.WriteLine();
row++;
}
// Вывод:
// *
// **
// ***
// ****
// *****
// Теперь перевёрнутый треугольник:
Console.WriteLine();
int row2 = size;
while (row2 >= 1)
{
int star2 = 1;
while (star2 <= row2)
{
Console.Write("*");
star2++;
}
Console.WriteLine();
row2--;
}
// Вывод:
// *****
// ****
// ***
// **
// *
Мини-тест
Проверь понимание вложенных циклов.
Вопрос 1
int i = 1;
while (i <= 3)
{
int j = 1;
while (j <= 2)
{
Console.Write("*");
j++;
}
i++;
}
Console.WriteLine();
// Сколько звёздочек? Сколько строк?<
Варианты: A) 6 звёзд, 1 строка B) 6 звёзд, 6 строк C) 3 звезды
Вопрос 2
int x = 0;
while (x < 4)
{
int y = 0;
while (y < 3)
{
if (y == 2) break;
Console.Write($"[{x},{y}]");
y++;
}
x++;
}
// Сколько итераций внешнего цикла выполнится?
Варианты: A) 3 B) 4 C) 2
Вопрос 3
int count = 0;
int i = 0;
while (i < 3)
{
int j = 0;
while (j < 5)
{
count++;
if (count >= 10) break;
j++;
}
i++;
}
Console.WriteLine(count);
Варианты: A) 10 B) 12 C) 15
Вопрос 4
// Сколько раз выполнится Console.WriteLine?
int i = 1;
while (i <= 2)
{
int j = 1;
while (j <= 3)
{
Console.WriteLine("Hello");
j++;
}
i++;
}
Варианты: A) 2 B) 3 C) 6
Задача
Задание: Пирамида из чисел
Напиши программу, которая выводит числовую пирамиду высотой N (вводится пользователем).
Пример для N = 5:
1
12
123
1234
12345
Усложнённая версия — треугольник Паскаля (необязательно):
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
Требования:
- Используй вложенные циклы while
- Проверь корректность ввода (N должно быть положительным целым)
Что важно запомнить
Тест: 4.3: Вложенные циклы
5 вопросов