
- •1Введение
- •2Сортировки
- •2.1Сортировки массивов
- •2.2Сортировка простым включением
- •2.3Сортировка простым выбором
- •2.4Сортировка простым обменом
- •2.5Сравнение простых сортировок
- •2.6 Сортировка шелла
- •2.7Пирамидальная сортировка
- •2.8Быстрая сортировка
- •2.9Поиск медианы и квантилей
- •2.10Сравнение сортировок
- •3Поиск подстроки в строке
- •3.1Поиск в строке
- •3.2Простой поиск в строке
- •3.3Поиск в строке. Алгоритм боуера-мура
- •4Генерация перестановок
- •4.1Генерация перестановок последовательности натуральных чисел
- •4.2Генерация перестановок элементов в антилексикографическом порядке
- •4.3Генерация перестановок за одну транспозицию элементов
- •4.4Генерация перестановок за одну транспозицию соседних элементов
- •5Генерация подмножеств
- •5.1Генерация всех подмножеств множества
- •5.2Генерация m -элементных подмножеств множества натуральных чисел
- •Var m: integer; {Размер подмножества}
- •Var I, j, p: integer;
- •5.3Генрация k-компонентных выборок на множестве {0, 1}
- •Var k: integer; {Количество нулей в кортеже}
- •I: integer;
- •Var I, j, p: integer;
- •If Finish then Break {Exit};
- •6Генерация разбиений
- •6.1Разбиение целых чисел
- •Var I, j: integer;
- •Var j: integer;
- •Var I, j, k: integer;
- •Var d, l, Sum: integer;
- •6.2Разбиение множеств
- •/1, 2, 3/ И /4/ затем /1, 2/ и /3, 4/ и т.Д. }
- •I, j, k, r, s: integer;
- •If not Flag2 then
- •If Flag1 then
- •If Forvd[j] then { j движется вперед}
- •7Обходы бинарных деревьев
- •7.1Процедуры прохождения бинарных деревьев
- •8Поиск на бинарных деревьях
- •8.1Процедуры поиска на бинарных деревьях
- •Рекомендованная литература
2.7Пирамидальная сортировка
Пирамидальная сортировка или сортировка с помощью дерева являются развитием СПВыб, которая основана на повторном выборе наименьшего ключа сначала среди n элементов, затем среди n-1 элементов и т.д. Количество сравнений образуют убывающую арифметическую прогрессию, а их сумма равна
(n2 – n) / 2
Концепция сортировки с помощь дерева.
Идея улучшения алгоритма СПВыб состоит в том, чтобы после каждого прохода оставлять больше информации, чем простое указание на значение и координату наименьшего на данном проходе элемента.
Например, если всю последовательность элементов разбить на отдельные пары
<1, 2>, <3, 4>, <5, 6>...,
и выполнять сравнения в отдельных парах, то, сделав n/2 сравнений, можно определить в каждой паре наименьший элемент. Затем с помощью n/4 сравнений можно выбрать ключ, наименьший из каждой пары таких наименьших ключей и т.д. В результате при помощи всего n-1 сравнений можно построить дерево выбора.
Пример. Пусть задана входная последовательность чисел:
44, 55, 11, 33, 99, 22, 01, 77
Первый этап сортировки с помощь дерева.
На этом этапе выполняется построение дерева выбора.
В результате последовательного выбора наименьшего из двух ключей дерево выбора примет следующий вид:
-
0
1
11
01
44
11
22
01
44
55
11
33
99
22
01
77
Корень такого дерева будет содержать наименьший ключ.
Второй этап сортировки с помощь дерева.
На этом этапе наименьший элемент извлекается из дерева. Для этого выполняется:
спуск вдоль пути, отмеченного наименьшим элементом;
исключение наименьшего элемента из дальнейшего рассмотрения (образование “дыр”);
занесение наименьшего элемента в выходную последовательность.
В результате дерево примет следующий вид:
-
11
44
11
22
44
55
11
33
99
22
77
Образование “дыр”
а выходная последовательность будет иметь вид:
01
Третий этап сортировки с помощь дерева
На этом этапе выполняется замена "дыр", вызванных исключением отмеченного элемента:
в листе дерева он заменяется пустым элементом;
в промежуточных узлах дерева он заменяется элементом из соседней ветви дерева (братом).
Для нашего примера получим дерево вида:
-
1
1
11
22
44
11
22
77
44
55
11
33
99
22
77
Замена “дыр”
В результате в корень дерева вновь перемещается наименьший среди оставшихся элементов и его снова можно исключить. После его извлечения и занесения в выходную последовательность дерево примет вид:
-
2
2
33
22
44
33
22
77
44
55
33
99
22
77
а выходная последовательность примет вид:
01, 11
После n таких шагов дерево становится пустым, а выходная последовательность принимает вид:
01, 11, 22, 33, 44, 55, 77, 99
и процесс сортировки заканчивается.
Следует подчеркнуть, что:
на построение дерева требуется n сравнений;
на каждом из n шагов выбора наименьшего элемента из оставшейся последовательности требуется log2n сравнений.
Поэтому весь процесс сортировки требует
n + n * log2 n
элементарных операций.
Это существенное улучшение не только метода СПВыб, требующего n2 шагов, но и метода Шелла, требующего n1.2 шагов. И чем больше размер массива, тем лучше работает эта сортировка.
Таким образом, эффективность алгоритма привлекательна, но он основан на использовании дополнительной информации, которую нужно как-то хранить. Кроме того, желательно избавиться от “дыр”, которые, в конечном итоге, заполняют всё дерево.
На первый взгляд представляется, что для реализации сортировки требуется построить некоторую древовидную структуру, что требует дополнительно n-1 единиц памяти, тогда как основным требованием к сортировкам является их выполнение “на месте”, не привлекая дополнительную память.
Следовательно, нужно найти способ представления дерева с помощью n, а не 2n-1 единиц памяти. И здесь на помощь приходят механизмы, которые были рассмотрены при представлении почти полного бинарного дерева с помощью массива.
Представления дерева сортировки с помощью n элементов.
Реализация представленной задачи была предложена Дж. Уильямсом в 1964 г. Свой метод он назвал пирамидальной сортировкой или HeapSort.
На абстрактном уровне пирамида определяется как последовательность ключей вида:
hL, hL+1, .... , hR
такая, что
hj <= h2j, hj <= h2j+1, j = L, ..., R/2.
Представление пирамиды.
Если любое двоичное дерево рассматривать как массив по схеме, использовавшейся при построении почти полного бинарного дерева, когда индекс левого потомка равен удвоенному индексу родителя, а индекс правого потомка равен удвоенному индексу родителя плюс 1.
Например, при L = 1 и R = 15 получим дерево вида:
-
h
1
h2
h3
h4
h5
h6
h7
h8
h9
h10
h11
h12
h13
h14
h15
Однако для иллюстрации последующих рассуждений используем более простой массив из 7-ми элементов.
Ниже представлен массив из R = 7 элементов, являющийся пирамидой,
-
h
1
33
01
55
99
22
11
В этом массиве элемент h1 рассматривается как наименьший элемент пирамиды, т. е.
h1 = min(h1...... hR)
а для элементов hj при
j = 1 ... (R div 2)
имеет место условие
hj <= h2j, hj <= h2j+1
Пополнение пирамиды.
Пусть задана пирамида с элементами
hL+1, ..., hR
для некоторых значений L и R. К этой пирамиде необходимо добавить новый элемент x, для того чтобы сформировать расширенную пирамиду
hL, …, hR.
Возьмём, например, пирамиду h1, ..., h7 вида:
-
h
1
33
01
55
99
22
11
Извлечем из нее элемент h1 и добавим вместо него элемент x = 44.
Добавления нового элемента в пирамиду.
Процесс добавления состоит из двух последовательных этапов:
Размещение нового элемента x в вершине дерева, т. е. в h1.
Просеивание добавленного элемента x по дереву. Для этого элемент x постепенно опускается вниз по дереву каждый раз по направлению наименьшего из двух примыкающих к нему элементов, а наименьший из этих двух элементов передвигается вверх по дереву. Спуск выполняется до тех пор, пока элемент x не займет своего места в пирамиде.
В
нашем примере новый элемент x
= 44 размещается в вершине дерева.
Затем он меняется местами сначала с
элементом 01,
а затем с элементом 11.
В результате получаем пирамиду вида:
-
01
33
11
55
99
22
44
Если, в общем случае, через i и j обозначить пару индексов, соответствующих элементам hi <= hj, которые нужно менять местами на каждом шаге просеивания, то способ просеивания действительно обеспечивает сохранность условия, определяющего пирамиду, неизменным. То есть после просеивания по прежнему имеет место условие
hj <= h2j, hj <= h2j+1, при j = L, ..., R/2
Построения пирамиды на том же месте.
Одно из возможных решений предложено Р. Флойдом. В основе предложенного решения лежат два факта, один из которых вытекает из изученных ранее свойств деревьев, а другой можно легко установить, анализируя рассмотренные выше примеры пирамид.
Первый факт состоит в том, что, если на нижнем уровне полного бинарного дерева находится R элементов, то на всех его верхних уровнях находится R - 1 элемент.
Если теперь массив рассматривать как пирамиду, то правая половина массива из R/2 элементов заведомо образует нижний слой пирамиды, содержащий R/2 элементов.
Второй факт состоит в том, что на нижнем уровне пирамиды элементы никак не упорядочены, тогда, как на более верхних уровнях пирамиды, элементы связаны условием
hj <= h2j, hi <= h2j+1, j = L, ..., R/2
Теперь очевидна суть идеи Р. Флойда. Она состоит в выделении в исходном массиве двух групп элементов.
Одна (правая) группа элементов на первом этапе может рассматриваться как нижний уровень пирамиды;
Из другой (левой) группы элементов выполняется поочередное пополнение пирамиды с последующим её просеиванием.
В результате для каждого элемента массива в левой группе элементов выполняются требуемые условия, а на первом месте в массиве располагается самый малый элемент массива.
Решение этой задачи выполняется в два этапа.
Вначале вводится процедура ShiftMin, которая пополняет пирамиду одним элементом и просеивает этот элемент через пирамиду;
Затем создается обрамление процедуры ShiftMin, которое позволяет последовательно выбирать элементы из левой группы элементов массива и использовать их для пополнения пирамиды.
Результатом выполнения полученного кода будет массив, у которого минимальный элемент расположен на первом месте.
type TArr = array of Item ;
var A: TArr;
procedure ShiftMin (var A: TArr; Ls, Rs: integer);
var I, J: integer;
X: Item;
begin
I := Ls;
J := 2 * Ls;
X := A[Ls]; {новый элемент заносится в вершину дерева (пирамида расширяется влево)}
if (J < Rs) and (A[J+1].Key < A[J].Key) then J := J+1; {J задает позицию меньшего из двух элементов}
while (J <= Rs) and (A[J].Key < X.Key) do {сдвиги соответствующих элементов для освобождения места пришедшему элементу}
begin
A[I]:= A[J];
I:= J;
J:= 2 * J;
if (J < Rs) and (A[J+1].Key < A[J].Key) then J := J+1; {J задает позицию меньшего из двух элементов}
end;
A[I] := X; {пришедший элемент размещается на своем месте}
end; {ShiftMin}
Суть процедуры ShiftMin в следующем. Дан массив:
h1, .... , hR.
Ясно, что последовательность элементов:
hR/2+1, ..., hR
уже образует нижний слой пирамиды, где не требуется никакого упорядочивания, поскольку не существует двух индексов i и j таких, что:
i >= (R/2 + 1), j <= R и при этом
j = 2*i или j = 2*i +1
Процедура ShiftMin выполняет добавление к пирамиде нового элемента, стоящего на позиции Ls, а затем, путем сдвигов, устанавливает этот элемент в надлежащую позицию массива. В результате пирамида расширяется влево на один элемент.
В общем случае пирамида последовательно расширяется влево на R/2 первых элемента исходного массива. На каждом шаге добавляется новый элемент из левой половины массива и путём описанного выше механизма сдвигов он ставится на надлежащую позицию в массиве.
Рассмотрим процесс построения пирамиды для использованного выше примера. Количество элементов R в этом примере 8. Пирамида строится последовательным добавлением новых элементов, начиная с элемента hR/2 и заканчивая элементом h1.
44 55 11 33 | 99 22 01 77
Справа от проведенной границы, начиная с элемента 99, расположен нижний слой пирамиды, а слева – добавляемые элементы.
Теперь последовательно, справа налево, будут добавляться элементы:
44 55 11 33
Покажем это:
4
4
55 11 33 99 22 01 77
Добавим “33”.
Пока изменений нет
44 55 11 33 99 22 01 77
Добавим “11”. Элемент
заменяется на меньший
из “01” и “22”, т.е. на “01”
44 55 01 33 99 22 11 77
Добавим “55”. Элемент
заменяется на меньший
из “33” и “99”, т.е. на “33”
44 33 01 55 99 22 11 77
Добавим “44”. Элемент
заменяется на “01”,
а затем на “11”
01 33 11 55 99 22 44 77
Если теперь некоторому элементу с координатой i сопоставить элементы с координатами 2*i и 2*i+1, то получим графическое представление пирамиды
-
0
1
33
11
55
99
22
44
Следовательно, процесс построения пирамиды из N-элементов “на месте” можно описать следующим образом.
N := Length (A);
R := N-1;
L := ((N-1) div 2) + 1;
while L > 0 do
begin
L := L-1; {координата добавляемого элемента}
ShiftMin (L, R); {пополнение и просеивание пирамиды}
end;
В результате самый малый элемент массива помещается в вершину пирамиды.
Сортировка массива с помощью пирамиды.
Рассмотренный выше механизм позволяет переместить самый малый элемент массива в вершину пирамиды. Теперь, для того чтобы рассортировать все элементы массива, необходимо последовательно извлекать самый малый элемент из вершины пирамиды, при условии, что размер массива остаётся неизменным. Возникает вопрос, куда помещать извлекаемые элементы, и что делать с образующимися "дырами" и оставшимися элементами массива. Возможный ответ состоит в следующем.
На каждом шаге из пирамиды выбирается ее последняя (самая правая) компонента y. Верхний - самый малый элемент пирамиды, помещается на место компоненты y, а компонента y помещается на место верхнего элемента и затем просеивается на своё место в оставшейся пирамиде. В результате для сортировки массива из n элементов необходимо совершить n - 1 шагов. При таком подходе образуется массив, упорядоченный по убыванию.
Покажем это на рассматриваемом примере. Результатом предыдущих действий была пирамида, содержащая минимальный элемент в вершине пирамиды.
-
01
33
11
55
99
22
44
77
Выберем из пирамиды последний элемент “77” и поменяем его местами с первым (минимальным) элементом “01” – а затем просеем элемент “77” через пирамиду с помощью процедуры ShiftMin. Элемент “77” меняется местами вначале с элементом “11”, а затем с элементом “22”. Получим пирамиду.
-
0
1
33
11
55
99
22
44
77
11
33
22
55
99
77
44
01
просеивание
Проделаем эту процедуру N-2 раза с
оставшейся частью пирамиды. Получим
22
33
44
55
99
77
11
01
33
55
44
77
99
22
11
01
44
55
99
77
33
22
11
01
55
77
99
44
33
22
11
01
77
99
55
44
33
22
11
01
99
77
55
44
33
22
11
01
Описание этого процесса имеет вид:
N := Length (A);
R := N-1;
while R > 0 do
begin
X := A[0];
A[0] := A[R];
A[R] := X;
R := R - 1;
ShiftMin (0, R-1);
end;
Из примера видно, что в результате получается последовательность элементов, упорядоченных по убыванию. Это определяется отношениями порядка A[J].Key < X.Key и A[J+1].Key < A[J].Key, используемыми при сравнении элементов пирамиды в процедуре ShiftMin.
Если в процедуре сдвига заменить эти отношения на противоположные X.Key < A[J].Key и A[J].Key < A[J+1].Key, то пирамида будет строиться по принципу размещения в ее вершине самого большого элемента.
Назовем такую процедуру ShiftMax. Тогда в результате многократного применения процедуры ShiftMax по описанной выше схеме получим последовательность элементов, упорядоченную по возрастанию.
Окончательный вид процедуры HeapSort, обеспечивающей сортировку по возрастанию, имеет вид:
type TArr = array of Item ;
var I: integer;
N: integer; {количество элементов сортируемой последовательности}
A: TArr;
procedure HeapSort (var A: TArr);
var L, R: integer;
X: Item;
procedure ShiftMax (Ls, Rs: integer);
var I, J: integer;
X: Item;
begin
I := Ls;
J := 2 * Ls;
X := A[Ls]; {новый элемент заносится в вершину пирамиды (пирамида расширяется влево)}
if (J < Rs) and (A[J].Key < A[J+1].Key) then J := J + 1; {J задает позицию большего из двух элементов}
{с целью сортировки по возрастанию, по отношению к предыдущему варианту процедуры Shift, изменен знак сравнения в выражении A[J].Key < A[J+1].Key}
while (J <= Rs ) and (X.Key < A[J].Key) do {сдвиги соответствующих элементов для освобождения места пришедшему элементу}
begin
A[I] := A[J];
I := J;
J := 2 * J;
if (J < Rs) and (A[J].Key < A[J+1].Key) then J := J + 1; {J задает позицию большего из двух элементов}
end;
A[I] := X; {пришедший элемент размещается на своем месте}
end; {ShiftMax}
begin
N := Length (A);
L := ((N-1) div 2) + 1;
R := N-1;
{Построение пирамиды}
while L > 0 do {Самый большой элемент помещается в вершину пирамиды (в элемент A[0])}
begin
L := L - 1;
ShiftMax (L, R);
end;
while R > 0 do {перемещение очередного самого большого элемента из вершины пирамиды на его место в отсортированной последовательности }
begin
X := A[0];
A[0] := A[R];
A[R] := X;
R := R - 1;
ShiftMax (L, R);
end;
end; {HeapSort}
Следует обратить внимание на тот факт, что при такой реализации N‑1 ‑ый элемент, прежде чем попасть на свое место в правой части, сначала сдвигается влево, а уж затем в результате перемещений занимает свое место. Однако таких перемещений немного, причем они выполняются с большим шагом.
Даже в самом худшем случае процедура HeapSort требует n*log2(n) перемещений элементов, а в среднем она требует (n/2)*log2(n) перемещений элементов, причём отклонение от этого значения не велико.
Процедура HeapSort эффективна для больших массивов. Чем больше размер массива, тем лучше она работает. На больших массивах она превосходит сортировку Шелла.
Примечание. Механизмы пирамидальной сортировки имеют еще одно важное применение. Они могут использоваться для построения очередей из элементов, имеющих разный приоритет. Благодаря чему, элемент с наибольшим (наименьшим) значением приоритета всегда находится в верхушке пирамиды - начале последовательности элементов.
Действительно, поступление нового элемента с определенным приоритетом в очередь равносильно пополнению пирамиды, а выбор из пирамиды элемента с максимальным (минимальным) приоритетом равносилен выбору элемента из верхушки пирамиды.
Проблема снятия ограничений на размерность очереди решается использованием динамических массивов, дополнительное пространство в которые добавляется в начале массива, а не, как обычно, в конце массива.