Перейти к основному содержимому

Цикл внутри цикла

O(n²) означает квадратичное время: объём работы растёт как квадрат размера входных данных. В два раза больше элементов — в четыре раза больше шагов. В десять раз больше элементов — в сто раз больше шагов.

Характерная форма в коде — это цикл внутри цикла, причём оба проходят по одним и тем же данным. Внешний цикл выполняется n раз, и на каждом его шаге внутренний цикл снова выполняется n раз: n · n шагов.

Для небольших срезов квадратичное время вполне приемлемо. Но для 100 000 элементов это уже 10 миллиардов шагов — именно здесь программы начинают зависать.


Все пары

Самая наглядная задача на O(n²): соединить каждый элемент с каждым. 4 буквы дают 4 · 4 = 16 пар; 100 букв дали бы 10 000.

Построить все пары

Внешний цикл выбирает x, внутренний соединяет его с каждым y. 4 элемента дают 16 пар — квадрат размера входных данных.

Prompt
Напишите программу на Go со срезом a, b, c, d, которая с помощью двух вложенных циклов выводит каждую пару букв (например, aa ab ac ad) — по одной строке на каждый элемент внешнего цикла — и считает пары. В конце выведите, сколько элементов дали сколько пар.
Вывод: программа на Go и захваченный вывод
Build every pair

Сортировка пузырьком

Сортировка пузырьком — классический квадратичный алгоритм: он раз за разом проходит по срезу и меняет местами соседей, стоящих в неправильном порядке. Большие значения «всплывают» к концу, по одному проходу за раз.

С двумя вложенными циклами он делает около n²/2 сравнений. На нём отлично учиться — и плохо им пользоваться: slices.Sort() делает ту же работу за O(n log n).

Сортировка двумя вложенными циклами

Сортировка 5 элементов требует 4 + 3 + 2 + 1 = 10 сравнений. Для 1000 элементов их было бы около полумиллиона — тогда как сортировке за O(n log n) нужно около десяти тысяч.

Prompt
Напишите программу на Go, которая сортирует срез 5, 3, 8, 1, 4 методом пузырька — двумя вложенными циклами, меняющими местами соседей, стоящих в неправильном порядке, — и при этом считает сравнения. Выведите отсортированный срез и количество сравнений.
Вывод: программа на Go и захваченный вывод
Sort with two nested loops

Поиск дубликатов медленным способом

Сравнение каждого элемента с каждым, который идёт после него, — ещё один шаблон с вложенными циклами: перебор всех пар, чтобы найти дубликат.

Это работает, но это O(n²). На странице «Пространственная сложность» в этой группе показан стандартный приём, который делает задачу линейной: запоминать уже встреченные значения в map.

Найти дубликат двумя циклами

Каждый элемент сравнивается с каждым последующим. Пара одинаковых единиц находится после 7 проверок; в худшем случае — когда дубликата нет вовсе — выполняются все 15 проверок пар.

Prompt
Напишите программу на Go, которая находит повторяющееся значение в срезе 3, 1, 4, 1, 5, 9, сравнивая каждый элемент с каждым последующим в двух вложенных циклах и считая проверки. Когда дубликат найден, выведите это значение и количество проверок на данный момент.
Вывод: программа на Go и захваченный вывод
Find a duplicate with two loops