Kombinatoryka odgrywa ważną rolę w informatyce. Często stosujemy metody kombinatoryczne zarówno w analizie, jak i projektowaniu w algorytmach. Na przykład jedna metoda znajdowania zestawu okładek -vertex na wykresie może po prostu sprawdzić wszystkie możliwe podzbiory . Podczas gdy funkcje dwumianowe rosną wykładniczo, jeśli jest jakąś stałą stałą, w wyniku analizy asymptotycznej otrzymujemy algorytm wielomianowy. k
Często rzeczywiste problemy wymagają bardziej złożonych mechanizmów kombinatorycznych, które możemy zdefiniować w kategoriach nawrotów. Jednym znanym przykładem jest sekwencja Fibonacciego (naiwnie) zdefiniowana jako:
Teraz obliczenie wartości tego terminu rośnie wykładniczo przy tej rekurencji, ale dzięki programowaniu dynamicznemu możemy obliczyć ją w czasie liniowym. Teraz nie wszystkie rekurencje nadają się do DP (z ręki, funkcja czynnikowa), ale jest to potencjalnie użyteczna właściwość, gdy określa się, że niektóre liczą się jako rekurencja, a nie funkcja generująca.
Generowanie funkcji to elegancki sposób sformalizowania pewnej liczby dla danej struktury. Być może najbardziej znaną jest funkcja generowania dwumianowego zdefiniowana jako:
Na szczęście ma to rozwiązanie w formie zamkniętej. Nie wszystkie funkcje generujące pozwalają na tak zwięzły opis.
Teraz moje pytanie brzmi: jak często generowane są funkcje wykorzystywane w projektowaniu algorytmów? Łatwo jest zobaczyć, jak można je wykorzystać do zrozumienia tempa wzrostu wymaganego przez algorytm za pomocą analizy, ale co mogą nam powiedzieć o problemie podczas tworzenia metody rozwiązania jakiegoś problemu?
Jeśli wiele razy ta sama liczba może zostać przeformułowana jako powtórzenie, może ulec programowaniu dynamicznemu, ale znowu być może ta sama funkcja generująca ma postać zamkniętą. Więc nie jest tak równomiernie cięty.
źródło
Odpowiedzi:
Funkcje generujące są przydatne podczas projektowania algorytmów liczenia. Oznacza to, że nie tylko szukasz liczby obiektów mających określoną właściwość, ale także szukasz sposobu wyliczenia tych obiektów (i być może wygenerowania algorytmu zliczania obiektów). Bardzo dobra prezentacja znajduje się w rozdziale 7 konkretnej matematyki autorstwa Ronalda Grahama, Donalda Knutha i Orena Patashnika . Poniższe przykłady pochodzą z tych książek (błędy i brak jasności są moje).
Załóżmy, że szukasz sposobów na zmianę za pomocą danego zestawu monet. Na przykład w przypadku wspólnych nominałów amerykańskich¹ możliwe monety to . Aby dać ¢ 42 w zamian, jedną z możliwości jest ; inną możliwością jest . Napiszemy . Mówiąc bardziej ogólnie, możemy napisać funkcję generującą dla wszystkich sposobów wprowadzania zmian: w bardziej technicznych[ 25 ] [ 10 ] [ 5 ] [ 1 ] [ 1 ] [ 10 ] [ 10 ] [ 10 ] [ 10 ] [ 1 ] [ 1 ] 42 ⟨ [ 25 ] [ 10 ][1],[5],[10],[25],[100] [25][10][5][1][1] [10][10][10][10][1][1] H = Σ H ≥ 0 Σ q ≥ 0 Σ d ≥ 0 Σ n ≥ 0 Σ s ≥ 0 [ 100 ] H [ 25 ] q [ 10 ] d [ 5 ] n [ 1 ] p42⟨[25][10][5][1]2⟩=⟨[10]4[1]2⟩
Trudniejszy przykład: załóżmy, że chcesz przestudiować wszystkie sposoby układania prostokątów domino 2 × 1. Na przykład istnieją dwa sposoby na ułożenie prostokąta 2 × 2 za pomocą dwóch poziomych domino lub dwóch pionowych domino. Zliczanie liczby sposobów na kafelkowanie prostokąta jest dość łatwe, ale przypadek szybko staje się nieoczywisty. Możemy wyliczyć wszystkie możliwe nachylenia poziomego pasma wysokości 3, sklejając domino razem, co szybko daje powtarzalne wzory:2 × n 3 × n
Ponownie przeczytaj Matematykę konkretną, aby uzyskać mniej pochopną prezentację.
¹ Wiem, że moja lista jest niekompletna; Załóżmy, że USA są uproszczone i odpowiednie dla przykładów matematycznych.
² ² Także, jeśli się pojawi, załóż monety sferyczne.
³ I lepszy skład.
źródło
Pamiętam problem, który musiałem rozwiązać podczas konkursu programistycznego w 2001 roku. Problem był następujący:
Zacząłem od zagnieżdżonych pętli, ale szybko uderzyłem w ścianę. Potem zdałem sobie sprawę, że musiałem zacząć od wyliczenia, co można zrobić z lżejszymi masami, zanim zacznę z cięższymi. Mógłbym rozwiązać problem z dużą ilością nieotwartych pętli.
Gdybym w tym czasie nie był młodzieńczo arogancki i samowystarczalny (i gdybym wiedział i ćwiczył funkcje generowania), mógłbym zdefiniować problem z generowaniem funkcji jako takich:
Zdefiniuj jako OGF dla wielu sposobów ważenia masy przy danym zbiorze mas.fa( x ) n
Jaką wagę na prawej szalce mogę zważyć przy pojedynczej masie 1?
Trzy możliwości:
Jest więc jeden sposób ważenia , jeden sposób ważenia i jeden sposób ważenia . Funkcja generująca dla tej masy to coś w rodzaju , co odpowiada:- 1 0 1 x- 1+ 1 + x
Funkcja generująca dla pojedynczej masy to , co oznacza:m x- m+ 1 + xm
Biorąc pod uwagę wielokrotność mas, wyraża się jako iloczyn funkcji generujących pojedynczą masę:M. fa
Teraz, biorąc pod uwagę pakiet, który może wykonywać operacje na wielomianach, wystarczy:
Algorytm zaprojektowałem przy użyciu matematycznie poprawnych komponentów. Główna część algorytmu, która jest podziałem wielomianowym o najniższym stopniu jako pierwszym, jest liniowa i może być zaimplementowana w gotowym pakiecie. Może nie jest optymalny, ale z pewnością działa lepiej niż to, co zrobiłem na zawodach i w mniej podatny na błędy sposób.
Jeśli przyjrzysz się uważnie procesowi podziału, szybko zauważysz, że reszta może być postrzegana jako „bieżący stan ukryty” w każdym stanie procesu, a iloraz jako wynik. Proces kończy się, gdy „bieżący stan ukryty” wszędzie osiąga zero.
Możesz implementować wielomiany jako tablice lub, jeśli są one naprawdę rzadkie, jako listy uporządkowane według współczynnika indeksu, a to nie zmieni algorytmu.
źródło
źródło
Być może najobszerniejszym przykładem jest obszerne studium Quicksort i jego wielu wariantów. Istnieją rozważania kombinatoryczne rządzące rozważaniem alternatyw, a analiza rozwiązań dość skomplikowanych równań wykazuje zalety (lub nie) wydajności.
źródło