- •Глава 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)
2. Набор операций для множеств
Набор операций называют функционально полным, если он позволяет реализовать любой алгоритм вычислений. Выбор функционально полного набора операций не является однозначным. Следующий вариант такого выбора хотя и является избыточным, но удобным для вычислений:
- операция проверки принадлежности объекта данному множеству;
- операция включения нового элемента множества;
- операция исключения элемента из множества;
- операция объединения;
- операция пересечения;
- операция дополнения.
Проверка принадлежности
Результатом выполнения операции xA является логическое значение "Истина", если x принадлежит множеству A и значение "Ложь", если нет.
Включение
Операция обозначается как A+x или Ax , результатом является множество, в которое добавлен элемент x.
Исключение
Операция обозначается как A - x или A \ x . Результатом формируется путем исключения x из множества A. Если xA, результатом является множество A.
Объединение
Операция обозначается как AB. Результатом является множество, в которое включены все элементы из A и из B: AB = {x | xA или xB}.
Пересечение
Операция обозначается как AB. Результатом является множество, в которое включены только те элементы, которые принадлежат множеству A и, в то же время, множеству B:
AB = {x | xA и xB}.
Дополнение
Операция обозначается как A. Результатом является множество, в которое включены те элементы универсума, которые не принадлежат A: A = {x | xU и xA }. Эта операция определена только лишь в том случае, когда задан универсум.
3. Алгоритмы выполнения основных операций над множествами и их эффективность
Проверка принадлежности
Множество представлено битовым вектором
Проверка отношения xA . Под А будем понимать заданное множество и, в то же время, бинарный код этого множества. Эта операция сводится к считыванию из битового вектора значения бита номер x. Обозначим его через qx.
Пусть M = |U| . В качестве массива-носителя B бит. вектора выберем массив с элементами типа unsigned short (16 бит). Размер n массива B будет равным:
m = M/16+1 . (1)
Определим константу bit:
const unsigned short bit = 0x8000;
Она содержит единственный единичный бит в первой слева позиции 16-битового значения. Вычисление qx сводится к выполнению след. операций:
j=i/16; k=i%16;
qx = B[j] & (bit>>k);
Отсюда время выполнения этой операции равно: t = Const .
Это время не зависит ни от размера множества А, ни от размера универсума U.
Множество представлено одномерным массивом
Проверка отношения xA. Значение x поочередно сравнивается с элементами мн-ва A. Отсюда:
t = Const ∙n .
где n = |A| .
Множество представлено упорядоченным массивом
Использование алгоритма бинарного поиска дает:
t = Const ∙log(n) .
Включение
Множество представлено битовым вектором
Рассмотрим алгоритм включения элемента x в множество A, т.е. алгоритм выполнения операции A+x , где А – множество, x – добавляемый элемент. Операция сводится к установке бита с номером, равным x, в 1. Вычисления сводятся к выполнению след. операций:
j=i/16; k=i%16;
qx = B[j] || (bit>>k);
Отсюда время выполнения этой операции также равно: t = Const .
Множество представлено одномерным массивом
Операция A+x . Включаемый элемент x вносится в мн-во A в том случае, если он там не содержится. Отсюда имеем:
t = Const ∙n .
Множество представлено упорядоченным массивом
Эта операция содержит два шага:
- проверить, содержится ли заданное x в мн-ве А,
- если содержится, то вставить его в А, не нарушая порядка.
Определяющим (более медленным) является второй шаг. Для времени имеем:
t = Const ∙n .
Исключение
Множество представлено битовым вектором
Исключение элемента x из множества A сводится к установке бита с номером , в 0. Вычисления сводятся к выполнению след. операций:
bit = ~ (bit>>k);
j = i/16; k = i % 16;
qx = B[j] & bit;
Отсюда время выполнения этой операции также равно: t = Const .
Множество представлено одномерным массивом
Операция A-x может быть реализована такими операторами:
for (i = 0; i < n; i++) if (x == A[i]) break;
for (k = i; k < n-1; k++) A[i] = A[i+1];
Время выполнения обоих циклов будет равно:
t = c1n1+c2n2 ,
где c1 - время выполнения операции сравнения, c2 - время выполнения операции присваивания (пересылки), n1 – к-во итераций первого цикла, n2 – к-во итераций второго цикла. Пусть c = max(c1,c2), тогда, учитывая, что n = n1+n2, получаем:
t ≤ Constn .
Множество представлено упорядоченным массивом
Алгоритм такой же, как и для представления мн-ва неупорядоченным массивом. Для снижения времени можно использовать алгоритм бинарного поиска, однако порядок алгоритма остается таким же:
t ≤ Constn .
Объединение
Множество представлено битовым вектором
Определение операции объединения: AB = { x | xA или xB } .
Операция сводится к поэлементному выполнению операции дизъюнкции над парами соответствующих битов. Реализовать это можно следующим образом:
for (i = 0; i < m ;i++) C[i] = A[i] || B[i];
Здесь символы A,B,C используются как имена множеств и, в то же время, как имена соответствующих массивов-носителей.
Т.к. m и N связаны линейным соотношением (1), то при достаточно больших N будем иметь:
t = Const ∙N .
Множество представлено одномерным массивом
Операция AB. Пусть nA = |A| , nB = |B|. Данную операцию можно рассматривать как последовательность операций включения: для каждого элемента мн-ва В необходимо выполнить операцию вкючения его в м-во А. Т.к. операция включения требует линейного времени, имеем:
t ≤ ConstnAnB .
Множество представлено упорядоченным массивом
Можно использовать идею алгоритма слияния, которая используется для сортировки. Тогда упорядоченность массивов, представляющих множества, дает алгоритм с временем:
t = Const (nA+nB) ,
т.е. имеем линейный по времени алгоритм.
Пересечение
Множество представлено битовым вектором
AB = { x | xA и xB } .
Реализация этой операции выглядит так:
for (i = 0; i < m; i++) C[i] = A[i] & B[i];
и время выполнения этой операции такое же:
t = Const ∙N .
Множество представлено одномерным массивом
Операция AB. Здесь ситуация похожая: для каждого элемента мн-ва А необходимо проверить принадлежность его к мн-ву В. Отсюда:
t ≤ ConstnAnB .
Множество представлено упорядоченным массивом
Для пересечения легко строится алгоритм со временем:
t = Const (nA+nB) ,
Дополнение
Множество представлено битовым вектором
I ─ A
Алгоритм выполнения этой операции:
for (i = 0; i < m; i++) B[i] = ~ A[i];
И время равно
t = Const ∙N .
Множество представлено одномерным массивом
Для получения результата необходимо из универсума поочередно извлечь все элементы мн-ва А. Отсюда получаем: t = ConstnN .
Множество представлено упорядоченным массивом
Упорядоченность массивов-носителей не улучшает время выполнения этой операции:
t = Const ∙n∙N .
Эффективность выполнения операций над множествами для различных реализаций
Указаны оценки времени выполнения алгоритма в среднем
-
Битовый вектор
Не упорядоч. мн-во
Упорядоч. мн-во
xA ?
(1)
(nA)
(log(nA))
A + x
(1)
(nA)
(nA)
A – x
(1)
(nA)
(nA)
A B
(N)
(nA∙nB)
(nA+nB)
А В
(N)
(nA∙nB)
(nA+nB)
A
(N)
(nA∙N)
(N)
где n - число элементов множества X.