Files
hello-algo/ru/docs/chapter_sorting/summary.md
2026-01-20 15:08:42 +08:00

11 KiB
Raw Blame History

Резюме

Основные моменты

  • Сортировка пузырьком реализует сортировку путем обмена соседних элементов. Добавив флаг для досрочного возврата, мы можем оптимизировать лучшую временную сложность сортировки пузырьком до O(n).
  • Сортировка вставками на каждом раунде вставляет элемент из неотсортированного диапазона в правильную позицию в отсортированном диапазоне, завершая таким образом сортировку. Хотя временная сложность сортировки вставками составляет O(n^2), благодаря относительно небольшому количеству элементарных операций она очень популярна для задач сортировки небольших объемов данных.
  • Быстрая сортировка реализует сортировку на основе операции разделения с опорным элементом. При разделении с опорным элементом возможна ситуация, когда каждый раз выбирается наихудший базовый элемент, что приводит к ухудшению временной сложности до O(n^2). Введение медианного или случайного базового элемента может снизить вероятность такого ухудшения. Приоритетная рекурсия более короткого подмассива может эффективно уменьшить глубину рекурсии, оптимизируя пространственную сложность до O(\log n).
  • Сортировка слиянием включает две фазы: разделение и слияние, типично воплощая стратегию "разделяй и властвуй". При сортировке слиянием массива необходимо создать вспомогательный массив, пространственная сложность составляет O(n); однако пространственная сложность сортировки связного списка может быть оптимизирована до O(1).
  • Блочная сортировка включает три шага: распределение данных по блокам, сортировка внутри блоков и объединение результатов. Она также воплощает стратегию "разделяй и властвуй" и подходит для случаев с очень большим объемом данных. Ключ к блочной сортировке заключается в равномерном распределении данных.
  • Сортировка подсчетом является частным случаем блочной сортировки, она реализует сортировку путем подсчета количества появлений данных. Сортировка подсчетом подходит для случаев с большим объемом данных, но ограниченным диапазоном данных, и требует, чтобы данные могли быть преобразованы в положительные целые числа.
  • Поразрядная сортировка реализует сортировку данных путем последовательной сортировки по разрядам, требуя, чтобы данные могли быть представлены в виде чисел с фиксированным количеством разрядов.
  • В целом, мы хотим найти алгоритм сортировки, обладающий такими преимуществами, как высокая эффективность, стабильность, сортировка на месте и адаптивность. Однако, как и в случае с другими структурами данных и алгоритмами, не существует алгоритма сортировки, который одновременно удовлетворял бы всем этим условиям. В практических приложениях необходимо выбирать подходящий алгоритм сортировки в зависимости от характеристик данных.
  • На рисунке ниже сравниваются эффективность, стабильность, местность и адаптивность основных алгоритмов сортировки.

Сравнение алгоритмов сортировки

Вопросы и ответы

В: В каких случаях стабильность алгоритма сортировки является необходимой?

В реальности мы можем сортировать объекты по какому-либо их атрибуту. Например, у студентов есть два атрибута: имя и рост, и мы хотим реализовать многоуровневую сортировку: сначала отсортировать по имени, получив (A, 180) (B, 185) (C, 170) (D, 170); затем отсортировать по росту. Поскольку алгоритм сортировки нестабилен, можно получить (D, 170) (C, 170) (A, 180) (B, 185).

Можно заметить, что позиции студентов D и C поменялись местами, упорядоченность по имени нарушена, а это нежелательно.

В: Можно ли поменять местами порядок "поиска справа налево" и "поиска слева направо" при разделении с опорным элементом?

Нет, когда мы используем крайний левый элемент в качестве базового, необходимо сначала выполнить "поиск справа налево", а затем "поиск слева направо". Этот вывод несколько противоречит интуиции, давайте разберем причину.

Последний шаг разделения с опорным элементом partition() -- это обмен nums[left] и nums[i]. После завершения обмена все элементы слева от базового элемента <= базового элемента, это требует, чтобы перед последним шагом обмена обязательно выполнялось условие nums[left] >= nums[i]. Предположим, мы сначала выполняем "поиск слева направо", тогда если не найдется элемент больше базового, цикл завершится при i == j, и в этот момент возможно nums[j] == nums[i] > nums[left]. То есть в этом случае последняя операция обмена поместит элемент больше базового в крайнюю левую позицию массива, что приведет к сбою разделения с опорным элементом.

Приведем пример: дан массив [0, 0, 0, 0, 1], если сначала выполнить "поиск слева направо", после разделения с опорным элементом массив станет [1, 0, 0, 0, 0], этот результат неверен.

Если подумать глубже, если мы выберем nums[right] в качестве базового элемента, то все будет наоборот, необходимо сначала выполнить "поиск слева направо".

В: Почему при оптимизации глубины рекурсии быстрой сортировки выбор более короткого массива гарантирует, что глубина рекурсии не превысит \log n?

Глубина рекурсии -- это количество текущих невозвращенных рекурсивных методов. На каждом раунде разделения с опорным элементом мы разделяем исходный массив на два подмассива. После оптимизации глубины рекурсии длина подмассива, в который происходит рекурсия, максимум составляет половину длины исходного массива. Предположим наихудший случай, когда длина всегда составляет половину, тогда конечная глубина рекурсии будет \log n.

Вспомним исходную быструю сортировку, мы можем последовательно рекурсивно обрабатывать массивы большей длины, в наихудшем случае n, n - 1, \dots, 2, 1, глубина рекурсии составит n. Оптимизация глубины рекурсии может избежать такой ситуации.

В: Когда все элементы в массиве равны, временная сложность быстрой сортировки составляет O(n^2)? Как обработать такой случай деградации?

Да. В этом случае можно рассмотреть разделение массива с помощью опорного элемента на три части: меньше, равно и больше базового элемента. Рекурсия выполняется только для частей меньше и больше. При таком методе для массива, где все входные элементы равны, достаточно одного раунда разделения с опорным элементом для завершения сортировки.

В: Почему наихудшая временная сложность блочной сортировки составляет O(n^2)?

В наихудшем случае все элементы попадают в один блок. Если мы используем алгоритм O(n^2) для сортировки этих элементов, то временная сложность составит O(n^2).