Мистецтво комп’ютерного програмування Том 4: Чарівник 3 містить багато таких, які могли б відповідати вашій конкретній ситуації краще, ніж я описав.
Сірі коди
Проблема, до якої ви зіткнетеся, - це звичайно пам'ять і досить швидко, у вас будуть проблеми з 20 елементами у вашому наборі - 20 C 3 = 1140. І якщо ви хочете перебрати набір, найкраще використовувати модифікований сірий алгоритм коду, тому ви не зберігаєте їх у пам'яті. Вони генерують наступну комбінацію з попередньої та уникають повторів. Їх багато для різних цілей. Чи хочемо ми максимізувати відмінності між послідовними комбінаціями? мінімізувати? et cetera.
Деякі з оригінальних статей, що описують сірі коди:
- Деякі шляхи Гамільтона та алгоритм мінімальних змін
- Алгоритм генерації суміжних міжмісних комбінацій
Ось деякі інші статті, що висвітлюють цю тему:
- Ефективна реалізація алгоритму покоління комбінації Eades, Hickey файлів (PDF, з кодом на Паскалі)
- Комбіновані генератори
- Огляд комбінаторних сірих кодів (PostScript)
- Алгоритм сірих кодів
Чізл Чейза (алгоритм)
Філіп Дж Чейз, ` Алгоритм 382: Комбінації M із N об'єктів '(1970)
Алгоритм в С ...
Індекс комбінацій у лексикографічному порядку (Алгоритм пряжок 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 для початку набору для першої різниці.
Індекс комбінацій у лексикографічному порядку (Маккафрі)
Є й інший спосіб : його концепцію легше зрозуміти та запрограмувати, але це без оптимізацій Пряжки. На щастя, він також не створює повторюваних комбінацій:
Набір, який максимально збільшує , де .
Як приклад: 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