Сорт | Алгоритм сортировки |
---|---|
Структура данных | Множество |
Худший вариант производительности | |
Лучшая производительность | (различные ключи) [1] [2] или (одинаковые ключи) |
Средняя производительность | |
Наихудшая сложность пространства | всего вспомогательный |
В информатике пирамидальная сортировка — это эффективный алгоритм сортировки , основанный на сравнении , который реорганизует входной массив в кучу (структуру данных, в которой каждый узел больше своих дочерних узлов), а затем многократно удаляет самый большой узел из этой кучи, помещая его в конец массива. [3]
Хотя на практике на большинстве машин он несколько медленнее, чем хорошо реализованная быстрая сортировка , он имеет преимущества очень простой реализации и более благоприятного времени выполнения O ( n log n ) в худшем случае . Большинство реальных вариантов быстрой сортировки включают реализацию пирамидальной сортировки в качестве запасного варианта, если они обнаружат, что быстрая сортировка становится вырожденной. Пирамидальная сортировка — это алгоритм на месте , но это не стабильная сортировка .
Пирамидальная сортировка была изобретена Дж. У. Дж. Уильямсом в 1964 году. [4] В статье также была представлена двоичная куча как полезная структура данных сама по себе. [5] В том же году Роберт У. Флойд опубликовал улучшенную версию, которая могла сортировать массив на месте, продолжив свои ранние исследования алгоритма сортировки деревьев . [5]
Алгоритм пирамидальной сортировки можно разделить на две фазы: построение кучи и извлечение кучи.
Куча — это неявная структура данных , которая не занимает места за пределами массива объектов, подлежащих сортировке; массив интерпретируется как полное двоичное дерево , где каждый элемент массива является узлом, а родительские и дочерние связи каждого узла определяются простой арифметикой индексов массива. Для массива с нулевой базой корневой узел хранится под индексом 0, а узлы, связанные с узлом, i
являются
iLeftChild(i) = 2⋅i + 1 iПравыйРебенок(i) = 2⋅i + 2 iParent(i) = пол((i−1) / 2)
где функция floor округляет вниз до предыдущего целого числа. Для более подробного объяснения см. Двоичная куча § Реализация кучи .
Это бинарное дерево является max-heap, когда каждый узел больше или равен обоим своим потомкам. Эквивалентно, каждый узел меньше или равен своему родителю. Это правило, применяемое ко всему дереву, приводит к тому, что максимальный узел располагается в корне дерева.
На первом этапе из данных строится куча (см. Двоичная куча § Построение кучи ).
На втором этапе куча преобразуется в сортированный массив путем многократного удаления наибольшего элемента из кучи (корня кучи) и помещения его в конец массива. Куча обновляется после каждого удаления для поддержания свойства кучи. После удаления всех объектов из кучи результатом становится сортированный массив.
Пирамидальная сортировка обычно выполняется на месте. На первом этапе массив делится на несортированный префикс и упорядоченный по куче суффикс (изначально пустой). Каждый шаг сжимает префикс и расширяет суффикс. Когда префикс пуст, эта фаза завершается. На втором этапе массив делится на упорядоченный по куче префикс и отсортированный суффикс (изначально пустой). Каждый шаг сжимает префикс и расширяет суффикс. Когда префикс пуст, массив сортируется.
Алгоритм пирамидальной сортировки начинается с перестановки массива в двоичную максимальную кучу. Затем алгоритм многократно меняет местами корень кучи (наибольший элемент, оставшийся в куче) с ее последним элементом, который затем объявляется частью отсортированного суффикса. Затем куча, которая была повреждена заменой корня, восстанавливается так, что наибольший элемент снова оказывается в корне. Это повторяется до тех пор, пока в куче не останется только одно значение.
Шаги следующие:
heapify()
функцию на массиве. Это создаст кучу из массива за O ( n ) операций.siftDown()
функцию для массива, чтобы переместить новый первый элемент на правильное место в куче.Операция heapify()
выполняется один раз и имеет производительность O ( n )siftDown()
. Функция вызывается n раз и требует O (log n ) работы каждый раз, поскольку ее обход начинается с корневого узла. Таким образом, производительность этого алгоритма составляет O ( n + n log n ) = O ( n log n ) .
Сердцем алгоритма является siftDown()
функция. Она создает двоичные кучи из меньших куч и может рассматриваться двумя эквивалентными способами:
Чтобы установить свойство max-heap в корне, необходимо сравнить до трех узлов (корень и два его потомка), и наибольший узел должен быть сделан корнем. Это проще всего сделать, найдя наибольшего потомка, а затем сравнив его с корнем. Есть три случая:
siftDown()
на поддереве, корнем которого является недавно пониженный бывший корень.Число итераций в одном siftdown()
вызове ограничено высотой дерева, которая равна ⌊ log 2 n ⌋ = O (log n ) .
Ниже приведен простой способ реализации алгоритма в псевдокоде . Массивы имеют нулевой базис и swap
используются для обмена двух элементов массива. Движение «вниз» означает от корня к листьям или от меньших индексов к большим. Обратите внимание, что во время сортировки наибольший элемент находится в корне кучи в a[0]
, тогда как в конце сортировки наибольший элемент находится в a[end]
.
процедура heapsort(a, count) имеет входные данные: неупорядоченный массив a длины count (Создайте кучу в массиве a так, чтобы наибольшее значение оказалось в корне) heapify(a, count) (Следующий цикл сохраняет инварианты , что a[0:end−1] является кучей, и каждый элемент a[end:count−1] за пределами end больше, чем все элементы до него, т.е. a[end:count−1] находится в отсортированном порядке.) конец ← количество while end > 1 do (размер кучи уменьшается на единицу) конец ← конец − 1 (a[0] — корень и наибольшее значение. Перестановка перемещает его вперед отсортированных элементов.) поменять местами(a[конец], a[0]) (обмен испортил свойство кучи, поэтому восстановите его) siftDown(a, 0, конец)
Процедура сортировки использует две подпрограммы, heapify
и siftDown
. Первая — это общая процедура построения кучи на месте, а вторая — общая подпрограмма для реализации heapify
.
(Поместить элементы 'a' в порядке кучи, на месте) процедура heapify(a, count) - это (start инициализируется первым конечным узлом) (последний элемент в массиве, отсчитываемом от 0, имеет индекс count-1; найти родительский элемент этого элемента) начало ← iParent(count-1) + 1 while start > 0 do (перейти к последнему узлу, не входящему в кучу) начать ← начать − 1 (просеиваем узел с индексом «start» до нужного места так, чтобы все узлы ниже начального индекса располагались в порядке кучи) siftDown(a, начало, количество) (после просеивания вниз по корню все узлы/элементы располагаются в порядке кучи) (Восстановить кучу, корневой элемент которой находится по индексу 'start', предполагая, что кучи, корнем которых являются ее дочерние элементы, являются допустимыми) procedure siftDown(a, root, end) is while iLeftChild(root) < end do (Пока у корня есть хотя бы один дочерний элемент) child ← iLeftChild(root) (Левый дочерний элемент корня) (Если есть правый дочерний элемент и этот дочерний элемент больше) if child+1 < end and a[child] < a[child+1] then ребенок ← ребенок + 1 если а[корень] < а[ребенок] тогда swap(a[корень], a[ребенок]) root ← child (повторите, чтобы продолжить просеивание дочернего элемента) else (Корень содержит наибольший элемент. Поскольку мы можем предположить, что кучи, корнем которых являются дочерние элементы, являются допустимыми, это означает, что мы закончили.) return
Процедура heapify
работает путем построения небольших куч и многократного их слияния с помощью siftDown
. Она начинается с листьев, замечая, что они являются тривиальными, но допустимыми кучами сами по себе, а затем добавляет родителей. Начиная с элемента n /2 и двигаясь в обратном направлении, каждый внутренний узел становится корнем допустимой кучи путем просеивания вниз. Последний шаг — просеивание вниз первого элемента, после чего весь массив подчиняется свойству кучи.
Чтобы увидеть, что это занимает O ( n ) времени, посчитайте наихудшее число siftDown
итераций. Вторая половина массива требует ноль итераций, предыдущая четверть требует не более одной итерации, восьмая перед ней требует не более двух итераций, шестнадцатая перед ней требует не более трех и так далее.
Если посмотреть с другой стороны, если предположить, что каждый siftDown
вызов требует максимального количества итераций, то первая половина массива потребует одну итерацию, первая четверть — еще одну (всего 2), первая восьмая — еще одну (всего 3) и т. д.
Это дает в сумме n /2 + n /4 + n /8 + ⋯ = n⋅(1/2 + 1/4 + 1/8 + ⋯) , где бесконечная сумма — это хорошо известная геометрическая прогрессия , сумма которой равна 1 , поэтому произведение просто равно n .
Вышеприведенное является приближением. Точное наихудшее число сравнений во время фазы построения кучи пирамидальной сортировки, как известно, равно 2 n − 2 s 2 ( n ) − e 2 ( n ) , где s 2 ( n ) — это количество битов 1 в двоичном представлении n , а e 2 ( n ) — это количество конечных битов 0 . [6] [7]
Хотя удобно думать о двух фазах по отдельности, большинство реализаций объединяют их, позволяя одному экземпляру siftDown
быть развернутым в строке . [8] : Алгоритм H Две переменные (здесь, start
и end
) отслеживают границы области кучи. Часть массива до start
не отсортирована, в то время как часть, начинающаяся с , end
отсортирована. Построение кучи уменьшается , start
пока не станет равным нулю, после чего извлечение кучи уменьшается, end
пока не станет равным 1, и массив будет полностью отсортирован.
процедура heapsort(a, count) имеет входные данные: неупорядоченный массив a длины count начало ← этаж(кол-во/2) конец ← количество while end > 1 do if start > 0 then (конструкция кучи) начать ← начать − 1 иначе (извлечение кучи) конец ← конец − 1 поменять местами(a[конец], a[0]) (Далее — siftDown(a, start, end)) корень ← начало пока iLeftChild(root) < конец делать child ← iLeftChild(корень) (Если есть правый потомок и этот потомок больше) если потомок+1 < конец и a[ребенок] < a[ребенок+1] тогда ребенок ← ребенок + 1 если а[корень] < а[ребенок] тогда swap(a[корень], a[ребенок]) root ← child (повторить, чтобы продолжить просеивание потомка) в противном случае break (вернуться к внешнему циклу)
Описание выше использует улучшенный алгоритм построения кучи Флойда, который работает за время O ( n ) и использует тот же siftDown
примитив, что и фаза извлечения кучи. Хотя этот алгоритм, будучи и более быстрым, и более простым в программировании, используется всеми практическими реализациями пирамидальной сортировки, оригинальный алгоритм Уильямса может быть более понятным и необходим для реализации более общей двоичной очереди приоритетов кучи .
Вместо того, чтобы объединять множество маленьких куч, алгоритм Уильямса поддерживает одну единственную кучу в начале массива и многократно добавляет дополнительный элемент с помощью siftUp
примитива. Находясь в конце массива, новый элемент является листом и не имеет потомков, о которых нужно беспокоиться, но может нарушить свойство кучи, будучи больше своего родителя. В этом случае поменяйте его с его родителем и повторяйте тест, пока родитель не станет больше или не останется ни одного родителя (мы достигли корня). В псевдокоде это выглядит так:
процедура siftUp(a, end) имеет входные данные: a — массив, упорядоченный по куче до end-1. end — узел для просеивания. while end > 0 родитель := iParent(конец) если a[parent] < a[end] тогда (вне максимального порядка кучи) swap(a[родитель], a[конец]) конец := родитель (продолжить просеивание) иначе возврат процедура heapify(a, count) — это (начнем с тривиальной одноэлементной кучи) конец := 1 while end < count (просеиваем узел с индексом end до нужного места так, чтобы все узлы выше конечного индекса располагались в порядке кучи) просеятьВверх(а, конец) конец := конец + 1 (после просеивания последнего узла все узлы располагаются в порядке кучи)
Чтобы понять, почему этот алгоритм может занять асимптотически больше времени для построения кучи ( O ( n log n ) против O ( n ) в худшем случае), обратите внимание, что в алгоритме Флойда почти все вызовы siftDown
операций применяются к очень маленьким кучам. Половина куч являются тривиальными кучами высоты 1 и могут быть полностью пропущены, половина оставшихся — высотой 2 и т. д. Только два вызова выполняются для куч размером n /2 , и только одна siftDown
операция выполняется для полной кучи из n элементов. Общая средняя siftDown
операция занимает O (1) времени.
Напротив, в алгоритме Уильямса большинство вызовов siftUp
производятся на больших кучах высотой O (log n ) . Половина вызовов производится с размером кучи n /2 или более, три четверти производятся с размером кучи n /4 или более и т. д. Хотя среднее число шагов похоже на технику Флойда, [9] : 3 предварительно отсортированных входных данных приведут к наихудшему случаю: каждый добавленный узел просеивается до корня, поэтому средний вызов siftUp
потребует приблизительно (log 2 n − 1)/2 + (log 2 n − 2)/4 + (log 2 n − 3)/8 + ⋯ = log 2 n − (1 + 1/2 + 1/4 + ⋯) = log 2 n − 2 итераций.
Поскольку в нем доминирует вторая фаза извлечения кучи, сам алгоритм пирамидальной сортировки имеет временную сложность O ( n log n ) при использовании любой версии heapify.
Пирамидальная сортировка снизу вверх — это вариант, который уменьшает количество требуемых сравнений на значительный фактор. В то время как обычная пирамидальная сортировка сверху вниз требует 2 n log 2 n + O ( n ) сравнений в худшем случае и в среднем [10] , вариант снизу вверх требует n log 2 n + O (1) сравнений в среднем [10] и 1,5 n log 2 n + O ( n ) в худшем случае. [11]
Если сравнения дешевы (например, целочисленные ключи), то разница не важна, [12] поскольку нисходящая пирамидальная сортировка сравнивает значения, которые уже загружены из памяти. Если же сравнения требуют вызова функции или другой сложной логики, то восходящая пирамидальная сортировка выгодна.
Это достигается с помощью более сложной siftDown
процедуры. Изменение немного улучшает фазу линейного времени построения кучи, [13] , но более существенно во второй фазе. Как и в нисходящей пирамидальной сортировке, каждая итерация второй фазы извлекает вершину кучи, a[0]
и заполняет образовавшийся пробел с помощью a[end]
, затем просеивает этот последний элемент вниз по куче. Но этот элемент пришел с самого нижнего уровня кучи, то есть это один из самых маленьких элементов в куче, поэтому просеивание вниз, вероятно, потребует много шагов, чтобы переместить его обратно вниз. [14] В нисходящей пирамидальной сортировке каждый шаг siftDown
требует двух сравнений, чтобы найти минимум из трех элементов: новый узел и его двух дочерних элементов.
Пирамидальная сортировка снизу вверх концептуально заменяет корень значением −∞ и просеивает его вниз, используя только одно сравнение на уровень (поскольку ни один дочерний элемент не может быть меньше −∞), пока не будут достигнуты листья, затем заменяет −∞ правильным значением и просеивает его вверх (опять же, используя одно сравнение на уровень), пока не будет найдена правильная позиция.
Это помещает корень в то же место, что и сверху вниз siftDown
, но для нахождения этого места требуется меньше сравнений. Для любой отдельной siftDown
операции техника снизу вверх выгодна, если количество движений вниз составляет не менее 2 ⁄ 3 высоты дерева (когда количество сравнений составляет 4 ⁄ 3 высоты для обеих техник), и оказывается, что это более чем верно в среднем, даже для наихудших входных данных. [11]
Наивная реализация этого концептуального алгоритма привела бы к избыточному копированию данных, поскольку просеивание вверх отменяет часть просеивания вниз. Практическая реализация ищет вниз лист, где будет размещено −∞, затем вверх, где должен быть размещен корень . Наконец, восходящий обход продолжается до начальной позиции корня, не выполняя больше сравнений, но обмениваясь узлами для завершения необходимой перестановки. Эта оптимизированная форма выполняет то же количество обменов, что и сверху вниз siftDown
.
Поскольку она доходит до самого низа, а затем возвращается наверх, некоторые авторы называют ее пирамидальной сортировкой с отскоком . [15]
Функция leafSearch(a, i, end) — это ж ← я while iRightChild(j) < end do (Определить, какой из двух дочерних элементов j больше) if a[iRightChild(j)] > a[iLeftChild(j)] then j ← iПравыйРебенок(j) еще j ← iLeftChild(j) (На последнем уровне может быть только один дочерний элемент) если iLeftChild(j) < end тогда j ← iLeftChild(j) возвращение j
Возвращаемое значение leafSearch
используется в измененной siftDown
процедуре: [11]
процедура siftDown(a, i, end) — это j ← листПоиск(a, i, конец) пока a[i] > a[j] делают j ← iРодитель(j) пока j > я делаю поменять местами(a[i], a[j]) j ← iРодитель(j)
Было объявлено, что пирамидальная сортировка снизу вверх превосходит быструю сортировку (с медианным выбором из трех опорных элементов) на массивах размером ≥16000. [10]
Повторная оценка этого алгоритма в 2008 году показала, что он не быстрее, чем нисходящая пирамидальная сортировка для целочисленных ключей, предположительно потому, что современное предсказание ветвлений сводит к нулю стоимость предсказуемых сравнений, которых удаётся избежать восходящей пирамидальной сортировке. [12]
Дальнейшее уточнение выполняет бинарный поиск в восходящем поиске и сортирует в худшем случае ( n +1)(log 2 ( n +1) + log 2 log 2 ( n +1) + 1,82) + O (log 2 n ) сравнений, приближаясь к информационной теоретико-нижней границе n log 2 n − 1,4427 n сравнений. [16]
Вариант, который использует два дополнительных бита на внутренний узел ( всего n −1 бит для n -элементной кучи) для кэширования информации о том, какой дочерний элемент больше (для хранения трех случаев: левый, правый и неизвестный требуется два бита) [13], использует менее n log 2 n + 1,1 n сравнений. [17]
Пирамидальная сортировка в первую очередь конкурирует с быстрой сортировкой — другим очень эффективным универсальным алгоритмом сортировки, основанным на нестабильном сравнении на месте.
Главными преимуществами пирамидальной сортировки являются ее простой, нерекурсивный код , минимальные требования к вспомогательной памяти и надежно хорошая производительность: ее лучшие и худшие случаи находятся в пределах небольшого постоянного множителя друг от друга и теоретической нижней границы сравнительных сортировок . Хотя она не может превзойти O ( n log n ) для предварительно отсортированных входных данных, она также не страдает от худшего случая быстрой сортировки O ( n 2 ) .
Реализации быстрой сортировки в реальном мире используют различные эвристики , чтобы избежать худшего случая, но это делает их реализацию намного более сложной, и такие реализации, как introsort и pattern-defeating quicksort [29], используют пирамидальную сортировку в качестве последнего средства отката, если они обнаруживают вырожденное поведение. Таким образом, их производительность в худшем случае немного хуже, чем если бы пирамидальная сортировка использовалась с самого начала.
Основными недостатками пирамидальной сортировки являются ее плохая локальность ссылок и ее последовательная природа; доступы к неявному дереву сильно разбросаны и в основном случайны, и не существует простого способа преобразовать ее в параллельный алгоритм .
Гарантии производительности в худшем случае делают пирамидальную сортировку популярной в вычислениях в реальном времени и системах, связанных с вредоносным выбором входных данных [30], таких как ядро Linux. [31] Сочетание небольшой реализации и надежной «достаточно хорошей» производительности делает ее популярной во встраиваемых системах и, в целом, в любых приложениях, где сортировка не является узким местом производительности . Например, пирамидальная сортировка идеально подходит для сортировки списка имен файлов для отображения, но система управления базами данных , вероятно, захочет более агрессивно оптимизированный алгоритм сортировки.
Хорошо реализованная быстрая сортировка обычно в 2–3 раза быстрее пирамидальной сортировки. [19] [32] Хотя быстрая сортировка требует меньше сравнений, это незначительный фактор. (Результаты, утверждающие, что в два раза больше сравнений, измеряют версию сверху вниз; см. § Пирамидальная сортировка снизу вверх.) Главное преимущество быстрой сортировки — гораздо лучшая локальность ссылок: разбиение представляет собой линейное сканирование с хорошей пространственной локальностью, а рекурсивное подразделение имеет хорошую временную локальность. При дополнительных усилиях быструю сортировку можно реализовать в коде, в основном без ветвлений , [33] [34] , и для параллельной сортировки подразделов можно использовать несколько ЦП. Таким образом, быстрая сортировка предпочтительнее, когда дополнительная производительность оправдывает усилия по реализации.
Другой основной алгоритм сортировки O ( n log n ) — сортировка слиянием , но он редко конкурирует напрямую с пирамидальной сортировкой, поскольку она не является локальной. Требование сортировки слиянием для Ω( n ) дополнительного пространства (примерно половина размера входных данных) обычно является запретительным, за исключением ситуаций, когда сортировка слиянием имеет явное преимущество:
Примеры сортируют значения { 6, 5, 3, 1, 8, 7, 2, 4 } в порядке возрастания с использованием обоих алгоритмов построения кучи. Сравниваемые элементы показаны жирным шрифтом. Обычно их два при просеивании вверх и три при просеивании вниз, хотя их может быть меньше, когда достигается вершина или низ дерева.
Куча | Несортированный | Поменять местами элементы |
---|---|---|
6 | 5, 3, 1, 8, 7, 2, 4 | |
6 , 5 | 3, 1, 8, 7, 2, 4 | |
6 , 5, 3 | 1, 8, 7, 2, 4 | |
6, 5 , 3, 1 | 8, 7, 2, 4 | |
6, 5 , 3, 1, 8 | 7, 2, 4 | 5 ↔ 8 |
6 , 8 , 3, 1, 5 | 7, 2, 4 | 6 ↔ 8 |
8 , 6, 3, 1, 5 | 7, 2, 4 | |
8, 6, 3 , 1, 5, 7 | 2, 4 | 3 ↔ 7 |
8 , 6, 7 , 1, 5, 3 | 2, 4 | |
8, 6, 7 , 1, 5, 3, 2 | 4 | |
8, 6, 7, 1 , 5, 3, 2, 4 | 1 ↔ 4 | |
8, 6 , 7, 4 , 5, 3, 2, 1 |
Несортированный | Куча | Поменять местами элементы |
---|---|---|
6, 5, 3, 1 | 8, 7, 2, 4 | |
6, 5, 3 | 1 , 8, 7, 2, 4 | 1 ↔ 4 |
6, 5, 3 | 4, 8, 7, 2, 1 | |
6, 5 | 3 , 4, 8, 7 , 2 , 1 | 3 ↔ 7 |
6, 5 | 7, 4, 8, 3 , 2, 1 | |
6 | 5 , 7, 4 , 8 , 3, 2, 1 | 5 ↔ 8 |
6 | 8, 7, 4, 5 , 3, 2, 1 | |
6 , 8 , 7 , 4, 5, 3, 2, 1 | 6 ↔ 8 | |
8, 6 , 7, 4 , 5 , 3, 2, 1 |
Куча | Сортированный массив | Поменять местами элементы | Подробности |
---|---|---|---|
8 , 6, 7, 4, 5, 3, 2, 1 | 8 ↔ 1 | Добавьте 8 к отсортированному массиву, поменяв его местами с 1. | |
1 , 6 , 7 , 4, 5, 3, 2 | 8 | 1 ↔ 7 | Поменяйте местами 1 и 7, так как они не в том порядке в куче. |
7, 6, 1 , 4, 5, 3 , 2 | 8 | 1 ↔ 3 | Поменяйте местами 1 и 3, так как они не в том порядке в куче. |
7, 6, 3, 4, 5, 1 , 2 | 8 | 1 не имеет потомков; siftDown завершен | |
7 , 6, 3, 4, 5, 1, 2 | 8 | 7 ↔ 2 | Добавьте 7 к отсортированному массиву, поменяв его местами с 2 |
2 , 6 , 3 , 4, 5, 1 | 7, 8 | 2 ↔ 6 | Поменяйте местами 2 и 6, так как они не в том порядке в куче. |
6, 2 , 3, 4 , 5 , 1 | 7, 8 | 2 ↔ 5 | Поменяйте местами 2 и 5, так как они не в том порядке в куче. |
6, 5, 3, 4, 2 , 1 | 7, 8 | 2 не имеет потомков; siftDown завершен | |
6 , 5, 3, 4, 2, 1 | 7, 8 | 6 ↔ 1 | Добавьте 6 к отсортированному массиву, поменяв его местами с 1. |
1 , 5 , 3 , 4, 2 | 6, 7, 8 | 1 ↔ 5 | Поменяйте местами 1 и 5, так как они не в том порядке в куче. |
5, 1 , 3, 4 , 2 | 6, 7, 8 | 1 ↔ 4 | Поменяйте местами 1 и 4, так как они не в том порядке в куче. |
5, 4, 3, 1 , 2 | 6, 7, 8 | 1 не имеет потомков; siftDown завершен | |
5 , 4, 3, 1, 2 | 6, 7, 8 | 5 ↔ 2 | Добавьте 5 к отсортированному массиву, поменяв его местами с 2 |
2 , 4 , 3 , 1 | 5, 6, 7, 8 | 2 ↔ 4 | Поменяйте местами 2 и 4, так как они не в том порядке в куче. |
4, 2 , 3, 1 | 5, 6, 7, 8 | 2 больше 1; siftDown завершен | |
4 , 2, 3, 1 | 5, 6, 7, 8 | 4 ↔ 1 | Добавьте 4 к отсортированному массиву, поменяв его местами с 1 |
1 , 2 , 3 | 4, 5, 6, 7, 8 | 1 ↔ 3 | Поменяйте местами 1 и 3, так как они не в том порядке в куче. |
3, 2, 1 | 4, 5, 6, 7, 8 | 1 не имеет потомков; siftDown завершен | |
3 , 2, 1 | 4, 5, 6, 7, 8 | 1 ↔ 3 | Добавьте 3 к отсортированному массиву, поменяв его местами с 1. |
1 , 2 | 3, 4, 5, 6, 7, 8 | 1 ↔ 2 | Поменяйте местами 1 и 2, так как они не в том порядке в куче. |
2, 1 | 3, 4, 5, 6, 7, 8 | 1 не имеет потомков; siftDown завершен | |
2 , 1 | 3, 4, 5, 6, 7, 8 | 2 ↔ 1 | Добавьте 2 к отсортированному массиву, поменяв его местами с 1. |
1 | 2, 3, 4, 5, 6, 7, 8 | Добавить 1 к отсортированному массиву | |
1, 2, 3, 4, 5, 6, 7, 8 | Завершенный |
За неимением лучшего названия мы называем эту улучшенную программу «пирамидальная сортировка с отскоком » .
Напишите процедуру сортировки, похожую на пирамидальную сортировку, за исключением того, что она использует троичную кучу.