Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

g30zAZLYUG

.pdf
Скачиваний:
3
Добавлен:
15.04.2023
Размер:
1.89 Mб
Скачать

if ((n>0) && (m>0))

{

RP(1);

}

Запустите программу на выполнение. При n = 2, m = 3 у Вас должен получиться следующий результат:

Протестируйте программу с другими значениями n и m.

1.6.Сохраните cpp-файл под другим именем (например, lab_06_RP) – он пригодится немного позже, закройте его и снова откройте файл lab_06.

1.7.Приступим к генерации размещений (тех, которые без повторений). Так как элементы в комбинации не должны повторяться, надо какимто образом запретить выбор i-го элемента множества A в качестве k-й компоненты формируемой комбинации, если этот элемент уже присутствует в комбинации.

Сделаем это с помощью глобального логического массива i_free, в котором столько же элементов, сколько их во множестве A. Определим его следующим образом:

(i_free[j] = 1) (элемент j множества A в комбинации не присутствует)

Сначала положим:

 

i_free[j] = 1,

j = 1, 2, …, n.

Затем, как только некоторое число i выбрано в качестве элемента массива myArray, переопределяем:

i_free[i] = 0,

тем самым запрещая его последующий выбор.

Под строкой объявления массива myArray добавьте объявление логического массива i_free:

bool i_free[MAX_N];

1.8. В процедуре main после ввода переменной n вставьте цикл инициализации массива i_free:

91

int i;

for (i=1; i<=n; i++)

{

i_free[i]=1;

};

1.9. Замените название процедуры RP на R (в трёх местах: заголовок процедуры, рекурсивный вызов, первый вызов в процедуре main).

1.10. Займёмся модификацией процедуры R. Теперь, прежде чем выбрать элемент i множества A в качестве k-й компоненты формируемой комбинации, проверяем, свободен ли этот элемент. Если свободен, то выполняем следующие действия:

определяем k-ю компоненту формируемой комбинации;

запрещаем последующий выбор элемента i;

проверяем, вся ли комбинация сформирована? (Если нет, то осуществляем рекурсивный вызов для поиска следующей компоненты. Если да, то печатаем массив.);

с целью обхода всего дерева решений освобождаем элемент i множества A, т.е. выполняем отход назад.

Замените тело цикла for следующим фрагментом:

if (i_free[i])

{

myArray[k]=i;

// Выбираем i в качестве k-й компоненты

i_free[i]=0;

 

if (k < m) // Если комбинация сформирована не полностью,

{

R(k+1); // то ищем k+1-ю компоненту

}

else // Если комбинация полностью сформирована,

{

count++;

cout << "Комбинация № " << count << "\t"; for (j=1; j<=m; j++)

{

cout << myArray[j] << "\t";

// то печатаем её

};

 

cout << "\n";

 

}

i_free[i]=1; // Отход назад - с целью обойти всё дерево решений

}

Обращаю Ваше внимание на предпоследнюю строку представленного кода: именно здесь осуществляется отход назад с целью обойти всё дерево решений.

92

Если эта строка отсутствует, то Ваша процедура пройдёт только по одной ветке дерева решений и на этом закончит свою работу. Можете в порядке эксперимента закомментировать эту строку и посмотреть результат.

1.11. Если процедуру генерации размещений с повторениями мы вызывали, убедившись, что (n>0) && (m>0), то теперь необходимо проверить ещё одно условие, а именно: m не должно превышать n.

Замените в процедуре main условие, при котором вызывается процедура R, следующим:

(n>0) && (m>0) && (m<=n)

Запустите программу на выполнение. При n = 4, m = 2 у Вас должен получиться следующий результат:

Протестируйте программу с другими значениями n и m.

1.12. Сохраните cpp-файл под другим именем (например, lab_06_R), закройте его и снова откройте файл lab_06.

1.13. Программу, которая генерирует все перестановки из n элемен-

тов, создайте самостоятельно.

Указания.

1.Ещё раз внимательно прочитайте определение перестановок.

2.В только что созданной программе уберите ввод переменной m и замените везде m на n.

Результат (работающую версию программы, а также программный код) предъявите преподавателю.

