g30zAZLYUG
.pdfif ((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