Искусство компьютерного программирования, том 4: В главе 3 есть тонна таких вещей, которые могли бы соответствовать вашей конкретной ситуации лучше, чем я описываю.
Серые коды
Проблема, с которой вы столкнетесь, это, конечно, память и довольно быстро, у вас будут проблемы с 20 элементами в вашем наборе - 20 C 3 = 1140. И если вы хотите перебрать множество, лучше использовать модифицированный алгоритм серого кода, чтобы не хранить все из них в памяти. Они генерируют следующую комбинацию из предыдущего и избегают повторений. Есть много из них для различных целей. Мы хотим максимизировать различия между последовательными комбинациями? минимизировать? и так далее.
Некоторые из оригинальных работ, описывающих серые коды:
- Некоторые пути Гамильтона и алгоритм минимального изменения
- Алгоритм генерации комбинации смежных обменов
Вот еще несколько статей на эту тему:
- Эффективная реализация алгоритма генерации комбинаций Eades, Hickey, Read для смежного обмена (PDF, с кодом на Паскале)
- Комбинированные генераторы
- Обзор комбинаторных кодов серого (PostScript)
- Алгоритм для кодов серого
Чейз Твиддл (алгоритм)
Филипп Дж. Чейз, ` Алгоритм 382: комбинации М из N объектов '(1970)
Алгоритм на C ...
Индекс комбинаций в лексикографическом порядке (алгоритм Buckles 515)
Вы также можете ссылаться на комбинацию по ее индексу (в лексикографическом порядке). Понимая, что индекс должен быть некоторой величиной изменения справа налево на основе индекса, мы можем построить нечто, что должно восстановить комбинацию.
Итак, у нас есть набор {1,2,3,4,5,6} ... и мы хотим три элемента. Допустим, {1,2,3} мы можем сказать, что разница между элементами одна и в порядке и минимальна. {1,2,4} имеет одно изменение и лексикографически номер 2. Таким образом, число «изменений» в последнем месте соответствует одному изменению в лексикографическом порядке. Второе место с одним изменением {1,3,4} имеет одно изменение, но учитывает большее изменение, поскольку оно находится на втором месте (пропорционально количеству элементов в исходном наборе).
Метод, который я описал, является деконструкцией, как кажется, от установки к индексу, нам нужно сделать обратное - что гораздо сложнее. Вот как Buckles решает проблему. Я написал несколько 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