- PVSM.RU - https://www.pvsm.ru -
Перевод статьи подготовлен специально для студентов курса «Алгоритмы для разработчиков» [1].
Пирамидальная сортировка (или сортировка кучей, HeapSort) — это метод сортировки сравнением, основанный на такой структуре данных как двоичная куча. Она похожа на сортировку выбором, где мы сначала ищем максимальный элемент и помещаем его в конец. Далее мы повторяем ту же операцию для оставшихся элементов.
Давайте сначала определим законченное двоичное дерево. Законченное двоичное дерево — это двоичное дерево, в котором каждый уровень, за исключением, возможно, последнего, имеет полный набор узлов, и все листья расположены как можно левее (Источник Википедия [3]).
Двоичная куча — это законченное двоичное дерево, в котором элементы хранятся в особом порядке: значение в родительском узле больше (или меньше) значений в его двух дочерних узлах. Первый вариант называется max-heap, а второй — min-heap. Куча может быть представлена двоичным деревом или массивом.
Поскольку двоичная куча — это законченное двоичное дерево, ее можно легко представить в виде массива, а представление на основе массива является эффективным с точки зрения расхода памяти. Если родительский узел хранится в индексе I, левый дочерний элемент может быть вычислен как 2 I + 1, а правый дочерний элемент — как 2 I + 2 (при условии, что индексирование начинается с 0).
Алгоритм пирамидальной сортировки в порядке по возрастанию:
Процедура преобразования в кучу (далее процедура heapify) может быть применена к узлу, только если его дочерние узлы уже преобразованы. Таким образом, преобразование должно выполняться снизу вверх. Давайте разберемся с помощью примера:
Входные данные: 4, 10, 3, 5, 1
4(0)
/
10(1) 3(2)
/
5(3) 1(4)
Числа в скобках представляют индексы в представлении данных в виде массива.
Применение процедуры heapify к индексу 1:
4(0)
/
10(1) 3(2)
/
5(3) 1(4)
Применение процедуры heapify к индексу 0:
10(0)
/
5(1) 3(2)
/
4(3) 1(4)
Процедура heapify вызывает себя рекурсивно для создания кучи сверху вниз.
Рекомендация: Пожалуйста, сначала решите задачу на “PRACTICE”, прежде чем переходить к решению [4].
// Реализация пирамидальной сортировки на C++
#include <iostream>
using namespace std;
// Процедура для преобразования в двоичную кучу поддерева с корневым узлом i, что является
// индексом в arr[]. n - размер кучи
void heapify(int arr[], int n, int i)
{
int largest = i;
// Инициализируем наибольший элемент как корень
int l = 2*i + 1; // левый = 2*i + 1
int r = 2*i + 2; // правый = 2*i + 2
// Если левый дочерний элемент больше корня
if (l < n && arr[l] > arr[largest])
largest = l;
// Если правый дочерний элемент больше, чем самый большой элемент на данный момент
if (r < n && arr[r] > arr[largest])
largest = r;
// Если самый большой элемент не корень
if (largest != i)
{
swap(arr[i], arr[largest]);
// Рекурсивно преобразуем в двоичную кучу затронутое поддерево
heapify(arr, n, largest);
}
}
// Основная функция, выполняющая пирамидальную сортировку
void heapSort(int arr[], int n)
{
// Построение кучи (перегруппируем массив)
for (int i = n / 2 - 1; i >= 0; i--)
heapify(arr, n, i);
// Один за другим извлекаем элементы из кучи
for (int i=n-1; i>=0; i--)
{
// Перемещаем текущий корень в конец
swap(arr[0], arr[i]);
// вызываем процедуру heapify на уменьшенной куче
heapify(arr, i, 0);
}
}
/* Вспомогательная функция для вывода на экран массива размера n*/
void printArray(int arr[], int n)
{
for (int i=0; i<n; ++i)
cout << arr[i] << " ";
cout << "n";
}
// Управляющая программа
int main()
{
int arr[] = {12, 11, 13, 5, 6, 7};
int n = sizeof(arr)/sizeof(arr[0]);
heapSort(arr, n);
cout << "Sorted array is n";
printArray(arr, n);
}
// Реализация пирамидальной сортировки на Java
public class HeapSort
{
public void sort(int arr[])
{
int n = arr.length;
// Построение кучи (перегруппируем массив)
for (int i = n / 2 - 1; i >= 0; i--)
heapify(arr, n, i);
// Один за другим извлекаем элементы из кучи
for (int i=n-1; i>=0; i--)
{
// Перемещаем текущий корень в конец
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
// Вызываем процедуру heapify на уменьшенной куче
heapify(arr, i, 0);
}
}
// Процедура для преобразования в двоичную кучу поддерева с корневым узлом i, что является
// индексом в arr[]. n - размер кучи
void heapify(int arr[], int n, int i)
{
int largest = i; // Инициализируем наибольший элемент как корень
int l = 2*i + 1; // левый = 2*i + 1
int r = 2*i + 2; // правый = 2*i + 2
// Если левый дочерний элемент больше корня
if (l < n && arr[l] > arr[largest])
largest = l;
// Если правый дочерний элемент больше, чем самый большой элемент на данный момент
if (r < n && arr[r] > arr[largest])
largest = r;
// Если самый большой элемент не корень
if (largest != i)
{
int swap = arr[i];
arr[i] = arr[largest];
arr[largest] = swap;
// Рекурсивно преобразуем в двоичную кучу затронутое поддерево
heapify(arr, n, largest);
}
}
/* Вспомогательная функция для вывода на экран массива размера n */
static void printArray(int arr[])
{
int n = arr.length;
for (int i=0; i<n; ++i)
System.out.print(arr[i]+" ");
System.out.println();
}
// Управляющая программа
public static void main(String args[])
{
int arr[] = {12, 11, 13, 5, 6, 7};
int n = arr.length;
HeapSort ob = new HeapSort();
ob.sort(arr);
System.out.println("Sorted array is");
printArray(arr);
}
}
# Реализация пирамидальной сортировки на Python
# Процедура для преобразования в двоичную кучу поддерева с корневым узлом i, что является индексом в arr[]. n - размер кучи
def heapify(arr, n, i):
largest = i # Initialize largest as root
l = 2 * i + 1 # left = 2*i + 1
r = 2 * i + 2 # right = 2*i + 2
# Проверяем существует ли левый дочерний элемент больший, чем корень
if l < n and arr[i] < arr[l]:
largest = l
# Проверяем существует ли правый дочерний элемент больший, чем корень
if r < n and arr[largest] < arr[r]:
largest = r
# Заменяем корень, если нужно
if largest != i:
arr[i],arr[largest] = arr[largest],arr[i] # свап
# Применяем heapify к корню.
heapify(arr, n, largest)
# Основная функция для сортировки массива заданного размера
def heapSort(arr):
n = len(arr)
# Построение max-heap.
for i in range(n, -1, -1):
heapify(arr, n, i)
# Один за другим извлекаем элементы
for i in range(n-1, 0, -1):
arr[i], arr[0] = arr[0], arr[i] # свап
heapify(arr, i, 0)
# Управляющий код для тестирования
arr = [ 12, 11, 13, 5, 6, 7]
heapSort(arr)
n = len(arr)
print ("Sorted array is")
for i in range(n):
print ("%d" %arr[i]),
# Этот код предоставил Mohit Kumra
// Реализация пирамидальной сортировки на C#
using System;
public class HeapSort
{
public void sort(int[] arr)
{
int n = arr.Length;
// Построение кучи (перегруппируем массив)
for (int i = n / 2 - 1; i >= 0; i--)
heapify(arr, n, i);
// Один за другим извлекаем элементы из кучи
for (int i=n-1; i>=0; i--)
{
// Перемещаем текущий корень в конец
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
// вызываем процедуру heapify на уменьшенной куче
heapify(arr, i, 0);
}
}
// Процедура для преобразования в двоичную кучу поддерева с корневым узлом i, что является
// индексом в arr[]. n - размер кучи
void heapify(int[] arr, int n, int i)
{
int largest = i;
// Инициализируем наибольший элемент как корень
int l = 2*i + 1; // left = 2*i + 1
int r = 2*i + 2; // right = 2*i + 2
// Если левый дочерний элемент больше корня
if (l < n && arr[l] > arr[largest])
largest = l;
// Если правый дочерний элемент больше, чем самый большой элемент на данный момент
if (r < n && arr[r] > arr[largest])
largest = r;
// Если самый большой элемент не корень
if (largest != i)
{
int swap = arr[i];
arr[i] = arr[largest];
arr[largest] = swap;
// Рекурсивно преобразуем в двоичную кучу затронутое поддерево
heapify(arr, n, largest);
}
}
/* Вспомогательная функция для вывода на экран массива размера n */
static void printArray(int[] arr)
{
int n = arr.Length;
for (int i=0; i<n; ++i)
Console.Write(arr[i]+" ");
Console.Read();
}
//Управляющая программа
public static void Main()
{
int[] arr = {12, 11, 13, 5, 6, 7};
int n = arr.Length;
HeapSort ob = new HeapSort();
ob.sort(arr);
Console.WriteLine("Sorted array is");
printArray(arr);
}
}
//Этот код предоставил
// Akanksha Ra (Abby_akku)
<?php
// Реализация пирамидальной сортировки на Php
// Процедура для преобразования в двоичную кучу поддерева с корневым узлом i, что является
// индексом в arr[]. n - размер кучи
function heapify(&$arr, $n, $i)
{
$largest = $i; // Инициализируем наибольший элемент как корень
$l = 2*$i + 1; // левый = 2*i + 1
$r = 2*$i + 2; // правый = 2*i + 2
// Если левый дочерний элемент больше корня
if ($l < $n && $arr[$l] > $arr[$largest])
$largest = $l;
//Если правый дочерний элемент больше, чем самый большой элемент на данный момент
if ($r < $n && $arr[$r] > $arr[$largest])
$largest = $r;
// Если самый большой элемент не корень
if ($largest != $i)
{
$swap = $arr[$i];
$arr[$i] = $arr[$largest];
$arr[$largest] = $swap;
// Рекурсивно преобразуем в двоичную кучу затронутое поддерево
heapify($arr, $n, $largest);
}
}
//Основная функция, выполняющая пирамидальную сортировку
function heapSort(&$arr, $n)
{
// Построение кучи (перегруппируем массив)
for ($i = $n / 2 - 1; $i >= 0; $i--)
heapify($arr, $n, $i);
//Один за другим извлекаем элементы из кучи
for ($i = $n-1; $i >= 0; $i--)
{
// Перемещаем текущий корень в конец
$temp = $arr[0];
$arr[0] = $arr[$i];
$arr[$i] = $temp;
// вызываем процедуру heapify на уменьшенной куче
heapify($arr, $i, 0);
}
}
/* Вспомогательная функция для вывода на экран массива размера n */
function printArray(&$arr, $n)
{
for ($i = 0; $i < $n; ++$i)
echo ($arr[$i]." ") ;
}
// Управляющая программа
$arr = array(12, 11, 13, 5, 6, 7);
$n = sizeof($arr)/sizeof($arr[0]);
heapSort($arr, $n);
echo 'Sorted array is ' . "n";
printArray($arr , $n);
// Этот код предоставил Shivi_Aggarwal
?>
Вывод:
Отсортированный массив:
5 6 7 11 12 13
Здесь предыдущий C-код для справки.
Замечания:
Пирамидальная сортировка — это вполне годный алгоритм. Его типичная реализация не стабильна, но может быть таковой сделана (см. Здесь [5]).
Временная сложность: Временная сложность heapify — O(Logn). Временная сложность createAndBuildHeap() равна O(n), а общее время работы пирамидальной сортировки — O(nLogn).
Применения пирамидальной сортировки:
Алгоритм пирамидальной сортировки имеет ограниченное применение, потому что Quicksort (Быстрая сортировка) и Mergesort (Сортировка слиянием) на практике лучше. Тем не менее, сама структура данных кучи используется довольно часто. См. Применения структуры данных кучи [8]
Скриншоты:
— (Теперь, когда мы построили кучу, мы должны преобразовать ее в max-heap)
— (В max-heap родительский узел всегда больше или равен по отношению к дочерним
10 больше 4. Поэтому мы меняем местами 4 и 10)
— (В max-heap родительский узел всегда больше или равен по отношению к дочерним
5 больше 4. По этому мы меняем местами 5 и 4)
— (Меняем местами первый и последний узлы и удаляем последний из кучи)
Тест по пирамидальной сортировке [9]
Другие алгоритмы сортировки на GeeksforGeeks/GeeksQuiz:
Быстрая сортировка [10], Сортировка выбором [11], Сортировка пузырьком [12], Сортировка вставками [13], Сортировка слиянием [14], Пирамидальная сортировка [15], Поразрядная сортировка [16], Сортировка подсчетом [17], Блочная сортировка [18], Сортировка Шелла [19], Сортировка расческой [20], Сортировка подсчетом со списком [21].
Пожалуйста, оставляйте комментарии, если вы обнаружите что-то неправильное или вы хотите поделиться дополнительной информацией по обсуждаемой выше теме.
Автор: vlstrochkov
Источник [23]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/algoritmy/323981
Ссылки в тексте:
[1] «Алгоритмы для разработчиков»: https://otus.pw/3aA3/
[2] двоичная куча: https://www.geeksforgeeks.org/binary-heap/
[3] Википедия: http://en.wikipedia.org/wiki/Binary_tree#Types_of_binary_trees
[4] Рекомендация: Пожалуйста, сначала решите задачу на “PRACTICE”, прежде чем переходить к решению: https://practice.geeksforgeeks.org/problems/heap-sort/1
[5] Здесь: https://www.geeksforgeeks.org/stability-in-sorting-algorithms/
[6] Отсортировать почти отсортированный (или отсортированный на K позиций) массив: https://www.geeksforgeeks.org/nearly-sorted-algorithm/
[7] k самых больших (или самых маленьких) элементов в массиве: https://www.geeksforgeeks.org/k-largestor-smallest-elements-in-an-array/
[8] Применения структуры данных кучи: https://www.geeksforgeeks.org/applications-of-heap-data-structure/
[9] Тест по пирамидальной сортировке: https://www.geeksforgeeks.org/quiz-heapsort-gq/
[10] Быстрая сортировка: https://www.geeksforgeeks.org/quick-sort/
[11] Сортировка выбором: https://www.geeksforgeeks.org/selection-sort/
[12] Сортировка пузырьком: https://www.geeksforgeeks.org/bubble-sort/
[13] Сортировка вставками: https://www.geeksforgeeks.org/insertion-sort/
[14] Сортировка слиянием: https://www.geeksforgeeks.org/merge-sort/
[15] Пирамидальная сортировка: https://www.geeksforgeeks.org/heap-sort/
[16] Поразрядная сортировка: https://www.geeksforgeeks.org/radix-sort/
[17] Сортировка подсчетом: https://www.geeksforgeeks.org/counting-sort/
[18] Блочная сортировка: https://www.geeksforgeeks.org/bucket-sort-2/
[19] Сортировка Шелла: https://www.geeksforgeeks.org/shellsort/
[20] Сортировка расческой: https://www.geeksforgeeks.org/comb-sort/
[21] Сортировка подсчетом со списком: https://www.geeksforgeeks.org/pigeonhole-sort/
[22] Практикум по сортировке: https://practice.geeksforgeeks.org/tag-page.php?tag=sorting&isCmp=0
[23] Источник: https://habr.com/ru/post/460087/?utm_campaign=460087&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.