Пузырьковые сортировки (Bubble Sort)
Также упоминается как: метод пузырька, сортировка простым обменом (академическое название), сортировка обменами (exchange sort)
Читать дальше
Одно из первых описаний метода дал Эдвард Френд в работе "Sorting on Electronic Computer Systems" (1956), где алгоритм назывался просто сортировкой обменами. Привычное Bubble Sort вошло в обиход позже: его употребляет Кеннет Айверсон, автор языка APL, в книге "A Programming Language" (1962). Кто произнес это слово первым, история не сохранила - у алгоритма нет одного изобретателя, слишком уж он очевиден. Каждый, кому нужно было упорядочить десяток чисел, придумывал его заново.
Дональд Кнут отозвался о нем беспощадно. В третьем томе "Искусства программирования" он пишет, что у пузырьковой сортировки нет ровно никаких достоинств, кроме броского названия и пары интересных теоретических задач, к которым она приводит. И это справедливо: сортировка вставкой при той же квадратичной сложности почти всегда оказывается быстрее.
Зато как учебный пример алгоритм пережил всех. В 2007 году на встрече в Google Эрик Шмидт спросил Барака Обаму, тогда кандидата в президенты, как эффективнее всего отсортировать миллион 32-битных целых чисел. "Думаю, пузырьковая сортировка тут точно не подойдет", - ответил Обама под смех зала.
Самый знаменитый учебный алгоритм. Основан на локальных обменах соседних элементов, которые постепенно приводят к глобальной упорядоченности.
Алгоритм работы
- Последовательно проходим по массиву слева направо. Сравниваем два соседних элемента: если левый больше правого, меняем их местами.
- После первого полного прохода самый большой ("тяжелый") элемент неизбежно окажется в самом конце массива, подобно пузырьку воздуха.
- На втором проходе на предпоследнее место всплывет второй по величине элемент, и так далее.
- Оптимизация: если за полный проход не было ни одного обмена, массив уже отсортирован - работу можно прервать!
На визуализации ниже видно главную механику этого алгоритма: попарное сравнение соседей и всплытие самого большого числа в конец.
Тренажер: пузырьковая сортировка
Отсортируем массив 3 6 2 5 4 7 по возрастанию.
Данный метод устойчив (если строго >, а не >=), но из-за огромного количества обменов на каждом шаге он крайне медленный на практике.
Другие пузырьковые сортировки
Обмен соседних элементов породил не один алгоритм, а целое семейство. Все они делают одно и то же и различаются лишь тем, в каком порядке перебирают пары - поэтому о пузырьковых сортировках говорят во множественном числе.
- Шейкерная сортировка (Cocktail Shaker Sort), она же сортировка перемешиванием и двунаправленная пузырьковая. Проходы идут попеременно слева направо и справа налево. Это лечит главную беду оригинального алгоритма: маленький элемент в конце массива добирается до начала за один проход, а не за N.
- Сортировка расческой (Comb Sort). Сравниваются не соседи, а элементы на расстоянии, которое сокращается от прохода к проходу. Идея та же, что у сортировки Шелла, и на практике расческа ведет себя заметно лучше квадратичной.
- Гномья сортировка (Gnome Sort), она же глупая сортировка. После обмена алгоритм возвращается на шаг назад вместо того, чтобы идти дальше. Пишется одним циклом без вложенного и потому часто служит самым коротким примером сортировки вообще.
- Чет-нечетная сортировка (Odd-Even Sort), она же кирпичная (brick sort). Проход разбит надвое: сначала пары с четными индексами, затем с нечетными. Внутри половины обмены не мешают друг другу, поэтому такую сортировку легко распараллелить.
Пузырьковая сортировка: реализация на C с оптимизацией
#include <stdbool.h>
void BubbleSort(int arr[], int n) {
bool swapped;
for (int i = 0; i < n - 1; i++) {
swapped = false; // Флаг: были ли обмены в этом проходе?
// Внутренний цикл до n-i-1, так как 'i' элементов уже всплыли в конец
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
Swap(&arr[j], &arr[j + 1]);
swapped = true;
}
}
// Если обменов не было, массив отсортирован - выходим досрочно!
if (!swapped) break;
}
}
Коротко: главное
- Алгоритм многократно проходит по массиву, сравнивая и обменивая соседние элементы.
- За один полный проход наибольший из оставшихся элементов всплывает в конец массива.
- Временная сложность сортировки составляет O(N²).
- Алгоритм устойчив, но крайне медленен на практике из-за большого числа обменов.
- Процесс можно оптимизировать: если за проход не было ни одного обмена, массив уже отсортирован.
Проверьте себя
В каком случае оптимизированный алгоритм досрочно завершает свою работу?