
- •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Процедуры поиска на бинарных деревьях
- •Рекомендованная литература
Var I, j: integer;
SS: TArr; {Содержит попарно различные значения слагаемых разбиения}
RR: TArr; {Содержит количество слагаемых разбиения с данным значением}
procedure Output1 (P: TArr);
Var j: integer;
begin
Write (' ');
for J := 1 to N do
if P[J] <>0 then Write (' ', P[J]);
Writeln;
end;
procedure Output2 (S, R: TArr);
{Формирует и выводит массив, который содержит все слагаемые разбиения}
Var I, j, k: integer;
P: TArr; {Содержит все слагаемые разбиения}
begin
I := 1;
for J := 1 to N do
if S[J] <> 0 then
for K := 1 to R[J] do
begin;
P[I] := S[J];
I := I + 1;
end;
Write (' ');
for K := I to N do
P[K] := 0;
for J := 1 to N do
if P[J] <> 0 then Write (' ', P[J]);
Writeln;
end;
procedure Divide_N (S, R: TArr);
{ S - Содержит попарно различные значения слагаемых разбиения}
{ R - {Содержит количество слагаемых разбиения с данным значением}
Var d, l, Sum: integer;
begin
{Первое разбиение}
S[1] := N; R[1] := 1; d := 1;
{Вывод первого разбиения}
{ Output1 (S); Output1 (R); }
Output2 (S, R);
while S[1] > 1 do {Последнему разбиению соответствует S[1] = 1}
begin {Нахождение следующего разбиения}
Sum := 0; {Сумма устраненных слагаемых}
if S[d] = 1 then {Удаление слагаемых, равных 1, и изъятие S[d] из рассмотрения}
begin
{S[d] является последним элементом разбиения и S[d] = 1. Начинается формирование нового разбиения}
Sum := Sum + R[d];
R[d] := 0;
S[d] := 0;
d := d - 1;
end;
{S[d] является последним элементом разбиения и S[d] > 1. Начинается формирование нового разбиения}
Sum := Sum + S[d];
R[d] := R[d] - 1;
L := S[d] - 1;
{Проверяется количество вхождений элемента S[d] в разбиение R[d].
Если R[d] > 1 формируется комбинация S[d+1] и R[d+1] и, возможно, комбинация S[d+2] и R[d+2].
Если R[d] = 0, то изменяется комбинация S[d] и R[d] и, возможно, формируется комбинация S[d+1] и R[d+1], а возможно и комбинация S[d+2] и R[d+2]}
if R[d] > 0 then D := D + 1; {Формируется содержимое S[d+1] и R[d+1]}
S[d] := L; {Новое значение S[d]}
R[d] := Sum div L; {Количество вхождений S[d] в новое разбиение}
L := Sum mod L; {Нераспределенное последнее слагаемое}
if L <> 0 then {Последнее слагаемое равно L. Формируется содержимое SS[d+2] и RR[d+2]}
begin
d := d + 1;
S[d] := L;
R[d] := 1;
end;
{Output1 (S); Output1 (R);}
Output2 (S, R);
end;
end; {DivideN}
begin
Clrscr;
{Подготовка массивов SS и RR}
for I := 1 to N do
begin
SS[I] := 0;
RR[I] := 0;
end;
Divide_N (SS, RR);
end.
6.2Разбиение множеств
Разбиение множества заданных элементов сводится к получению всех возможных сочетаний подмножеств из этих элементов при условии, что объединение всех подмножеств каждого из сочетаний образует заданное множество. Например, пусть задано множество из трех элементов {1, 2, 3}. Тогда все возможные разбиения этого множества имеют вид:
{1, 2, 3}
{1, 2} {3}
{1} {2, 3}
{1} {2} {3}
Простая идея получения всех разбиений заданного множества может быть представлена следующим образом.
Пусть необходимо получить все разбиения множества
{1, 2, 3, 4}
Пусть уже имеется множество разбиений множества
{1, 2, 3}
Каждое разбиение состоит из одного или нескольких блоков. Например, разбиение
{1, 2, 3}
содержит один блок, а разбиение
{1, 2} и {3}
состоит из двух блоков. Тогда необходимые разбиения множества
{1, 2, 4, 3}
образуются путем дописывания элемента 4 в одно из подмножеств каждого разбиения множества
{1, 2, 3}
При этом удобно предположить, что в каждом разбиении в качестве последнего множества неявно присутствует пустое множество. Например, разбиение
{1, 2, 3, 4}
следует рассматривать как разбиение
{1, 2, 3, 4} и {(}
Тогда получим следующие разбиения:
Таблица 5.1
Исходное разбиение множества {1 2 3} |
Результирующе разбиение множества {1 2 3 4} |
Движение активного элемента |
{1 2 3} |
{1 2 3 4} |
вправо |
{1 2 3} {4} |
||
{1 2} {3} |
{1 2} {3} {4} |
влево |
{1 2} {3 4} |
||
{1 2 4} {3} |
||
{1} {2} {3} |
{1 4} {2} {3} |
вправо |
{1} {2 4} {3} |
||
{1} {2} {3 4} |
||
{1} {2} {3} {4} |
||
{1} {2 3} |
{1} {2 3} {4} |
влево |
{1} {2 3 4} |
||
{1 4} {2 3} |
||
{1 3} {2} |
{1 3 4} {2} |
вправо |
{1 3} {2 4} |
||
{1 3} {2} {4} |
Проанализируем приведенный пример. Каждое следующее разбиение образуется из предыдущего разбиения путем перенесения единственного элемента в другой блок. Этим элементом является один из элементов исходного множества. Он называется "активным". Следует обратить внимание на то, что активный элемент при переходе от одного исходного разбиения к другому меняет направление своего движения: вперед (слева направо) и назад (слева направо.)
Рассмотренный пример содержит все возможные разбиения. Их получение носит рекуррентный характер – сначала для множества из одного элемента, затем из двух элементов и т. д., что неудобно для реализации. Ниже приводится описание алгоритма, реализующего не рекуррентный способ получение всех возможных разбиений. В этом алгоритме, реализуемом непосредственно на всем исходном множестве, каждое очередное разбиение формируется на основе предыдущего разбиения с помощью выбора движения очередного активного элемента поочередно то вправо, то влево.
Для управления процессом движения активного элемента с каждым элементом множества связывается признак – "движение вправо" и "движение влево" Первоначально для всех элементов задается режим "движения вправо". Значение этого признака меняется для активного элемента на "движение влево", когда он достигает самого правого из возможных подмножеств и наоборот, оно меняется на "движение вправо", когда активный элемент достигает самого левого из возможных подмножеств.
В приводимой ниже программе для представления подмножеств некоторого разбиения используется введенное выше понятие блок. Внутри блока элементы упорядочены в порядке их возрастания. Блоки имеют номера, которые совпадают со значением первого элемента блока. Блоки упорядочены в порядке возрастания первых элементов каждого блока, например,
{1, 2}, {3} и {4}
Следует подчеркнуть, что номера блоков не всегда образуют сплошную последовательность. В приведенном примере это 1, 3 и 4.
Для отнесения конкретного элемента множества к конкретному блоку используется одномерный массив Block[1..N]. Элемент массива Block[I] содержит номер блока для элемента I множества. Например, для разбиения {1, 2}, {3} и {4} массив Block имеет вид:
Block |
1 |
1 |
3 |
4 |
Для управления движением некоторого элемента множества между блоками (подмножествами) предыдущего разбиения используются три массива Next[1..N], Prev[1..N], Forvard[1..N].
Элемент массива Next[I] содержит номер следующего блока для блока с номером I. Для приведенного разбиения массив Next имеет вид:
Next |
3 |
0 |
4 |
0 |
Элемент массива Prev[I] содержит номер предыдущего блока для блока с номером I. Для приведенного разбиения массив Prev имеет вид:
Prev |
0 |
1 |
1 |
3 |
Элемент массива Forvd[I] задает направление, в котором "движется" активный элемент I. Для приведенного разбиения массив Forvd имеет вид:
Forvd |
True |
true |
true |
false |
Рассмотрим работу алгоритма генерации всех разбиений множества на примере множества {1, 2, 3, 4}. На каждом шаге будем рассматривать содержимое всех массивов и соответствующее разбиение.
На шаге инициализации алгоритма получим:
Block |
1 |
1 |
1 |
1 |
Next |
0 |
0 |
0 |
0 |
Prev |
0 |
0 |
0 |
0 |
Forvd |
true |
true |
true |
true |
что соответствует разбиению - {1, 2, 3, 4}
Задача алгоритма состоит в выборе на каждом шаге активного элемента J и его перемещении (вправо/влево) в соседний – последующий или предыдущий блок. Перемещение активного элемента в последующий блок может потребовать создание нового блока. После чего вновь выполняется выбор и перемещение активного элемент в соседний блок.
Пока остается открытым вопрос о выборе активного элемента. Из рассмотренного выше рекуррентного построения следует, что конкретный элемент, например 2, получает право перемещаться только тогда, когда все элементы, больше чем он (2 и 3), достигают своего крайне правого или левого положения. Более строго можно сказать, что активный элемент J*, является таким наименьшим элементом, что для каждого J>J* выполняется одно из двух условий:
Forvd[J] and (Block[J] = J), т. е. элемент движется вперед и достигает своего крайнего правого положения. Из этого следует, в частности, что элемент J при своем движении вперед не может переместиться в блок, номер которого больше, чем J. В частности, элемент J = 1 никогда не может быть активным элементом.
not Forvd[J] and (Block[J] = 1) т. е. элемент движется вперед и достигает своего крайнего левого положения, а именно, блока с номером 1.
Когда элемент J* достигает своего крайнего положения, все элементы J > J* меняют направление своего движения на противоположное.
Рассмотрим более подробно процесс движения активного элемента J вправо и влево.
При движении активного элемента J вправо возможны три случая:
Есть следующий блок, и он доступен для активного элемента (см. ограничение условия 1);
Есть следующий блок, и он недоступен для активного элемента (см. ограничение условия 1);
Отсутствует следующий блок. Его необходимо создать. Для пояснения выше предполагалось, что такой блок неявно существует и содержит пустое множество.
При переносе активного элемента J вправо, сначала отыскивается номер блока, содержащего активный элемент. Обозначим его K. Затем:
В случае а элемент движется вперед и его следует перенести в блок с номером Next[K].
В случае b элемент должен двигаться вперед, но следующий блок ему недоступен (J < Next[K]). Тогда создается одноэлементный блок, в который заносится J, и модифицируется Next[K] для корректной ссылки на этот блок (Next[K]=J).
В случае c элемент должен двигаться вперед, но следующего блока нет (Next[K]=0). Тогда создается одноэлементный блок, в который заносится J, и модифицируется Next[K] для корректной ссылки на этот блок (Next[K]=J).
При движении активного элемента J влево он помещается в предыдущий блок. Здесь возможны два случая:
В исходном блоке кроме активного элемента J были и другие элементы, т.е. Block[J]<>J.
В исходном блоке кроме активного элемента J не было других элементов, т.е. элемент J создавал одноэлементный блок (Block[J]=K=J). В этом случае необходимо выполнить корректировку Next и Prev.
Условием окончания работы алгоритма является факт того, что для каждого J выполняется условие 1 или 2.
Вернемся к нашему примеру. Для иллюстрации алгоритма использованы результаты пошагового выполнения программы генерации всех разбиений множества, реализующей данный алгоритм.
На шаге инициализации алгоритма получим:
Block |
1 |
1 |
1 |
1 |
Next |
0 |
0 |
0 |
0 |
Prev |
0 |
0 |
0 |
0 |
Forvd |
true |
true |
true |
true |
что соответствует разбиению - {1, 2, 3, 4}.
На первом шаге алгоритма активным элементом является элемент 4. Этот элемент начинает двигаться вправо,
Block |
1 |
1 |
1 |
4 |
Next |
4 |
0 |
0 |
0 |
Prev |
0 |
0 |
0 |
1 |
Forvd |
true |
true |
true |
true |
что соответствует разбиению - {1, 2, 3} {4}.
Дальнейшее движение вправо активного элемента 4 невозможно и тогда активным становится самый большой элемент предшествующего блока - элемент 3, а элемент 4 меняет направление своего движения на противоположное. Фактически образуется очередное разбиение предшествующего подмножества {1, 2, 3}. Новый активный элемент 3 начинает двигаться вправо. Войти в блок 4 он не может, т.к. 3 < 4 и образуется самостоятельный блок 3,
Block |
1 |
1 |
3 |
4 |
Next |
3 |
0 |
4 |
0 |
Prev |
0 |
0 |
1 |
3 |
Forvd |
true |
true |
true |
false |
что соответствует разбиению - {1, 2} {3} {4}.
Дальнейшее движение вправо активного элемента 3 невозможно т.к. 3 < 4. Активным становится самый последний элемент самого правого подмножества – элемент 4 и он начинает двигаться влево,
Block |
1 |
1 |
3 |
3 |
Next |
3 |
0 |
0 |
0 |
Prev |
0 |
0 |
1 |
3* |
Forvd |
true |
true |
true |
false |
что соответствует разбиению - {1, 2} {3, 4}.
Примечание. Prev[4]=3*. Верхний индекс * означает, что соответствующее значение унаследовано от предыдущего шага и несущественно для последующих действий.
Активный элемент 4 продолжает двигаться влево и для элемента 3 меняет направление его движения на противоположное,
Block |
1 |
1 |
3 |
1 |
Next |
3 |
0 |
0 |
0 |
Prev |
0 |
0 |
1 |
3* |
Forvd |
true |
true |
false |
true |
что соответствует разбиению - {1, 2, 4} {3}.
Далее влево активный элемент 4 двигаться не может. Активным становится предшествующий ему элемент самого левого подмножества – элемент 2 и он начинает двигаться вправо. Войти в блок 3 он не может, т.к. 2 < 3 и образуется самостоятельный блок 2. кроме того, у элементов 3 и 4 направление движения меняется на противоположное,
Block |
1 |
2 |
3 |
1 |
Next |
2 |
3 |
0 |
0 |
Prev |
0 |
1 |
2 |
3* |
Forvd |
true |
true |
false |
true |
что соответствует разбиению - {1, 4} {2} {3}.
Далее вправо активный элемент 2 двигаться не может. Активным становится самый последний элемент самого левого подмножества – элемент 4 и он начинает двигаться вправо,
Block |
1 |
2 |
3 |
2 |
Next |
2 |
3 |
0 |
0 |
Prev |
0 |
1 |
2 |
3* |
Forvd |
true |
true |
false |
true |
что соответствует разбиению - {1} {2, 4} {3}.
Активный элемент 4 продолжает двигаться вправо,
Block |
1 |
2 |
3 |
3 |
Next |
2 |
3 |
0 |
0 |
Prev |
0 |
1 |
2 |
3* |
Forvd |
true |
true |
False |
true |
что соответствует разбиению - {1} {2} {3, 4}.
Активный элемент 4 продолжает двигаться вправо,
Block |
1 |
2 |
3 |
4 |
Next |
2 |
3 |
4 |
0 |
Prev |
0 |
1 |
2 |
3 |
Forvd |
true |
true |
false |
false |
что соответствует разбиению - {1} {2} {3} {4}.
Активным становится элемент 2 и он начинает двигаться вправо,
Block |
1 |
2 |
2 |
4 |
Next |
2 |
4 |
4 |
0 |
Prev |
0 |
1 |
2 |
2 |
Forvd |
true |
true |
false |
false |
что соответствует разбиению - {1} {2, 3} {4}.
Активным становится элемент 4 и он начинает двигаться влево,
Block |
1 |
2 |
2 |
2 |
Next |
2 |
0 |
4 |
0 |
Prev |
0 |
1 |
2 |
2 |
Forvd |
true |
true |
false |
false |
что соответствует разбиению - {1} {2, 3, 4}.
Активный элемент 4 продолжает двигаться влево, достигает крайнего положения и меняет направление своего движения на противоположное
Block |
1 |
2 |
2 |
1 |
Next |
2 |
0 |
4 |
0 |
Prev |
0 |
1 |
2 |
2 |
Forvd |
true |
true |
false |
true |
что соответствует разбиению - {1, 4} {2, 3}
Активный становится элемент 3 и он начинает двигаться влево,
Block |
1 |
2 |
1 |
1 |
Next |
2 |
0 |
4 |
0 |
Prev |
0 |
1 |
2 |
2 |
Forvd |
true |
true |
false |
true |
что соответствует разбиению - {1, 3, 4} {2}.
Активный становится элемент 4 и он начинает двигаться вправо,
Block |
1 |
2 |
1 |
2 |
Next |
2 |
0 |
4 |
0 |
Prev |
0 |
1 |
2 |
2 |
Forvd |
true |
true |
false |
true |
что соответствует разбиению - {1, 3} {2, 4}.
Активный элемент 4 продолжает двигаться вправо,
Block |
1 |
2 |
1 |
4 |
Next |
2 |
4 |
4 |
0 |
Prev |
0 |
1 |
2 |
2 |
Forvd |
true |
false |
true |
false |
что соответствует разбиению - {1, 3} {2} {4}.
Алгоритм завершает свою работу, т.к. для каждого J=2(4 выполняется условие 1 или 2.
Ниже приводится текст программы, реализующий приведенный алгоритм получения всех разбиений заданного множества.
program DivideSet;
{Генерирует все разбиения множества /1, 2, ..., N/. Каждое разбиение образуется из предыдущего путем перенесения единственного элемента в другой блок. Если задано множество 1, 2, 3, 4 то получим