Методы получения случайных комбинаторных объектов
Задача: |
Необходимо сгенерировать случайный комбинаторный объект размера | с равномерным распределением вероятности, если в наличии есть функция для генерации случайного числа в заданном интервале.
Описание алгоритма
Определение: |
Комбинаторные объекты (англ. combinatorial objects) — конечные множества, на элементы которых могут накладываться определённые ограничения, такие как: различимость или неразличимость элементов, возможность повторения одинаковых элементов и т. п. |
Пусть
— множество различных элементов, которые могут находиться в данном комбинаторном объекте.Будем получать элементы по порядку: сначала определим, какой элемент будет стоять на первом месте, потом на втором и так далее. Считаем, что мы построили префикс длинны
: . Будем выбирать элемент из множества всех возможных так, чтобы вероятность выбора элемнта , была пропорциональна числу комбинторных обьектов размера с префиксом . Для этого разобъем отрезок натуральных чисел . где — число различных комбинаторных объектов с текущим префиксом, на диапазонов так, чтобы размер диапазаона был равен числу объектов с префиксом . С помощью функции для генерации случайного числа получим число в интервале и добавим к префиксу элемент соответствующий диапазону отрезка в котором находится полученное число.object randomObject(n: int, k: int): //— размер комбинторного объекта, — число различных элемнтов. for i = 1 to n s = number(prefix) // число комбинаторных объектов с текущим префиксом. r = random(1, s) for j = 1 to k if number(prefix + B[j]) < r // — множество всех возможных элементов. r = r - number(prefix + B[j]) // если не попало в текщий диапазон — перейдем к следующему. else prefix[i] = b[j] break return prefix
Сложность алгоритма —
. Количества комбинаторных объектов с заданными префиксами считаются известными, и их подсчет в сложности не учитывается. Стоит отметить, что подсчет количества комбинаторных объектов с заданным префиксом зачастую является задачей с достаточно большой вычислительной сложностью.Доказательство корректности
Лемма: |
Вероятность получить в процессе работы алгоритма некоторый префикс равна , где — число различных комбинаторных объектов данного типа длины , а — число различных комбинаторных обьектов длины с таким префиксом. |
Доказательство: |
Докажем по индукции: База: Заметим, что любой комбинаторный объект имеет пустой префикс ( ), следовательно . Вероятность получить некоторый префикс длины равна , что равно .Переход: Пусть вероятность получить префикс Следовательно предположение индукции верно для всех возможных префиксов любой длины. длины равна . Вероятность получить из некоторый префикс длины равна , следовательно вероятность получить префикс равна что равно . |
Утверждение: |
Результатом работы данного алгоритма является случайный комбинаторный объект размера , причем вероятность получения одинакова для каждого результата. |
Заметим, что результатом работы данного алгоритма является любой из возможных префиксов размера | . Любой такой префикс является комбинаторным объектом размера и наоборот, следовательно число объектов с таким префиксом равно . Из чего по доказанной лемме следует, что вероятность получения одинакова для всех различных результатов и равна .
Примеры решения задач
Рассмотрим примеры использования данного алгоритма для генерации различных типов комбинаторных объектов.
Битовые вектора
Определение: |
Битовые вектора (англ. Bit vectors) — последовательность нулей и единиц заданной длины. |
Рассмотрим алгоритм получения случайного битового вектора. В битовом векторе может находиться только два типа элементов:
и , следовательно . Заметим что для любого префикса длины число возможных комбинаторных объектов длины одинаково и равно , следовательно на каждом шаге алгоритма небходмо выбирать с равной вероятностью илиvector<int> randomBitVector(n: int): //
— размер битового вектора.
for i = 1 to n
r = random(0, 1)
v[i] = r
return prefix
Сложность алгоритма —
, так как в случае двоичных векторов постоянно и равно .Правильные скобочные последовательности
Определение: |
Правильная скобочная последовательность (анлг. Correct Bracket Sequences) — частный случай скобочной последовательности, определяющийся следующими образами:
|
Рассмотрим алгоритм получения случайной правильной скобочной последовательности. Правильная скобочная последовательность состоит из двух типов элементов: открывающей и закрывающей скобок, следовательно
.Определение: |
Полуправильная скобочная последовательность (анлг. Semi-Correct Bracket Sequence) — скобочная последовательность такая, что всякой закрывающей скобке соответствует парная открывающая, но не все открытые скобки закрыты. |
Рассмотрим полуправильные скобочные последовательности. Такую последовтеьность можно охарактеризовать двумя числами:
— длина скобочной последовательности и — баланс (т.е. разность между количеством открывающих и закрывающих скобок). Заметим что любой префикс правильной скобочной последовательности является полуправильной скобочной последовательностью, и что для любого префикса длины число различных правильных скобочных последовательностей длины равно числу полуправильных скобочных последовательностей длины с таким же балансом как у .Научимся считать
— число последовательностей длины и баланса . Если , то ответ понятен сразу: , все остальные . Пусть теперь , тогда переберём, чему мог быть равен последний символ этой последовательности. Если он был равен , то до этого символа мы находились в состоянии . Если он был равен , то предыдущим было состояние . Таким образом, получаем формулу:
(считается, что все значения
при отрицательном равны нулю). Этот преподсчет можно выполнить за .Будем строить префикс следующим образом: на каждом шаге интервал случайных чисел
(где ) , будет разбиваться на два диапазона размерами и , и к префиксу будет прибавляться или если случайное число попало в первый или второй диапазон соответственно.string randomBracketSequence(n: int): //
— длина скобочной последовательности.
b = 0
l = 0
for i = 1 to n
s = D[n - l][b]
r = random(1, s)
if D[n - l - 1][b + 1] >= r
l = l + 1
b = b + 1
result = result + '('
else
l = l + 1
b = b - 1
result = result + ')'
return result
Сложность алгоритма —
при условии что известно для любых . Преподсчет можно выполнить динамически за .Разбиения на множества
Определение: |
Разбиение множества (англ. Partition of a set) — семейство непустых множеств на подмножества , где — некоторое множество индексов, если:
|
Разбиение на подмножеств
Рассмотрим множество первых
натуральных чисел: . Необходимо разбить его на непустых подмножеств с равным распределением вероятности.Будем строить разбиение таким образом, чтобы в результате подмножества
оказались отсортированы в лексикографическом порядке (т.е. чтобы для любых наименьший элемент был меньше наименьшего элемента ). Для этого будем по очереди добавлять каждое число от до в одно из подмножеств и для каждого из подмножеств начиная с и заканчивая будем выбирать какой элемент будет добавлен в него последним(т.е. будет минимальным).На каждом шаге префиксом считаем текущее разбиение. Оно характеризуется двумя значениями: числу Стирлинга второго рода). Таким образом из префикса мы можем получить следующий префикс двумя способами:
— число добавленных элементов и — число подмножеств для которых определен последний элемент. Заметим, что количество разбиений на подмножества с заданным префиксом равно числу способов разбить еще не добавленные элементы на еще не законченные подмножества так, чтобы они оказались лексикографически упорядочены, то есть равно числу разбиений элементов на непустых подмножеств, что равно (- Добавить текущий элемент ( ) в одно из незаконченных подмножеств. В таком случае число обьектов с префиксом будет равно .
- Сделать текущий элемент последним в подмножестве . В таком случае это подмножество станет законченым, следовательно число обьектов с префиксом будет равно .
Таким образом на каждом шаге интервал случайных чисел
(где ) , будет разбиваться на два диапазона размерами и . Если случайно сгенерированное число попадет в первый диапазон, то сделаем последним элементом в подмножестве . Иначе добавим в случайно выбранное из незаконченных подмножеств ( ).int[] randomSetPartition(n: int k: int): //— количество элементов в множестве, — число подмножеств на которые нужно разбить исходное множество. l = 0 m = 0 downto i = n to 1 s = stirling(n - l, k - m) // — функция возвращающая число Стирлинга второго рода для заданных аргументов. r = random(1, s) if stirling(n - l - 1, k - m - 1) >= r result[i] = k - m // — номер подмножества в котором находится элемент . l = l + 1 m = m + 1 else p = random(1, k - m) // Случайным образом выбираем номер одного из незаконченных подмножеств. result[i] = p l = l + 1 return result
Так как на каждом шаге интервал случайных чисел разделяется только на на два диапазона, а всего шагов — динамически.
то итоговая сложность алгоритма — . Сложность преподсчета чисел Стирлинга второго рода — если преподсчитать ихРазбиение на случайное число подмножеств
Описаный алгоритм можно применить для получения разбиения множества на случайное число подмножеств. Для этого достаточно случайным образом выбрать число подмножеств из интевала
, так чтобы вероятность получить число была пропорциональна числу способов разбить элементов на подмножеств (что равно ).Разделим интервал случайных чисел
(где ) на диапазонов, так чтобы размер диапазона был равен . С помощью функции для генерации случайного числа получим число в интервале . Возьмем такое, что и построим случайное разбиение множества из элементов на подмножеств.См. также
Источники информации
- Комбинаторные алгоритмы: учебное пособие / Т. И. Федоряева. — Новосибирск: Новосибирский гос. ун-т, 2011. 118 с. — ISBN 978-5-4437-0019-9
- Non-Uniform Random Variate Generation / Luc Devroye. — Springer, New York, NY, 1986. 843 c. — ISBN 978-1-4613-8645-2