1.14. Сохраните cpp-файл с программой, генерирующей все перестановки, под другим именем (например, lab_06_P) и закройте его.

93

1.15. Настала очередь перестановок с повторениями. За основу возьмём процедуру генерации размещений с повторениями.

Закройте текущий cpp-файл с именем lab_06 (если он был открыт). Откройте cpp-файл с именем lab_06_RP и сохраните его под именем lab_06.

При генерации перестановок с повторениями важно помнить, что в отличие от размещений с повторениями, комбинация должна содержать все n объектов, некоторые из которых могут повторяться.

Рассмотрим пример 1. Пусть n = 4, m = 5, т.е. из множества A = {1, 2, 3, 4} мы набрали некоторые 5 элементов, например,

(2, 2, 1, 4, 4).

Очевидно, эта комбинация является размещением с повторением, но не является перестановкой с повторением, т.к. в ней отсутствует элемент 3.

Сгенерировать перестановки с повторениями можно следующим образом: сформировав очередное размещение с повторениями, проверить, все ли n объектов в нём содержатся? Если да, то это перестановка с повторениями, и её следует распечатать. Если нет, то такую комбинацию надо пропустить.

Итак, наша программа должна проверить, все ли n элементов содержатся в комбинации.

Пример того, как не надо решать эту задачу:

первый раз просматриваем комбинацию в поисках 1,

если 1 нашли, то второй раз просматриваем комбинацию в поисках 2,

если 2 нашли, то третий раз просматриваем комбинацию в поисках 3

ит.д.

При таком неэффективном подходе придётся n раз просмотреть m- элементный массив.

Мы поступим по-другому. Введем в рассмотрение вспомогательный локальный логический n-элементный массив b, контролирующий, какие из n объектов задействованы в текущем решении. Определим его так:

(b[j] = 1) (элемент j множества A в комбинации не присутствует)

Сначала положим: b[j] = 1, j = 1, 2, …, n.

Вернёмся к примеру 1. Комбинации (2,2,1,4,4) соответствуют следующие значения массива myArray:

myArray[1] = 2 myArray[2] = 2 myArray[3] = 1 myArray[4] = 4 myArray[5] = 4

94

Просматриваем этот массив слева направо, параллельно заполняя

массив b:

 

 

 

 

myArray[1] = 2

=>

2 присутствует

=>

b[2]=0;

myArray[2] = 2

=>

2 присутствует

=>

b[2]=0;

myArray[3] = 1

=>

1 присутствует

=>

b[1]=0;

myArray[4] = 4

=>

4 присутствует

=>

b[4]=0;

myArray[5] = 4

=>

4 присутствует

=>

b[4]=0.

За один просмотр массива myArray мы переопределили массив b. Осталось один раз просмотреть массив b.

Если в массиве b обнулились все элементы, то это означает, что все элементы множества A присутствуют в рассматриваемой комбинации, следовательно, она является перестановкой с повторениями. Печатаем её.

Если в массиве b осталась хотя бы одна единица, то просматривать его до конца не имеет смысла, т.к. уже ясно, что не все элементы множества A присутствуют в рассматриваемой комбинации. Она не является перестановкой с повторениями, и печатать её не надо.

1.16. Переименуйте процедуру RP в PP (в трёх местах: объявление процедуры, рекурсивный вызов, первоначальный вызов в процедуре main).

1.17. В начале процедуры PP объявите локальную логическую переменную flag и локальный логический массив b:

bool flag;

bool b[MAX_N];

1.18. На данный момент в блоке else процедуры PP производится лишь печать сформированной комбинации. Но мы должны сначала проверить комбинацию на присутствие всех элементов множества A, а уж затем, если все элементы присутствуют, распечатать эту комбинацию. В процедуре PP блок else замените следующим фрагментом:

else // Если комбинация полностью сформирована,

