- QuickSort и MergeSort — самые практичные алгоритмы сортировки в Java, у обоих средняя сложность O(n log n).
- QuickSort быстрее на практике и не требует лишней памяти, но нестабилен и может деградировать до O(n²); MergeSort стабилен и предсказуем, но тратит O(n) дополнительной памяти.
- В прикладном коде почти всегда достаточно встроенных
Arrays.sort()/Collections.sort()— под капотом Java сама выбирает Dual-Pivot Quicksort для примитивов и TimSort для объектов. - Ниже — таблица сравнения сложности, рабочий код на Java для QuickSort и MergeSort и чек-лист выбора алгоритма под конкретную задачу.
Сортировка – это одна из фундаментальных операций в программировании и компьютерных науках. Она позволяет упорядочивать структуры данных, что критично для производительности бэкенда: от индексов в базе данных до пагинации в API. В языке программирования представлены разнообразные методы и алгоритмы сортировки java, каждый из которых обладает своими уникальными характеристиками и применим в различных ситуациях. Мы в YuSMP Group закладываем выбор алгоритмов и структур данных уже на этапе разработки backend и API на Java — от этого решения напрямую зависит скорость отклика сервиса под нагрузкой.
Методы сортировки Java
Java предоставляет богатый набор инструментов для сортировки массивов java. Основные методы, предоставляемые в стандартной библиотеке, включают такие виды сортировок java:
- Сортировка пузырьком java (BubbleSort) — простой способ, который последовательно сравнивает и меняет местами соседние элементы, пока весь массив не будет отсортирован. Сложность O(n²), из-за неэффективности редко используется в продакшене.
- Сортировка вставками java (InsertionSort) — строит отсортированную последовательность по элементу за раз, вставляя каждый на правильное место в уже готовой части массива. Сложность O(n²), но на почти отсортированных данных приближается к O(n).
- Сортировка выбором java (SelectionSort) — на каждом шаге находит минимальный элемент и меняет его местами с элементом на текущей позиции. Сложность всегда O(n²), независимо от исходного порядка данных.
- Сортировка слиянием java (MergeSort) — разделяет массив на две части, рекурсивно сортирует каждую половину и затем сливает их в отсортированную последовательность. Гарантирует O(n log n) в любом случае и остаётся стабильной.
- Быстрая сортировка java (QuickSort) — основана на принципе «разделяй и властвуй»: находит опорный элемент, делит массив на меньшие и большие относительно него части и рекурсивно обрабатывает обе. В среднем работает за O(n log n) и обычно быстрее MergeSort на практике.
Из всех перечисленных выше QuickSort и MergeSort считаются наиболее эффективными для реальных задач. Далее подробно разберём оба алгоритма, приведём рабочий код на Java и сравним их с остальными по сложности.
Сложность алгоритмов сортировки: таблица сравнения
Прежде чем выбирать конкретный способ, полезно увидеть все пять алгоритмов рядом — по времени выполнения в лучшем, среднем и худшем случае, по расходу памяти и по стабильности (сохранению порядка равных элементов):
| Алгоритм | Лучший случай | Средний случай | Худший случай | Память | Стабилен |
|---|---|---|---|---|---|
| BubbleSort | O(n) | O(n²) | O(n²) | O(1) | Да |
| InsertionSort | O(n) | O(n²) | O(n²) | O(1) | Да |
| SelectionSort | O(n²) | O(n²) | O(n²) | O(1) | Нет |
| MergeSort | O(n log n) | O(n log n) | O(n log n) | O(n) | Да |
| QuickSort | O(n log n) | O(n log n) | O(n²) | O(log n) | Нет |
Вывод из таблицы: у QuickSort самая низкая накладная память — сортировка идёт на месте, — но без оптимизации выбора опорного элемента он может деградировать до O(n²) на уже отсортированных или специально подобранных данных. MergeSort даёт гарантию O(n log n) в любом случае и сохраняет стабильность ценой O(n) дополнительной памяти под слияние.

