Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Алгоритмы и структуры данных.doc
Скачиваний:
0
Добавлен:
01.03.2025
Размер:
1.14 Mб
Скачать

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* выполняется одно из двух условий:

  1. Forvd[J] and (Block[J] = J), т. е. элемент движется вперед и достигает своего крайнего правого положения. Из этого следует, в частности, что элемент J при своем движении вперед не может переместиться в блок, номер которого больше, чем J. В частности, элемент J = 1 никогда не может быть активным элементом.

  2. not Forvd[J] and (Block[J] = 1) т. е. элемент движется вперед и достигает своего крайнего левого положения, а именно, блока с номером 1.

Когда элемент J* достигает своего крайнего положения, все элементы J > J* меняют направление своего движения на противоположное.

Рассмотрим более подробно процесс движения активного элемента J вправо и влево.

При движении активного элемента J вправо возможны три случая:

  1. Есть следующий блок, и он доступен для активного элемента (см. ограничение условия 1);

  2. Есть следующий блок, и он недоступен для активного элемента (см. ограничение условия 1);

  3. Отсутствует следующий блок. Его необходимо создать. Для пояснения выше предполагалось, что такой блок неявно существует и содержит пустое множество.

При переносе активного элемента 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 влево он помещается в предыдущий блок. Здесь возможны два случая:

  1. В исходном блоке кроме активного элемента J были и другие элементы, т.е. Block[J]<>J.

  2. В исходном блоке кроме активного элемента 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 то получим