{

for (j=1; j<=n; j++) // Инициализация n-элементного массива b

{

b[j]=1;

};

// Просмотр m-элементного массива myArray и заполнение массива b for (j=1; j<=m; j++)

{

b[myArray [j]]=0;

};

flag = 1; // Поставили логический «флажок» for (j=1; j<=n; j++) // Просмотр n-элементного массива b

95

{

if(b[j]) // Если встретилась хотя бы одна единица

{

flag = 0;

// Сняли логический «флажок»

break;

// и закончили просмотр

}

}

// Печатаем комбинацию, если в массиве b нет ни одной единицы if (flag)

{

count++;

cout << "Комбинация № " << count << "\t"; for (j=1; j<=m; j++)

{

cout << myArray[j] << "\t";

// то печатаем её

};

 

cout << "\n";

 

}

}

Логическая переменная flag принимает значение 0, если хотя бы один из n объектов в размещении не использовался. Печать решения осуществляется лишь в том случае, когда flag = 1.

Запустите программу на выполнение.

При n = 2, m = 3 у Вас должен получиться следующий результат:

Протестируйте программу с другими значениями n и m.

1.19. Сохраните cpp-файл с программой, генерирующей все перестановки с повторениями, под другим именем (например, lab_06_PP) и закройте его.

1.20. Приступим к генерации сочетаний. За основу возьмём всё ту же процедуру генерации размещений с повторениями.

Закройте текущий cpp-файл с именем lab_06 (если он был открыт). Откройте cpp-файл с именем lab_06_RP и сохраните его под именем lab_06.

96

Напомню, что сочетания не различаются порядком элементов. Например, комбинации

(1, 3, 5), (1, 5, 3), (3, 1, 5), (3, 5, 1), (5, 1, 3), (5, 3, 1)

представляют собой одно и то же сочетание. Естественно, на печать надо вывести лишь одну из этих комбинаций – любую. Так вот. Печатать бу-

дем ту комбинацию, у которой элементы расположены в порядке возрас-

тания.

В нашем примере это комбинация (1, 3, 5).

Итак, будем формировать очередную комбинацию в порядке возрастания её элементов. Это означает, что k-я компонента обязательно должна быть больше, чем k 1-я. В рекурсивную процедуру формирования сочета-

ний будем передавать ещё один целочисленный параметр: previous зна-

чение k 1-й компоненты генерируемого сочетания. Тогда k-я компонента myArray[k] массива myArray выбирается из множества

{ previous +1, previous +2, ..., n},

после чего осуществляется вызов процедуры с параметрами (k+1, myArray[k]).

1.21. Переименуйте процедуру RP в S и добавьте в описание процедуры ещё один параметр

в объявлении процедуры:

void S(int k, int previous)

в рекурсивном вызове:

S(k+1, myArray[k]);

// то ищем k+1-ю компоненту

в первоначальном вызове:

S(1,0);

1.22. В процедуре S поменяйте условие цикла for :

for (i=previous+1; i<=n; i++)

1.23. В условие первоначального вызова процедуры S добавьте такое же условие, как и у размещений, т.е. m n:

if ((n>0) && (m>0) && (m<=n))

Запустите программу на выполнение.

При n = 4, m = 2 у Вас должен получиться следующий результат:

97

Протестируйте программу с другими значениями n и m.

1.24. Сохраните cpp-файл с программой, генерирующей все сочетания, под другим именем (например, lab_06_S) и закройте его.

1.25. Займёмся сочетаниями с повторениями. Откройте файл lab_06.

Сейчас в нём содержится программа генерации обычных сочетаний (без повторений). Её-то и возьмём за основу.

Напомню, что сочетания с повторениями тоже не отличаются порядком элементов. Например,

(1, 1, 2), (1, 2, 1), (2, 1, 1) –

это одно и то же сочетание с повторениями. На печать надо вывести любую из этих комбинаций. Будем печатать ту, у которой элементы расположены в порядке неубывания.

В нашем примере это комбинация (1, 1, 2).

Генерация сочетаний с повторениями практически не отличается от формирования сочетаний без повторений. Но теперь k-я компонента должна быть не меньше, чем k 1-я, т.е. k-я компонента myArray[k] массива myArray выбирается из множества

{previous, previous +1, previous +2, ..., n}.

1.26. Переименуйте процедуру S в SP (в трёх местах: объявление процедуры, рекурсивный вызов, первоначальный вызов в процедуре main).

1.27. В процедуре SP поменяйте условие цикла for :

