- •Глава 5
- •1. Понятие множества. Представление множества для вычислений
- •2. Набор операций для множеств
- •3. Алгоритмы выполнения основных операций над множествами и их эффективность
- •4. Получение полных наборов комбинаторных объектов. Перестановки
- •Void main()
- •Int next_permut(int* p, int n)
- •Void main()
- •5. Подмножества
- •Void main()
- •Int next_subset(int n, int r, int*& b)
- •Void main()
- •6. Разбиения
- •7. Поиск наибольшей монотонно возрастающей подпоследовательности
- •Int subsetnext(int n, int r, int* q)
- •Int subs2(int n, int* X)
- •Int subs3(int n, int* b)
4. Получение полных наборов комбинаторных объектов. Перестановки
Получение полных наборов комбинаторных объектов (КО) важно для решения многих задач дискретного анализа. Примером могут служить задачи оптимизации на дискретных множествах, при решении которых используются полные наборы КО.
При использовании полных наборов КО используются два вида процедур:
- процедуры, которые генерируют все КО, составляющие полный набор;
- процедуры, которые при каждом вызове выдают очередной КО.
Число перестановок n элементов равно:
Nprm(n) = n! = 123 ... n .
Перестановки
Рекурсивная процедура permuts генерирует и выводит на экран все перестановки элементов массива A.
int P; // номер очередной перастановки
void permuts(int* A, int n, int k) // k – номер рекурсивного вызова
{ int i,j;
for (i = k; i < n; i++)
{ swp(A[k], A[i]);
if (i !=k || k == 0) { P++; printf("%2d ",P);
for (j = 0; j < n; j++) printf("%2d ",A[j]); puts("");
}
if (k < n-2) permuts(A, n, k+1);
swp(A[k], A[i]);
}
}
Пример использования процедуры permuts:
Void main()
{ int x[5];
for (int i = 0; i < 5; i++) x[i] = i+1;
permuts(x, 5, 0);
}
Функция next_permut при каждом вызове формирует очередную перестановку элементов массива p и возвращает ее номер. При исчерпании всех подстановок функция возвращает 0.
Алгоритм получения следующей перестановки поясним на примере. Пусть имеем перестановку
p = {4,2,3,5,6,1}.
Просматриваем элементы последовательности справа налево, начиная с предпоследнего. Находим первый из них, который меньше последующего. В нашем примере это элемент 3. Среди элементов, которые расположены справа от него находим наименьший элемент, который, в то же время, больше элемента 3. Таким явялется элемент 5. Меняем местами элементы 3 и 5. Получаем:
p = {4,2,5,3,6,1}.
Теперь все элементы, которые находятся правее, чем 5, упорядочиваем по возрастанию. Получаем окончательно:
p = {4,2,5,1,3,6}.
Следующая перестановка получена.
Int next_permut(int* p, int n)
{ static int M=0;
M++; if (M == 1) return M;
int i, im, j, m;
for (i = n-2; i >= 0; i--)
{ if (p[i] < p[i+1]) { m = p[i+1]; im = i+1;
for (j = i+1; j < n; j++)
if (p[j] > p[i] && p[j] < m) { m = p[j]; im = j; }
break;
}
}
if (i < 0) return 0;
swp(p[i], p[im]);
sortup(i+1, n-1, p);
return M;
}
Пример использования функции next_permut:
Void main()
{ int i, N, A[5];
for (i = 0; i < 5; i++) A[i] = i+1;
while (N = next_permut(A, 5)) { printf("%3d ",N);
for (i = 0; i < 5; i++) printf("%2d ", A[i]);
puts("");
}
pause;
}
5. Подмножества
Получение полного набора некоторых комбинаторных объектов является важной задачей дискретных вычислений. Например, решение многих задач дискретной оптимизации почти всегда связано с перебором объектов некоторого полного набора. Число r-элементных подмножеств из n-элементного множества равно числу сочетаний:
.
Количество всех подмножеств n-элементного множества равно:
.
Алгоритм получения полного набора подмножеств представим в виде программы на языке С++.
Используется алгоритм перебора бинарных кодов, представляющих искомые подмножества. Бинарный код подмножества представлен массивом типа unsigned char, при этом в 1 байт – элемент массива записывается 1 бит бинарного кода. Однако эту же идею можно реализовать и для носителя подмножества типа битового вектора.
Множество представлено битовым вектором d: