- •Оглавление
- •Введение
- •Раздел 1. Основные структуры данных
- •Тема 1. Введение в структуры данных. Динамическое распределение памяти
- •Классификация структур данных
- •1.2. Переменные-указатели и динамическое распределение памяти.
- •Например, указатели на простейшие базовые типы вводятся следующим образом:
- •Var pStr1,pStr2 :TpString; {переменные-указатели на строки}
- •1.3. Дополнительные вопросы использования переменных-указателей
- •1.4. Контрольные вопросы по теме
- •Тема 2. Структуры данных “стек” и “очередь”
- •2.1. Что такое стек и очередь?
- •2.2. Статическая реализация стека
- •2.3. Динамическая реализация стека
- •{Ссылочный тип для адресации элементов стека}
- •2.4. Статическая реализация очереди
- •2.5. Динамическая реализация очереди
- •{Ссылочный тип для адресации элементов очереди}
- •2.6. Практические задания
- •2.7. Контрольные вопросы по теме
- •Тема 3. Основы реализации списковых структур
- •3.1. Структуры данных типа “линейный список”
- •3.2. Первый способ статической реализации списка.
- •3.3. Второй способ статической реализации списка.
- •3.4. Управление памятью при статической реализации списков
- •3.5. Динамическая реализация линейных списков
- •3.6. Практические задания
- •3.7. Контрольные вопросы по теме
- •Тема 4. Усложненные списковые структуры
- •4.1. Двунаправленные линейные списки
- •4.2. Комбинированные структуры данных: массивы и списки указателей
- •4.3. Комбинированные структуры данных: массивы и списки списков
- •4.4. Практические задания.
- •4.5. Контрольные вопросы по теме
- •Тема 5. Основные понятия о древовидных структурах
- •5.1. Основные определения
- •5.2. Двоичные деревья
- •5.3. Идеально сбалансированные деревья
- •5.4. Практические здания
- •5.5. Контрольные вопросы по теме
- •Тема 6. Реализация поисковых деревьев
- •Двоичные деревья поиска.
- •6.2. Добавление вершины в дерево поиска
- •6.3. Удаление вершины из дерева поиска
- •6.4. Практические задания
- •Контрольные вопросы по теме
- •Тема 7. Дополнительные вопросы обработки деревьев. Графы.
- •Проблемы использования деревьев поиска
- •7.2. Двоичные деревья с дополнительными указателями
- •7.3. Деревья общего вида (не двоичные).
- •Представление графов
- •Практические задания
- •Контрольные вопросы по теме
- •Раздел 2. Алгоритмы сортировки и поиска
- •Тема 1. Классификация методов. Простейшие методы сортировки
- •1.1. Задача оценки и выбора алгоритмов
- •1.2. Классификация задач сортировки и поиска
- •1.3. Простейшие методы сортировки: метод обмена
- •1.4. Простейшие методы сортировки: метод вставок
- •1.5. Простейшие методы сортировки: метод выбора
- •1.6. Практическое задание
- •1.7. Контрольные вопросы по теме
- •Тема 2. Улучшенные методы сортировки массивов
- •2.1. Метод Шелла
- •2.2. Метод быстрой сортировки
- •2.3. Пирамидальная сортировка
- •2.4. Практическое задание
- •2.5. Контрольные вопросы по теме
- •Тема 3. Специальные методы сортировки
- •3.1. Простейшая карманная сортировка.
- •3.2. Карманная сортировка для случая повторяющихся ключей
- •3.3. Поразрядная сортировка
- •3.4. Практическое задание
- •3.5. Контрольные вопросы по теме
- •Тема 4. Поиск с использованием хеш-функций
- •4.1. Основные понятия
- •4.2. Разрешение конфликтов: открытое хеширование
- •4.3. Разрешение конфликтов: внутреннее хеширование
- •4.4. Практические задания
- •4.5. Контрольные вопросы по теме
- •Тема 5. Внешний поиск и внешняя сортировка
- •5.1. Особенности обработки больших наборов данных
- •5.2. Организация внешнего поиска с помощью б-деревьев.
- •5.4. Поиск элемента в б-дереве.
- •5.5. Добавление вершины в б-дерево
- •5.6. Удаление вершины из б-дерева
- •5.7. Внешняя сортировка
- •5.8. Практические задания
- •5.9. Контрольные вопросы по теме
- •Основные термины и понятия
- •Литература
2.2. Метод быстрой сортировки
Данный метод в настоящее время считается наиболее быстрым универсальным методом сортировки. Как ни странно, он является обобщением самого плохого из простейших методов – обменного метода. Эффективность метода достигается тем, что перестановка применяется не для соседних элементов, а отстоящих друг от друга на приличном расстоянии.
Более конкретно, алгоритм быстрой сортировки заключается в следующем.
пусть каким-то образом в исходном наборе выделен некий элемент x, который принято называтьопорным. В простейшем случае в качестве опорного можно взятьсерединныйэлемент массива
просматривается часть массива, расположенная левееопорного элемента и находится первый по порядку элементai>x
после этого просматривается часть массива, расположенная правееопорного элемента, причем -в обратном порядке, и находится первый по порядку (с конца) элементaj<x
производится перестановкаэлементовaiиaj
после этого в левой части, начиная с aiотыскивается еще один элемент, большийx, а в правой части, начиная сajотыскивается элемент, меньшийх
эти два элемента меняются местами
эти действия (поиск слева и справа с последующим обменом) продолжаются до тех пор, пока не будет достигнут опорный элемент x
после этого слева от опорного элемента xбудут находиться элементы,меньшие опорного, а справа – элементы,большие опорного. При этом обе половины скорее всего не будут отсортированными
после этого массив разбивается на правуюилевуючасти и каждая часть обрабатываетсяотдельнопо той же самой схеме: определение опорного элемента, поиск слева и справа соответствующих элементов и их перестановка и т.д.
Пример. Пусть исходный набор включает 11 чисел: 13-42-28-17-09-25-47-31-39-15-20. Основные шаги сортировки:
Выбор серединного элемента 25 (индекс 6): 13 42 28 17 09 25 47 31 39 15 20
поиск слева первого элемента, большего 25: 42 (2 сравнения)
поиск справа от конца первого элемента, меньшего 25: 20 (1 сравнение)
перестановка элементов 42 и 20: 13 2028 17 0925 47 31 39 1542
поиск слева от 25 еще одного элемента, большего 25: 28 (1 сравнение)
поиск справа от 25 еще одного элемента, меньшего 25: 15 (1 сравнение)
перестановка элементов 28 и 15: 13 20 1517 0925 47 31 392842
поиск слева от 25 еще одного элемента, большего 25: нет (2 сравнения)
поиск справа от 25 еще одного элемента, меньшего 25: нет (3 сравнения)
теперь слева от 25 все элементы меньше 25, а справа – больше
выделяем отдельно левую часть: 13 20 15 17 09
выбираем серединный элемент 15 (индекс 3): 13 20 1517 09
поиск слева от 15 элемента, большего 15: 20 (2 сравнения)
поиск справа от 15 элемента, меньшего 15: 09 (1 сравнение)
перестановка 20 и 09: 13 09151720
поиск справа от 15 еще одного элемента, меньшего 15: нет (1 сравнение)
теперь слева от 15 все элементы меньше 15, а справа – больше
поскольку слева от 15 только 2 элемента, просто сравниваем их друг с другом и переставляем (09 и 13)
поскольку справа от 15 только 2 элемента, просто сравниваем их и не переставляем
получаем для левой части упорядоченный набор: 09 13 15 17 20
возвращаемся к правой части: 47 31 39 28 42
выделяем серединный элемент 39 (индекс в данном поднаборе – 3): 47 31 3928 42
поиск слева от 39 элемента, большего 39: 47 (1 сравнение)
поиск справа от 39 элемента, меньшего 39: 28 (2 сравнения)
переставляем 47 и 28: 2831394742
поиск слева от 39 еще одного элемента, большего 39: нет (1 сравнение)
теперь слева от 39 все элементы меньше 39, а справа – больше
поскольку слева от 39 только 2 элемента, просто сравниваем их и не переставляем
поскольку справа от 39 только 2 элемента, просто сравниваем их и переставляем (42 и 47)
получаем для правой части упорядоченный набор: 28 31 39 42 47
вместе с левой частью и серединным элементом 25 получаем окончательный результат
Итого для данного примера потребовалось 22 сравнения и 6 пересылок.
В целом, оценка трудоемкости метода быстрой сортировки является типичной для улучшенных методов и выражается соотношением (n*log 2 n)/6. Отсюда следует, что данный метод неэффективен при малыхn(десятки или сотни элементов), но с ростомnего эффективность резко растет, и при очень большихnметод даетнаилучшие показателисреди всех универсальных методов сортировки.
К сожалению, есть одна ситуация, когда быстрая сортировка теряет свою эффективностьи становится пропорциональнойn2, т.е. опускается до уровня простых методов. Эта ситуация связана справилом выбора опорного элемента. Эффективность метода сильно зависит от выбора опорного элемента, и использование простейшего способа выбора (серединный элемент массива) часто приводит к падению эффективности метода. Это связано с тем, что каждое разделение массива на две половины в идеале должно даватьпримерноравноечисло элементов слева и справа от опорного элемента (принцип дихотомии!). Если опорный элемент близок к минимальному или максимальному, после попарных перестановок будут полученысущественнонеравномерныенаборы. Если подобная ситуация возникает на каждом шаге работы алгоритма, общая эффективность резко падает. Для устранения этого недостатка надо уметьправильно выбирать опорный элемент.
Наилучшее правило выбора опорного элемента – это так называемая медиана. Медиана – это средний элемент массиване по расположению, а позначению. В приведенном выше примере медианой является число 25, которое также было и серединным элементом (честно говоря, пример был подобран специально). К сожалению, поиск медианы в массиве является задачей,сопоставимой по трудоемкостис самой сортировкой, поэтому были предложены другие, более простые правила выбора опорного элемента.
На практике хорошо показал себя следующий способ: выбрать случайно в массиве три элементаи взять в качестве опорногосредний из них. Этот способ очень прост в реализации, т.к. требует только двух сравнений, но, конечно, он может обеспечивать хорошие показатели только в среднем, и не гарантирует идеальное поведение алгоритма абсолютно для ЛЮБЫХ входных данных.
Есть и другие усовершенствования базового алгоритма быстрой сортировки. Среди них:
Проверка каждого подмассива на возможную упорядоченность перед самой сортировкой
Для малых подмассивов (n< 10) разумно проводить сортировку с помощью более простых методов, например – Шелла
Комбинация всех этих методов известна как метод Синглтона [7].
Что касается программной реализации базового алгоритма, то можно заметить его принципиальную особенность: разделение массива на 2 половины, разделение каждой половины на свои половины и т.д. При каждом разделении приходится запоминатьправую половину (конечно, не сами элементы, а лишьиндексылевой и правой границы) ивозвращатьсяк ней после полной обработки левой половины. Все это как нельзя лучше соответствуетрекурсивномупринципу обработки, и поэтому быстрая сортировка проще всего реализуется рекурсивно. Конечно, при необходимости можно включить явное использование стека для запоминания левой и правой границы подмассива, аналогично нерекурсивной реализации процедур обхода дерева.
Рекурсивная реализация с серединным опорным элементом схематично выглядит следующим образом:
ProcedureQuickSort(left,right:integer);
{формальные параметры для запоминания границ}
var i, j : integer;
sred,temp: <описание элемента исходного массива>;
begin
i:=left;j:=right; {установка начальных значений границ подмассива}
sred:=mas[(left+right)div2]; {определение серединного элемента}
repeat
while (mas [i] < sred) do i := i + 1;
{поиск слева элемента, большего опорного}
while(mas[j] >sred)doj:=j– 1;
{поиск справа элемента, меньшего опорного}
if i <= j then
begin {обмениваем элементы и изменяем индексы}
temp := mas [i]; mas [i] := mas [j]; mas [j] := temp;
i := i + 1; j := j –1;
end;
untili > j;
if left < j then QuickSort (left , j); {обработка левой половины}
if i < right then QuickSort (i , right); {обработка правой половины}
end;
Первоначальный вызов этой процедуры производится в главной программе в виде QuickSort(1,n); здесьn– число элементов в массиве.