for (i=previous; i<=n; i++)

1.28. Из условия первоначального вызова процедуры S уберите условие m n, т.е. оставьте

if ((n>0) && (m>0))

1.29. Наконец, исправьте первоначальный вызов процедуры SP в процедуре main:

SP(1,1);

98

Запустите программу на выполнение.

При n = 2, m = 3 у Вас должен получиться следующий результат:

Протестируйте программу с другими значениями n и m.

1.30. Сохраните cpp-файл с программой, генерирующей все сочетания с повторениями, под другим именем (например, lab_06_SP) и закройте его.

1.31. Сгенерируем те композиции (z1, z2, …, zn) числа m, которые отвечают условию:

zi − число целое неотрицательное, т.е.

zi N {0}; i = 1, 2, …, n.

Известно, что существует взаимно однозначное соответствие между множеством решений в целых неотрицательных числах уравнения

z1 + z2 + …+ zn = m

и множеством сочетаний с повторениями из n по m по следующему правилу:

z1 в композиции – это количество 1 в сочетании с повторениями;

z2 в композиции – это количество 2 в сочетании с повторениями;

zn в композиции – это количество n в сочетании с повторениями.

Сочетание с по-

(1, 1,…,1,

2, 2, …,2,

…, n, n, …, n)

вторениями

z1 раз

z2 раз

zn раз

Пример 2. Пусть n = 3, m = 5, т.е. из множества A = {1, 2, 3} извлекаем пять элементов. В сочетании с повторениями

(1, 1, 1, 3, 3)

объект 1 присутствует 3 раза, объект 2 присутствует 0 раз, объект 3 присутствует 2 раза; поэтому этой комбинации будет соответствовать композиция (3, 0, 2) числа 5.

Наоборот, композиции (1, 3, 1) числа 5 соответствует сочетание с повторениями, у которого одна единица, три двойки и одна тройка, т.е.

(1, 2, 2, 2, 3).

Значит, вместо композиций достаточно сформировать все сочетания с повторениями из n по m, после чего в каждом таком сочетании подсчи-

99

тать z1 количество вхождений 1, z2 количество вхождений 2, ..., zn количество вхождений n.

Пример того, как не надо решать эту задачу:

первый раз просматриваем сочетание с повторениями и подсчитываем количество 1;

второй раз просматриваем сочетание с повторениями и подсчитываем количество 2;

третий раз просматриваем сочетание с повторениями и подсчитываем количество 3 и т.д.

При таком неэффективном подходе придётся n раз просмотреть m-

элементный массив.

Мы поступим по-другому. Введем в рассмотрение вспомогательный локальный целочисленный n-элементный массив z. Определим его так:

z[j] = количеству элементов j в сочетании с повторениями Сначала положим: z[j] = 0, j = 1, 2, …, n.

Вернёмся к примеру 2. Сочетанию с повторениями (1, 1, 1, 3, 3) соответствуют следующие значения массива myArray:

myArray[1] = 1 myArray[2] = 1 myArray[3] = 1 myArray[4] = 3 myArray[5] = 3

Просматриваем этот массив слева направо, параллельно заполняя

массив z:

 

 

myArray[1] = 1

=>

наращиваем счётчик z[1]: z[1] = 1;

myArray[2] = 1

=>

наращиваем счётчик z[1]: z[1] = 2;

myArray[3] = 1

=>

наращиваем счётчик z[1]: z[1] = 3;

myArray[4] = 3

=>

наращиваем счётчик z[3]: z[3] = 1;

myArray[5] = 3

=>

наращиваем счётчик z[3]: z[3] = 2.

За один просмотр массива myArray мы заполнили массив z. Осталось распечатать массив z (вместо массива myArray).

Откройте файл lab_06_SP. Сейчас в нём содержится программа генерации сочетаний с повторениями. Возьмём её за основу. Сохраните этот файл под именем lab_06.

1.32. Переименуйте процедуру SP в K (в трёх местах: объявление процедуры, рекурсивный вызов, первоначальный вызов в процедуре main).

1.33. В начале процедуры K объявите локальный целочисленный массив z:

int z[MAX_N];

100

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]