- •Что я должен предварительно знать?
- •Какая версия Delphi мне нужна?
- •Что и где я могу найти в книге, или, другими словами, из чего состоит эта книга?
- •Глава 11 сконцентрирована вокруг нескольких технологий сжатия. Подробно рассматриваются такие алгоритмы сжатия, как Шеннона‑Фано, Хаффмана, с применением скошенного дерева и lz77.
- •От изготовителя fb2.
- •Благодарности
- •Глава 1. Что такое алгоритм?
- •Что такое алгоритм?
- •Анализ алгоритмов
- •О‑нотация
- •Лучший, средний и худший случаи
- •Алгоритмы и платформы
- •Виртуальная память и страничная организация памяти
- •Пробуксовка
- •Локальность ссылок
- •Кэш процессора
- •Выравнивание данных
- •Пространство или время
- •Длинные строки
- •Использование ключевого слова const
- •Осторожность в отношении автоматического преобразования типов
- •Тестирование и отладка
- •Утверждения
- •Комментарии
- •Протоколирование
- •Трассировка
- •Анализ покрытия
- •Тестирование модулей
- •Отладка
- •Глава 2. Массивы.
- •Массивы
- •Типы массивов в Delphi
- •Стандартные массивы
- •Динамические массивы
- •Новые динамические массивы
- •Класс tList, массив указателей
- •Краткий обзор класса tList
- •Класс TtdObjectList
- •Массивы на диске
- •Глава 3. Связные списки, стеки и очереди
- •Односвязные списки
- •Узлы связного списка
- •Создание односвязного списка
- •Вставка и удаление элементов в односвязном списке
- •Соображения по поводу эффективности
- •Использование начального узла
- •Использование диспетчера узлов
- •Класс односвязного списка
- •Двухсвязные списки
- •Вставка и удаление элементов в двухсвязном списке
- •Использование начального и конечного узлов
- •Использование диспетчера узлов
- •Класс двухсвязного списка
- •Достоинства и недостатки связных списков
- •Стеки на основе односвязных списков
- •Стеки на основе массивов
- •Пример использования стека
- •Очереди
- •Очереди на основе односвязных списков
- •Очереди на основе массивов
- •Глава 4. Поиск.
- •Процедуры сравнения
- •Последовательный поиск
- •Массивы
- •Связные списки
- •Бинарный поиск
- •Массивы
- •Связные списки
- •Вставка элемента в отсортированный контейнер
- •Глава 5. Сортировка
- •Алгоритмы сортировки
- •Тасование массива tList
- •Основы сортировки
- •Самые медленные алгоритмы сортировки
- •Пузырьковая сортировка
- •Шейкер‑сортировка
- •Сортировка методом выбора
- •Сортировка методом вставок
- •Быстрые алгоритмы сортировки
- •Сортировка методом Шелла
- •Сортировка методом прочесывания
- •Самые быстрые алгоритмы сортировки
- •Сортировка слиянием
- •Быстрая сортировка
- •Сортировка слиянием для связных списков
- •Глава 6. Рандомизированные алгоритмы.
- •Генерация случайных чисел
- •Критерий хи‑квадрат
- •Метод средних квадратов
- •Линейный конгруэнтный метод
- •Тестирование
- •Тест на однородность
- •Тест на пропуски
- •Тест "покер"
- •Тест "сбор купонов"
- •Результаты выполнения тестов
- •Комбинирование генераторов
- •Аддитивные генераторы
- •Тасующие генераторы
- •Выводы по алгоритмам генерации случайных чисел
- •Другие распределения случайных чисел
- •Списки с пропусками
- •Поиск в списке с пропусками
- •Вставка в список с пропусками
- •Удаление из списка с пропусками
- •Полная реализация класса связного списка
- •Глава 7. Хеширование и хеш‑таблицы
- •Функции хеширования
- •Простая функция хеширования для строк
- •Функции хеширования pjw
- •Разрешение конфликтов посредством линейного зондирования
- •Преимущества и недостатки линейного зондирования
- •Удаление элементов из хеш‑таблицы с линейным зондированием
- •Класс хеш‑таблиц с линейным зондированием
- •Другие схемы открытой адресации
- •Квадратичное зондирование
- •Псевдослучайное зондирование
- •Двойное хеширование
- •Разрешение конфликтов посредством связывания
- •Преимущества и недостатки связывания
- •Класс связных хеш‑таблиц
- •Разрешение конфликтов посредством группирования
- •Хеш‑таблицы на диске
- •Расширяемое хеширование
- •Глава 8. Бинарные деревья.
- •Создание бинарного дерева
- •Вставка и удаление с использованием бинарного дерева
- •Перемещение по бинарному дереву
- •Обход в ширину, симметричный обход и обход в глубину
- •Обход по уровням
- •Реализация класса бинарных деревьев
- •Деревья бинарного поиска
- •Вставка в дереве бинарного поиска
- •Удаление из дерева бинарного поиска
- •Реализация класса дерева бинарного поиска
- •Перекомпоновка дерева бинарного поиска
- •Скошенные деревья
- •Реализация класса скошенного дерева
- •Красно‑черные деревья
- •Вставка в красно‑черное дерево
- •Удаление из красно‑черного дерева
- •Глава 9. Очереди по приоритету и пирамидальная сортировка.
- •Очередь по приоритету
- •Первая простая реализация
- •Вторая простая реализация
- •Сортирующее дерево
- •Вставка в сортирующее дерево
- •Удаление из сортирующего дерева
- •Реализация очереди по приоритету при помощи сортирующего дерева
- •Пирамидальная сортировка
- •Алгоритм Флойда
- •Завершение пирамидальной сортировки
- •Расширение очереди по приоритету
- •Восстановление свойства пирамидальное
- •Отыскание произвольного элемента в сортирующем дереве
- •Реализация расширенной очереди по приоритету
- •Глава 10. Конечные автоматы и регулярные выражения.
- •Конечные автоматы
- •Использование конечного автомата: синтаксический анализ
- •Синтаксический анализ файлов с разделяющими запятыми
- •Детерминированные и недетерминированные конечные автоматы
- •Регулярные выражения
- •Использование регулярных выражений
- •Синтаксический анализ регулярных выражений
- •Компиляция регулярных выражений
- •Сопоставление строк с регулярными выражениями
- •Глава 11. Сжатие данных.
- •Представление данных
- •Сжатие данных
- •Типы сжатия
- •Потоки битов
- •Сжатие с минимальной избыточностью
- •Кодирование Шеннона‑Фано
- •Кодирование Хаффмана
- •Кодирование с использованием скошенного дерева
- •Сжатие с использованием словаря
- •Описание сжатия lz77
- •Особенности кодирования литеральных символов и пар расстояние/длина
- •Восстановление с применением алгоритма lz77
- •Сжатие lz77
- •Глава 12. Дополнительные темы.
- •Алгоритм считывания‑записи
- •Алгоритм производителей‑потребителей
- •Модель с одним производителем и одним потребителем
- •Модель с одним производителем и несколькими потребителями
- •Поиск различий между двумя файлами
- •Вычисление lcs двух строк
- •Вычисление lcs двух файлов
- •Список литературы
Завершение пирамидальной сортировки
Итак, массив упорядочен в виде сортирующего дерева. Что дальше? Удаление элементов по одному по‑прежнему означает, что их нужно поместить куда‑либо в отсортированном порядке, предположительно, в какой‑нибудь вспомогательный массив. Так ли это? Немного подумаем. Если мы удаляем наибольший элемент, размер сортирующего дерева уменьшается на единицу, а в конце массива остается место для только что удаленного элемента. Фактически, алгоритм удаления элемента из сортирующего дерева требует, чтобы самый нижний, крайний справа узел копировался в позицию корневого узла, прежде чем к нему будет применена операция просачивания. Поэтому нужно всего лишь поменять местами корневой узел и самый нижний крайний справа узел, уменьшить значение счетчика количества элементов сортирующего дерева, а затем применить алгоритм просачивания.
Этот процесс нужно продолжать до тех пор, пока элементы в сортирующем дереве не иссякнут. В результате мы получаем элементы исходного массива, но теперь они оказываются отсортированными.
Полный код подпрограммы пирамидальной сортировки, реализованной так же, как были реализованы все процедуры сортировки в главе 5, приведен листинге 9.8.
Листинг 9.8. Алгоритм пирамидальной сортировки
procedure HSTrickleDown( aList : PPointerList; aFromInx : integer;
aCount : integer; aCompare : TtdCompareFunc );
var
Item : pointer;
ChildInx : integer;
ParentInx: integer;
begin
{вначале необходимо выполнить простую операцию просачивания, постоянно заменяя родительский узел его большим дочерним элементом, пока не будет достигнут нижний уровень сортирующего дерева}
Item := aList^[aFromInx];
ChildInx := (aFromInx * 2) + 1;
while (ChildInx < aCount) do
begin
if (suce(ChildInx) < aCount) and
(aCompare(aList^[ChildInx], aList^[suce(ChildInx)]) < 0) then
inc(ChildInx);
aList^[aFromInx] := aList^[ChildInx];
aFromInx := ChildInx;
ChildInx := (aFromInx * 2) + 1;
end;
{теперь из позиции, в которой был прекращен предыдущий процесс, необходимо выполнить операцию пузырькового подъема}
ParentInx := (aFromInx ‑ 1) div 2;
while (aFromInx > 0) and (aCompare (Item, aList^[ParentInx] ) > 0) do
begin
aList^[aFromInx] := aList^[ParentInx];
aFromInx := ParentInx;
ParentInx := (aFromInx ‑ 1) div 2;
end;
{сохранить элемент в той позиции, где был прекращен процесс пузырькового подъема}
aList^[aFromInx] := Item;
end;
procedure HSTrickleDownStd( aList : PPointerList;
aFromInx : integer;
aCount : integer;
aCompare : TtdCompareFunc );
var
Item : pointer;
ChildInx : integer;
begin
Item := aList^[aFromInx];
ChildInx := (aFromInx * 2) + 1;
while (ChildInx < aCount) do
begin
if (succ(ChildInx) < aCount) and
(aCompare(aList^[ChildInx], aList^[succ(ChildInx)]) < 0) then
inc(ChildInx);
if aCompare(Item, aList^[ChildInx]) >= 0 then
Break;
aList^[aFromInx] := aList^[ChildInx];
aFromInx := ChildInx;
ChildInx := (aFromInx * 2) + 1;
end;
aList^[aFromInx] := Item;
end;
procedure TDHeapSort( aList : TList; aFirst : integer;
aLast : integer; aCompare : TtdCompareFunc );
var
ItemCount : integer;
Inx : integer;
Temp : pointer;
begin
TDValidateListRange(aList, aFirst, aLast, 'TDHeapSort');
{преобразовать список за счет применения алгоритма пирамидальной сортировки Флойда}
ItemCount := aLast ‑ aFirst + 1;
for Inx := pred( ItemCount div 2) downto 0 do
HSTrickleDownStd(@aList.List^[aFirst], Inx, ItemCount, aCompare);
{удаление элементов из сортирующего дерева по одному, с помещением их в конец массива}
for Inx := pred( ItemCount) downto 0 do
begin
Temp := aList.List^[aFirst];
aList.List^[aFirst] := aList.List^[aFirst+Inx];
aList.List^ [aFirst+Inx] :=Temp;
HSTrickleDown(@aList.List^[aFirst], 0, Inx, aCompare);
end;
end;
Обратите внимание, что на первом этапе, при создании сортирующего дерева из массива, мы использовали стандартный алгоритм просачивания (алгоритм Флойда), но на втором этапе (при удалении наибольшего элемента из постоянно уменьшающегося сортирующего дерева) был применен оптимизированный алгоритм просачивания Флойда. На первом этапе мы ничего не знали о распределении элементов в массиве, поэтому имело смысл просто применить стандартный алгоритм просачивания ‑ в конце концов, в целом алгоритм Флойда является операцией типа O(n). Однако на втором этапе мы знаем, что меняем местами наибольший элемент и один из наименьших элементов. Поэтому в целесообразно осуществить оптимизацию.
До сих пор не был пояснен один момент. Если в качестве очереди по приоритету используется сортирующее дерево, отсортированное выбором максимального элемента, извлечение элементов будет выполняться в обратном порядке ‑ начиная с наибольшего и заканчивая наименьшим. Однако если сортирующее дерево, отсортированная выбором максимального элемента, используется для пирамидальной сортировки, элементы будут отсортированы в порядке возрастания, а не в обратном порядке. При использовании кучи, отсортированной выбором минимального элемента, элементы будут удаляться в порядке возрастания, но пирамидальная сортировка будет выполняться в порядке убывания.
Важность алгоритма пирамидальной сортировки обусловлена целым рядом причин. Во‑первых, время его выполнения определяется отношением O(n log(n)), следовательно, он работает достаточно быстро. Во‑вторых, пирамидальная сортировка не имеет худшего случая. Сравним ее с быстрой сортировкой. В общем случае, как правило, быстрая сортировка выполняется быстрее пирамидальной (для выполнения пирамидальной сортировки потребуется выполнение большего количества операций сравнения, чем для быстрой сортировки, а внутренний цикл пирамидальной сортировки длится дольше, чем цикл быстрой сортировки). Но при выполнении быстрой сортировки возможны случаи, когда все ее преимущества сводятся буквально на нет, делая ее чрезвычайно медленной. (В худшем случае время выполнения этого алгоритма может определяться отношением O(n(^2^)), если только не будут приняты определенные меры по оптимизации алгоритма.) Если же сравнить пирамидальную сортировку с сортировкой слиянием, то мы видим, что эта сортировка выполняется на месте и не требует большого дополнительного объема памяти, как имеет место при выполнении сортировки слиянием. В заключение приходится признать, что алгоритм пирамидальной сортировки не очень устойчив.
Исходный код процедуры TDHeapSort и вспомогательных процедур можно найти на web‑сайте издательства, в разделе материалов. После выгрузки материалов отыщите среди них файл TDSorts.pas.
