Искусство компьютерного программирования. Том 4: В Fascicle 3 есть тонна из них, которая может соответствовать вашей конкретной ситуации лучше, чем я описываю.
Серые коды
Проблема, с которой вы столкнетесь, это, конечно, память и довольно быстро, у вас будут проблемы с 20 элементами в вашем наборе - 20 C 3 = 1140. И если вы хотите перебрать набор, лучше всего использовать модифицированный серый закодируйте алгоритм, чтобы вы не держали их все в памяти. Они генерируют следующую комбинацию из предыдущего и избегают повторений. Есть много из них для различных целей. Мы хотим максимизировать различия между последовательными комбинациями? минимизировать? и так далее.
Некоторые из оригинальных работ, описывающих серые коды:
- Некоторые гамильтоновы пути и алгоритм минимального изменения
- Алгоритм генерации комбинации смежных обменов
Вот некоторые другие документы, освещающие эту тему:
- Эффективная реализация алгоритма генерации комбинаций Eades, Hickey, Read и смежных обменов (PDF, с кодом на Pascal)
- Комбинированные генераторы
- Обзор комбинаторных серых кодов (PostScript)
- Алгоритм для кодов серого
Чейз Твиддл (алгоритм)
Филип Дж. Чейз, « Алгоритм 382: комбинации М из N объектов » (1970)
Алгоритм в C ...
Индекс комбинаций в лексикографическом порядке (алгоритм Buckles 515)
Вы также можете ссылаться на комбинацию по ее индексу (в лексикографическом порядке). Понимая, что индекс должен быть некоторой величиной изменения справа налево на основе индекса, мы можем построить нечто, что должно восстановить комбинацию.
Итак, у нас есть набор {1,2,3,4,5,6} ... и мы хотим три элемента. Допустим, {1,2,3} мы можем сказать, что разница между элементами одна и в порядке и минимальна. {1,2,4} имеет одно изменение и лексикографически номер 2. Таким образом, число «изменений» в последнем месте соответствует одному изменению в лексикографическом порядке. Второе место с одним изменением {1,3,4} имеет одно изменение, но учитывает большее изменение, поскольку оно находится на втором месте (пропорционально количеству элементов в исходном наборе).
Метод, который я описал, является деконструкцией, как кажется, от установки к индексу, мы должны сделать обратное - что гораздо сложнее. Вот как Баксл решает проблему. Я написал несколько C для их вычисления с небольшими изменениями - я использовал индекс наборов, а не диапазон номеров, чтобы представить набор, поэтому мы всегда работаем от 0 ... n. Замечания:
- Поскольку комбинации неупорядочены, {1,3,2} = {1,2,3} - мы заказываем их как лексикографические.
- Этот метод имеет неявный 0, чтобы начать набор для первого различия.
Индекс комбинаций в лексикографическом порядке (McCaffrey)
Есть и другой способ : его концепцию легче понять и запрограммировать, но без оптимизации Buckles. К счастью, он также не создает дублирующихся комбинаций:
Набор, который максимизирует , где .
В качестве примера: 27 = C(6,4) + C(5,3) + C(2,2) + C(1,1)
. Итак, 27-я лексикографическая комбинация из четырех вещей: {1,2,5,6}, это индексы любого набора, на который вы хотите посмотреть. Пример ниже (OCaml), требует choose
функции, оставленной читателю:
(* this will find the [x] combination of a [set] list when taking [k] elements *)
let combination_maccaffery set k x =
(* maximize function -- maximize a that is aCb *)
(* return largest c where c < i and choose(c,i) <= z *)
let rec maximize a b x =
if (choose a b ) <= x then a else maximize (a-1) b x
in
let rec iterate n x i = match i with
| 0 -> []
| i ->
let max = maximize n i x in
max :: iterate n (x - (choose max i)) (i-1)
in
if x < 0 then failwith "errors" else
let idxs = iterate (List.length set) x k in
List.map (List.nth set) (List.sort (-) idxs)
Маленький и простой итератор комбинаций
Следующие два алгоритма предоставляются для дидактических целей. Они реализуют итератор и (более общие) общие комбинации папок. Они максимально быстрые, имеющие сложность O ( n C k ). Потребление памяти связано с k
.
Мы начнем с итератора, который будет вызывать пользовательскую функцию для каждой комбинации
let iter_combs n k f =
let rec iter v s j =
if j = k then f v
else for i = s to n - 1 do iter (i::v) (i+1) (j+1) done in
iter [] 0 0
Более общая версия будет вызывать предоставленную пользователем функцию вместе с переменной состояния, начиная с начального состояния. Поскольку нам нужно передать состояние между различными состояниями, мы не будем использовать цикл for, а вместо этого используем рекурсию,
let fold_combs n k f x =
let rec loop i s c x =
if i < n then
loop (i+1) s c @@
let c = i::c and s = s + 1 and i = i + 1 in
if s < k then loop i s c x else f c x
else x in
loop 0 0 [] x