2.2 Построение кучи
Для построения кучи из обычного массива достаточно, чтобы для первых floor(n / 2) элементов этого массива выполнилось основное свойство кучи (поскольку остальные элементы не могут содержать потомков, т. е. являются листьями). Для этого мы последовательно, начиная с последних вершин, вызываем функцию heapify() для каждой вершины. Благодаря этому каждый вызов будет работать для вершины, у предков которой основное свойство уже выполнено.
Приведём код на C++.
//Строит в массиве A размера n кучу, возвращая её размер
int buildHeap(int* A, int n)
{
//Проходим, начиная с последней, все вершины,
//которые могут иметь потомков
for (int i = n >> 1; i;)
heapify(A, n, --i); //восстановим основное свойство
//Возвращаем размер построенной кучи
return n;
}
Данный алгоритм, очевидно, работает за время O(nlogn), поскольку n / 2 раз выполняется тело цикла, время работы которого равно O(logn). На самом деле для алгоритма верна более точная оценка O(n). Доказательство этого факта предлагается найти самостоятельно.
2.3 Алгоритм сортировки с помощью кучи
Алгоритм сортировки кучи прост и прозрачен. Сначала необходимо построить кучу в сортируемом массиве. Потом необходимо в цикле менять местами элемент A[1] с последним элементом кучи, уменьшать размер кучи и каждый раз восстанавливать основное свойство кучи для нового элемента A[1]. Таким образом, текущий наибольший элемент кучи каждый раз отправляется в конец кучи, т. е. ставится перед другими бОльшими элементами, а первый элемент снова становится наибольшим среди оставшихся. В итоге массив отсортирован. Всё гениальное просто.
Ниже – код на C++.
//Сортирует массив A размера n
void heapSort(int* A, int n)
{
//Сначала построим кучу
buildHeap(A, n);
//Теперь будем отправлять наибольшие элементы ближе к концу
//массива и восстанавливать основное свойство оставшейся кучи
for (int i = n - 1; i; --i) {
swap(A, A + i); //см. быструю сортировку
heapify(A, --n, 0);
}
}
Куча строится за время O(n). Цикл выполняется n – 1 раз, причём трудоёмкость тела цикла равна O(logn). Таким образом, время работы алгоритма сортировки с помощью кучи T(n) = O(nlogn).
3 Сортировки за линейное время
С помощью разрешающих деревьев показано, что без использования внутренней структуры сортируемых данных (при сортировке только с помощью сравнений элементов) сортировка за время, меньшее Ω(nlogn), невозможно. Но добиться линейного времени сортировки (т. е. сортировки за время O(n)) всё-таки можно, используя знания о внутренней структуре сортируемых данных.
Ниже будут рассмотрены два метода сортировки за линейное время: сортировка подсчётом и цифровая сортировка.
3.1 Сортировка подсчётом (counting sort)
Данная сортировка оперирует с целыми числами в диапазоне [0..k-1]. Для работы алгоритма за время O(n) должно быть выполнено условие k = O(n).
Суть алгоритма очень проста. Необходимо пройти весь исходный массив A и для каждого числа в этом массиве записать во вспомогательный массив размера k количество чисел, предшествующих текущему. Обладая такой информацией, мы сможем выводить элемент в результирующий массив B сразу в нужную позицию, которая зависит от количества предшествующих ему меньших элементов. При реализации необходимо только учесть, что в массиве A могут встречаться одинаковые элементы.
Код на C++ приведён ниже.
//Функция записывает в массив B длиной n отсортированные данные
//из массива A, содержащего целые числа в диапазоне [0..k-1]
void countingSort(const int* A, int* B, int n, int k)
{
//Выделим массив для хранения кол-ва предшествующих чисел
int *C = (int*)std::calloc(k, sizeof(int));
//Сначала сохраним для каждого числа кол-во его вхождений в массив
for (int i = 0; i < n; ++i)
++C[A[i]];
//Потом преобразуем массив C так, что для каждого числа будет
Уважаемый посетитель!
Чтобы распечатать файл, скачайте его (в формате Word).
Ссылка на скачивание - внизу страницы.