Jak definiować ciągi rekurencyjne?
Tytułowa rekurencja jest jednym z podstawowych pojęć w informatyce, które umożliwia definiowanie ciągów różnych obiektów, pozwalając odwoływać się w definicji danego obiektu do jego poprzedników. Pokażemy dwie klasy takich definicji i omówimy ich równoważność.
Zacznijmy od prostych przykładów: ciągu arytmetycznego i ciągu geometrycznego. Rozważmy dwa ciągi i zadane przez
(Takie ciągi przydają się, na przykład, do szacowania, jak długo działa program, który rekurencyjnie wywołuje siebie lub inne programy.)
W obu przypadkach, żeby poznać wartość konkretnego lub (korzystając wprost z definicji), trzeba się wciąż odwoływać do wartości poprzednich elementów, aż dojdziemy do pierwszego elementu lub Oczywiście, jest ciągiem arytmetycznym, a ciągiem geometrycznym, więc wiemy, że procedurę obliczania wartości konkretnego elementu ciągu da się przyspieszyć i skorzystać ze wzorów: oraz Nie zawsze jest to takie proste. Trochę ciekawszym przykładem jest ciąg Fibonacciego zadany przez:
Zauważmy, że aby obliczyć wartości kolejnych elementów ciągu, musimy się zawsze odwołać do dwóch poprzednich elementów i dlatego potrzebne są aż dwa początkowe elementy. Spróbujmy uogólnić to zjawisko i zdefiniować tak zwane liniowe ciągi rekurencyjne, którym będziemy się dalej szerzej przyglądać. Powiemy, że ciąg jest liniowym ciągiem rekurencyjnym stopnia jeśli znamy wartości pierwszych elementów, a pozostałe elementy są wyznaczone przez poprzednich elementów. Dokładniej:
(1) |
dla danych liczb rzeczywistych Dodatkowo zakładamy, że (inaczej ciąg byłby stopnia ). Przykładowo ciąg jest takim ciągiem stopnia 1 i podobnie jest takim ciągiem stopnia 2. Ciąg nie spełnia tej definicji, ponieważ odwołuje się do stałej (zauważmy, że w definicji (1) stałe mogą być tylko przemnożone przez jeden z poprzednich elementów ciągu ). Można to jednak łatwo naprawić: zauważmy, że ciąg spełnia Tak więc wystarczy w definicji podmienić trójkę na Otrzymujemy w ten sposób równoważną rekurencję stopnia 2:
Pierwsze pytanie, jakim się zajmiemy w tym tekście, to pytanie, czy możemy ograniczyć się wyłącznie do ciągów stopnia 1. Okazuje się, że odpowiedź brzmi "tak", ale - niestety - nie za darmo. Jeśli pozwolimy na to, żeby użyć więcej niż jednego ciągu, to każdy ciąg stopnia można zamienić na układ ciągów stopnia 1. Przykładowo ciąg Fibonacciego można zdefiniować, używając jednego dodatkowego ciągu:
Nietrudno zauważyć, że ciąg służy jako bufor do przetrzymywania wartości i równie nietrudno uogólnić tę metodę na dowolny ciąg stopnia W takim razie zadajmy pytanie odwrotne: czy mając układ ciągów stopnia 1, możemy zdefiniować każdy ciąg z tego układu za pomocą jednego ciągu (być może stopnia większego niż 1)? Rozważmy przykład, który pokaże trudność tego zadania: zdefiniujemy ciąg używając dwóch dodatkowych ciągów: i
(2) |
Poprawność tej definicji wynika z prostej tożsamości Czy możemy zdefiniować ciąg bez użycia dodatkowych ciągów? Warto się chwilę zastanowić nad tym pytaniem, zanim przeczytamy rozwiązanie. Odpowiedź brzmi "tak" i realizuje ją rekurencja poniżej:
(3) |
Szybkie rachunki pokazują poprawność tego wzoru
(4) |
Ogólnie prawdą jest, że każdy ciąg zdefiniowany za pomocą układu ciągów stopnia 1 możemy przekształcić w ciąg zdefiniowany za pomocą tylko jednego ciągu o stopniu Dowód tego faktu nie jest bardzo trudny, ale wymaga podstawowej znajomości algebry liniowej, więc nie omówimy go tutaj w pełnej ogólności.
Zamiast tego spróbujemy zademonstrować tę własność dla szczególnego przypadku. Zauważmy, że nietrudno uogólnić definicję (2) tak, żeby zdefiniować dla dowolnego Wystarczy rozpisać wzór na i użyć dodatkowych ciągów. Wykażemy, że można ten ciąg zdefiniować, używając tylko jednego ciągu stopnia Pierwsze elementów zadajemy w oczywisty sposób jako:
Rekurencję weźmiemy trochę "z kapelusza". Najpierw rozwiniemy wielomian w równaniu :
(5) |
i przeniesiemy wszystkie wyrazy o stopniu mniejszym niż na prawą stronę
(6) |
Z tego wzoru magicznie otrzymujemy wzór rekurencyjny na podstawiając pod jednomian dla każdego (w szczególności pod podstawiamy ):
Sprawdźmy, że dla dostajemy czyli dokładnie rozwiązanie z (3). Pozostaje wykazać, że rekurencja otrzymana z (6) jest poprawna dla każdego Wystarczy sprawdzić, że
czyli
(7) |
Uzasadnimy to równanie, korzystając z metody interpretacji kombinatorycznej. Otóż dla ustalonej wartości rozważmy Przyjmijmy, że mamy dwie grupy elementów: pierwszą grupę gdzie ; i drugą gdzie Możemy zinterpretować jako wybranie podzbioru o rozmiarze natomiast jako dobranie ciągu z powtórzeniami spośród pozostałych elementów, czyli Pozostały czynnik określa, czy to wszystko dodamy, czy odejmiemy od całej sumy.
Obliczymy teraz to samo raz jeszcze, ale tym razem w innej kolejności. Ustalmy najpierw jakiś ciąg z powtórzeniami gdzie Niech będzie zbiorem elementów, które nie występują w ciągu i oznaczmy Pytanie brzmi, na ile różnych sposobów można dobrać do tego ciągu zbiór tak, żeby otrzymać dokładnie to samo co poprzednio. Jedyny warunek, jaki musi spełniać, to Przyjmijmy dodatkowo, że chcemy wybrać ustalonego rozmiaru Możemy to zrobić na sposobów i wtedy będziemy brali ten składnik ze znakiem To znaczy, że dla ustalonego ciągu dodamy do sumy Podstawiając w (5) oraz widzimy, że to wyrażenie jest zawsze równe To znaczy, że sumując po wszystkich ciągach otrzymamy i tak czyli wykazaliśmy (7).
Nasz argument, niestety, wymagał wyczarowania rekurencji otrzymanej z rozpisania (5). Żeby otrzymać ten wzór bez zgadywania, potrzeba ponownie nieco algebry liniowej. Co więcej, zauważmy, że nawet samo udowodnienie poprawności tego wzoru wymagało znajomości podstaw kombinatoryki. Niech będzie to więc przestroga, że i informatykowi dobrze jest znać trochę matematyki.