- •Что я должен предварительно знать?
- •Какая версия 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 двух файлов
- •Список литературы
Сортировка методом вставок
И последний алгоритм из первого рассматриваемого нами набора ‑ сортировка методом вставок, или сортировка простыми вставками (Insertion sort). Этот алгоритм покажется знакомым всем, кто играет в такие карточные игры, как вист или бридж, поскольку большинство игроков сортирует свои карты именно так.
Рисунок 5.4. Стандартная сортировка методом вставок
Начинаем с левого края колоды. Сравниваем две первые карты и располагаем их в правильном порядке. Смотрим на третью карту. Вставляем ее в требуемое место по отношению к первым двум картам. Смотрим на четвертую карту и вставляем ее в требуемое место по отношению к первым трем картам. Те же операции выполняем с пятой, шестой, седьмой и всеми последующими картами. При перемещении слева направо левая часть колоды будет отсортированной.
Листинг 5.7. Стандартная сортировка методом вставок
procedure TDInsertionSortStd(aList : TList;
aFirst : integer;
aLast : integer;
aCompare : TtdCompareFunc);
var
i, j : integer;
Temp : pointer;
begin
TDValidateListRange(aList, aFirst, aLast, 'TDInsertionSortStd');
for i := succ(aFirst) to aLast do
begin
Temp := aList.List^[i];
j :=i;
while (j > aFirst) and (aCompare(Temp, aList.List^[j‑1]) < 0) do
begin
aList.List^[j] := aList.List^[j‑1];
dec(j);
end;
aList.List^[j] := Temp;
end;
end;
В приведенной реализации сортировки методом вставок имеется одна очень интересная особенность: значение текущего элемента сохраняется в локальной переменной, а затем при поиске нужного места его вставки (внутренний цикл) мы перемещаем каждый элемент, значение которого больше текущего, на одну позицию вправо, тем самым, перемещая "дыру" в списке влево. В конце концов, мы находим нужное место и помещаем сохраненное значение в освободившееся место.
Давайте посмотрим на внутренний цикл. Его выполнение завершается при соблюдении одного из двух условий: достигнуто начало списка, т.е. текущее значение меньше значений всех уже отсортированных элементов, или обнаружено значение, меньшее текущего. Тем не менее, обратите внимание, что первое условие проверяется при каждом выполнении внутреннего цикла, несмотря на то что оно соблюдается достаточно редко, когда текущее значение меньше, чем значения всех уже отсортированных элементов, однако оно предотвращает выход за пределы списка. Традиционным методом исключения этой дополнительной проверки является введение в начало списка сигнального элемента, который меньше любого другого элемента в списке. К сожалению, в общем случае минимальный элемент в списке заранее неизвестен и, кроме того, в списке нет места для вставки дополнительного элемента. (Теоретически потребуется скопировать весь список в другой, размер которого больше на один элемент, установить значение первого элемента в этом новом списке равным минимальному значению из сортируемого списка, а затем после сортировки скопировать элементы в исходный список. И все это ради того, чтобы исключить одну проверку. Нет уж, спасибо.)
Рисунок 5.5. Сортировка методом вставок
Существует более эффективный метод оптимизации: просмотреть весь список, найти элемент с наименьшим значением и переставить его на первое место (фактически это выполнение первого цикла Сортировки методом выбора). Теперь, когда первый элемент находится в требуемой позиции, можно выполнять стандартную процедуру метода вставок и игнорировать возможность выхода за начало списка.
Листинг 5.8. Оптимизированная сортировка методом вставок
procedure TDInsertionSort(aList : TList;
aFirst : integer;
aLast : integer;
aCompare : TtdCompareFunc);
var
i, j : integer;
IndexOfMin : integer;
Temp : pointer;
begin
TDValidateListRange(aList, aFirst, aLast, 'TDInsertionSort');
{найти наименьший элемент и поместить его в первую позицию}
IndexOfMin := aFirst;
for i := succ(aFirst) to aLast do
if (aCompare(aList.List^[i], aList.List^[IndexOfMin]) < 0) then
IndexOfMin := i;
if (aFirst <> indexOfMin) then begin
Temp := aList.List^[aFirst];
aList.List^[aFirst] := aList.List^[IndexOfMin];
aList.List^ [IndexOfMin] := Teufend;
{отсортировать с использованием метода простых вставок}
for i := aFirst+2 to aLast do
begin
Temp := aList.List^[i];
j := i;
while (aCompare(Temp, aList.List^[j‑1]) < 0) do
begin
aList.List^[j] := aList.List^[j‑1];
dec(j);
end;
aList.List^[j] := Temp;
end;
end;
Хотите верьте, хотите нет, но предварительная установка наименьшего значения в первую позицию и исключение дополнительной проверки выхода за границы списка при тестировании дала увеличение быстродействия примерно на 7%.
Как и три предыдущие рассмотренные нами алгоритма, сортировка методом вставок принадлежит к классу алгоритмов O(n(^2^)). Как и в случае с пузырьковой, сортировкой, если исходный список уже отсортирован, сортировка методом вставок практически не выполняет никаких действий помимо сравнения пар двух соседних элементов. Худшим случаем для сортировки методом вставок является ситуация, когда исходный список отсортирован в обратном порядке (как и для пузырьковой сортировки) ‑ для попадания в требуемое место каждому элементу нужно пройти максимальное расстояние.
Тем не менее, если список частично отсортирован, и каждый элемент находится недалеко от требуемого места, сортировка методом вставок будет выполняться очень быстро. Фактически она превращается в алгоритм класса O(n). (Другими словами, внешний цикл выполняется n ‑ 1 раз, а внутренний ‑ всего несколько раз, что соответствует небольшому расстоянию элементов от их позиции в отсортированном списке.) Таким образом, во внутреннем цикле выполняется некоторое постоянное количество проходов (т.е. сравнений и перемещений), скажем, d. Для внешнего цикла, как мы уже говорили, количество проходов равно n ‑ 1. Следовательно, общее количество сравнений и перемещений будет выражаться значением d(n‑ 1) (алгоритм класса O(n)). Несмотря на то что на практике частично отсортированные списки встречаются достаточно редко, тем не менее, возможна ситуация, когда с частично отсортированными списками можно сталкиваться гораздо чаще. Мы рассмотрим эту ситуацию немного ниже.
Сортировка методом вставок (любая ее вариация) принадлежит к группе устойчивых алгоритмов. Она сохраняет относительное положение элементов с равными значениями, поскольку поиск требуемой позиции для элемента завершается, когда найден элемент, значение которого меньше или равно значению текущего элемента. Следовательно, относительное положение элементов с равными значениями сохраняется.
Как и при пузырьковой сортировке, при сортировке методом вставок элементы попадают в нужные позиции только за счет смены позиций с соседними элементами. Если элемент находится далеко от требуемой позиции, его перемещение занимает много времени. Если бы только мы могли перемещать элементы не через соседние элементы, а сразу в некоторый диапазон, где текущий элемент должен находиться! Давайте познакомимся со вторым набором алгоритмов.