QuickSort — быстрая сортировка
Это один из наиболее широко используемых алгоритмов на этом и других языках программирования. Его особенность в том, что он имеет среднюю сложность O(n log n), что делает его подходящим для big data, и при этом сортирует массив на месте — без выделения дополнительной памяти под копии.
Принцип работы состоит в нахождении опорного элемента и разделении массива на две части — меньше опорного и больше опорного. Затем каждая из этих частей обрабатывается рекурсивно, пока не останутся отсортированные подмассивы из одного элемента.
public static void quickSort(int[] arr, int low, int high) {
if (low < high) {
int pivotIndex = partition(arr, low, high);
quickSort(arr, low, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, high);
}
}
private static int partition(int[] arr, int low, int high) {
int pivot = arr[high];
int i = low - 1;
for (int j = low; j < high; j++) {
if (arr[j] < pivot) {
i++;
int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp;
}
}
int temp = arr[i + 1]; arr[i + 1] = arr[high]; arr[high] = temp;
return i + 1;
}
Оптимизация: как выбрать опорный элемент
Худший случай O(n²) у QuickSort возникает, когда опорный элемент раз за разом оказывается минимумом или максимумом подмассива — например, если всегда брать последний элемент на уже отсортированном массиве. На практике это лечат тремя способами:
- Медиана трёх — в качестве опорного берут медиану первого, среднего и последнего элементов подмассива, а не фиксированную позицию.
- Случайный выбор — опорный элемент выбирается случайно, что делает злонамеренный худший случай статистически маловероятным.
- Переход на InsertionSort — на маленьких подмассивах (обычно меньше 10–20 элементов) рекурсию QuickSort обрывают и досортировывают вставками: на таком размере она быстрее из-за меньших накладных расходов.
Именно комбинацию этих приёмов использует Dual-Pivot Quicksort — реализация, которая лежит в основе Arrays.sort() для примитивных типов в самой Java (подробнее — в разделе про встроенные методы ниже).
MergeSort — сортировка слиянием
MergeSort обеспечивает стабильную производительность даже на больших объёмах данных: в отличие от QuickSort, у него нет плохого случая — сложность всегда O(n log n), вне зависимости от исходного порядка элементов. Основной принцип — разделить массив пополам, рекурсивно отсортировать каждую половину и затем слить два отсортированных подмассива в один.
public static void mergeSort(int[] arr, int left, int right) {
if (left < right) {
int mid = (left + right) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
}
private static void merge(int[] arr, int left, int mid, int right) {
int[] leftArr = Arrays.copyOfRange(arr, left, mid + 1);
int[] rightArr = Arrays.copyOfRange(arr, mid + 1, right + 1);
int i = 0, j = 0, k = left;
while (i < leftArr.length && j < rightArr.length) {
arr[k++] = (leftArr[i] <= rightArr[j]) ? leftArr[i++] : rightArr[j++];
}
while (i < leftArr.length) arr[k++] = leftArr[i++];
while (j < rightArr.length) arr[k++] = rightArr[j++];
}
Ключевое свойство MergeSort — стабильность: элементы с равными ключами сохраняют исходный взаимный порядок, потому что на шаге слияния при равенстве всегда сначала берётся элемент из левой половины (leftArr[i] <= rightArr[j]). Это важно, когда сортировка выполняется по одному полю, а порядок по другому полю нужно сохранить — например, сортировка заказов по статусу с сохранением исходной сортировки по дате.
Плата за эту стабильность и гарантию — память: слияние требует O(n) дополнительного места под временные массивы leftArr и rightArr, а не сортирует на месте, как QuickSort.
Какой алгоритм сортировки использует Java «из коробки»?
В реальных проектах писать QuickSort или MergeSort руками почти никогда не нужно — в стандартной библиотеке уже есть проверенные, оптимизированные реализации:
Arrays.sort(int[])и другие перегрузки для примитивных типов используют Dual-Pivot Quicksort — модификацию QuickSort с двумя опорными элементами вместо одного, которая на практике быстрее классического QuickSort и MergeSort для примитивов.Arrays.sort(Object[])иCollections.sort(List)для объектов используют TimSort — гибридный алгоритм на основе MergeSort и InsertionSort, изначально разработанный для Python и портированный в Java. TimSort стабилен (это критично для объектов, где вместе с ключом сортировки переносятся другие поля) и хорошо использует уже упорядоченные участки данных («natural runs»).- Для сортировки по кастомному критерию используют
Comparator:list.sort(Comparator.comparing(User::getRegisteredAt))или цепочку.thenComparing()для сортировки по нескольким полям.
Почему для примитивов и объектов выбраны разные алгоритмы? Примитивы сравнивать дёшево, и стабильность неважна — нет привязанных данных, которые нужно сохранить в исходном порядке. Для объектов сравнение дороже, а стабильность часто критична, поэтому Java жертвует небольшим выигрышем в скорости ради гарантированной стабильности TimSort.
Как выбрать алгоритм сортировки для своей задачи?
Выбор подходящего способа зависит от объёма данных, доступных ресурсов и требований к стабильности:
- Маленький массив (до ~20 элементов): InsertionSort или даже BubbleSort — из-за простоты и минимальной дополнительной нагрузки они на таком размере не медленнее QuickSort или MergeSort.
- Большой объём данных: QuickSort или MergeSort — оба дают O(n log n) там, где InsertionSort и BubbleSort скатываются в O(n²).
- Нужна стабильность: MergeSort или TimSort (встроенная сортировка объектов в Java) — QuickSort порядок равных элементов не сохраняет.
- Ограничена память: QuickSort — сортирует на месте и не требует O(n) под временные массивы, в отличие от MergeSort.
- Данные почти отсортированы: InsertionSort или TimSort — оба эффективно используют уже упорядоченные участки и на таких данных приближаются к O(n).
На практике для собственного кода это редко значит «писать алгоритм с нуля» — почти всегда достаточно вызвать Arrays.sort() или Collections.sort() и, если нужно, передать свой Comparator. Знание внутреннего устройства этих методов помогает объяснить, почему сортировка объектов ведёт себя иначе, чем сортировка примитивов, и когда стоит задуматься об оптимизации вручную — например, при сортировке миллионов записей в потоковой обработке.

Частые вопросы о сортировке в Java
Какой алгоритм сортировки самый быстрый в Java?
На практике — Dual-Pivot Quicksort, который используется под капотом Arrays.sort() для примитивных типов: он быстрее классического QuickSort и MergeSort на большинстве реальных наборов данных. В теории у QuickSort, MergeSort и Dual-Pivot Quicksort одинаковая средняя сложность O(n log n) — разница в константах и работе с кэшем процессора.
Почему QuickSort быстрее MergeSort, если у обоих одинаковая сложность O(n log n)?
Дело не в теоретической сложности, а в константах и работе с памятью. QuickSort сортирует на месте и лучше использует кэш процессора, а MergeSort постоянно выделяет и копирует временные массивы. На практике это даёт QuickSort ощутимое преимущество, хотя у него есть риск худшего случая O(n²), которого у MergeSort нет.
Какая сортировка используется в Java по умолчанию?
Arrays.sort() для массивов примитивов (int, long, double и др.) использует Dual-Pivot Quicksort. Для массивов объектов и списков (Arrays.sort(Object[]), Collections.sort()) используется TimSort — стабильный гибрид MergeSort и InsertionSort.
Что такое стабильная сортировка и когда это важно?
Стабильная сортировка сохраняет исходный относительный порядок элементов с одинаковыми ключами. Это важно, когда список нужно отсортировать сразу по нескольким критериям: например, список уже отсортирован по дате, а затем его стабильно сортируют по статусу — записи с одинаковым статусом останутся в порядке по дате.
Можно ли отсортировать список объектов по нескольким полям сразу?
Да, через цепочку компараторов: list.sort(Comparator.comparing(Order::getStatus).thenComparing(Order::getCreatedAt)). Такой код сначала сортирует по статусу, а внутри одинакового статуса — по дате создания, и не требует писать собственный алгоритм сортировки.
Какая сложность у быстрой сортировки в худшем случае?
O(n²) — это происходит, когда опорный элемент раз за разом оказывается минимальным или максимальным в подмассиве, например на уже отсортированных данных при наивном выборе опорного (последний элемент). На практике этого избегают выбором медианы трёх или случайного опорного элемента — тогда худший случай становится статистически маловероятным.
Быстрая и слиянием остаются самыми универсальными алгоритмами сортировки Java для промышленной разработки, а для повседневных задач чаще хватает встроенных Arrays.sort() и Collections.sort(). Понимание того, что происходит под капотом, помогает разработчикам осознанно выбирать между готовыми методами и ручной реализацией, когда стандартной производительности не хватает.
Этот материал подготовили для вас специалисты веб-услуг и разработки в YuSMP Group. Больше интересных текстов по технологиям и ИТ в блоге студии web-разработки YuSMP Group, а в разделе кейсы можно найти реальные проекты, которые мы создали.
Нужны разработчики в команду?
Подберём инженеров нужного стека под ваш проект — быстрый старт.




