Rekursja to trudny temat do zrozumienia i nie sądzę, żebym mógł to w pełni oddać tutaj sprawiedliwie. Zamiast tego spróbuję skupić się na konkretnym fragmencie kodu, który tu masz i spróbuję opisać zarówno intuicję, dlaczego rozwiązanie działa, jak i mechanikę, w jaki kod oblicza wynik.
Kod, który tu podałeś, rozwiązuje następujący problem: chcesz poznać sumę wszystkich liczb całkowitych od a do b włącznie. Na przykład chcesz sumę liczb od 2 do 5 włącznie, czyli
2 + 3 + 4 + 5
Próbując rozwiązać problem rekurencyjnie, jednym z pierwszych kroków powinno być ustalenie, jak podzielić problem na mniejszy problem o tej samej strukturze. Załóżmy więc, że chcesz zsumować liczby od 2 do 5 włącznie. Jednym ze sposobów na uproszczenie tego jest zauważenie, że powyższą sumę można przepisać jako
2 + (3 + 4 + 5)
Tutaj (3 + 4 + 5) jest sumą wszystkich liczb całkowitych od 3 do 5 włącznie. Innymi słowy, jeśli chcesz poznać sumę wszystkich liczb całkowitych od 2 do 5, zacznij od obliczenia sumy wszystkich liczb całkowitych od 3 do 5, a następnie dodaj 2.
Jak więc obliczyć sumę wszystkich liczb całkowitych od 3 do 5 włącznie? Cóż, ta kwota jest
3 + 4 + 5
które można traktować zamiast tego jako
3 + (4 + 5)
Tutaj (4 + 5) jest sumą wszystkich liczb całkowitych od 4 do 5 włącznie. Tak więc, jeśli chcesz obliczyć sumę wszystkich liczb od 3 do 5 włącznie, obliczysz sumę wszystkich liczb całkowitych od 4 do 5, a następnie dodasz 3.
Tutaj jest wzór! Jeśli chcesz obliczyć sumę liczb całkowitych między a i b włącznie, możesz wykonać następujące czynności. Najpierw oblicz sumę liczb całkowitych od a + 1 do b włącznie. Następnie dodaj do tej sumy. Zauważysz, że „oblicz sumę liczb całkowitych między a + 1 i b, włącznie” jest mniej więcej tego samego rodzaju problemem, który już próbujemy rozwiązać, ale z nieco innymi parametrami. Zamiast obliczać od a do b włącznie, obliczamy od a + 1 do b włącznie. To jest krok rekurencyjny - aby rozwiązać większy problem („suma od a do b, włącznie”), redukujemy problem do mniejszej wersji samego siebie („suma od a + 1 do b włącznie”).
Jeśli spojrzysz na kod, który masz powyżej, zauważysz, że jest w nim następujący krok:
return a + sumInts(a + 1, b: b)
Ten kod jest po prostu tłumaczeniem powyższej logiki - jeśli chcesz sumować od a do b włącznie, zacznij od zsumowania a + 1 do b, włącznie (to jest rekurencyjne wywołanie sumInt
s), a następnie dodaj a
.
Oczywiście samo to podejście nie zadziała. Na przykład, jak obliczyć sumę wszystkich liczb całkowitych od 5 do 5 włącznie? Cóż, używając naszej bieżącej logiki, obliczylibyście sumę wszystkich liczb całkowitych od 6 do 5 włącznie, a następnie dodalibyście 5. Jak więc obliczyć sumę wszystkich liczb całkowitych od 6 do 5 włącznie? Cóż, korzystając z naszej obecnej logiki, obliczysz sumę wszystkich liczb całkowitych od 7 do 5 włącznie, a następnie dodasz 6. Zauważysz tutaj problem - to po prostu trwa i trwa!
W rekurencyjnym rozwiązywaniu problemów musi istnieć sposób, aby przestać upraszczać problem i zamiast tego po prostu przejść bezpośrednio do jego rozwiązania. Zazwyczaj można znaleźć prosty przypadek, w którym odpowiedź można określić natychmiast, a następnie skonstruować rozwiązanie tak, aby rozwiązywać proste przypadki bezpośrednio, gdy się pojawią. Jest to zwykle nazywane przypadkiem bazowym lub rekurencyjną podstawą .
Więc jaki jest podstawowy przypadek w tym konkretnym problemie? Kiedy sumujesz liczby całkowite od a do b, włącznie, jeśli a jest większe od b, to odpowiedź brzmi 0 - w zakresie nie ma żadnych liczb! Dlatego skonstruujemy nasze rozwiązanie w następujący sposób:
- Jeśli a> b, to odpowiedź wynosi 0.
- W przeciwnym razie (a ≤ b), uzyskaj odpowiedź w następujący sposób:
- Oblicz sumę liczb całkowitych między a + 1 i b.
- Dodaj, aby uzyskać odpowiedź.
Teraz porównaj ten pseudokod z rzeczywistym kodem:
func sumInts(a: Int, b: Int) -> Int {
if (a > b) {
return 0
} else {
return a + sumInts(a + 1, b: b)
}
}
Zauważ, że istnieje prawie dokładnie mapa jeden do jednego między rozwiązaniem opisanym w pseudokodzie a tym rzeczywistym kodem. Pierwszym krokiem jest przypadek podstawowy - w przypadku, gdy pytasz o sumę pustego zakresu liczb, otrzymujesz 0. W przeciwnym razie oblicz sumę między a + 1 i b, a następnie dodaj a.
Do tej pory przedstawiłem tylko ogólną koncepcję kodu. Ale miałeś jeszcze dwa bardzo dobre pytania. Po pierwsze, dlaczego to nie zawsze zwraca 0, biorąc pod uwagę, że funkcja mówi, że zwraca 0, jeśli a> b? Po drugie, skąd właściwie pochodzi 14? Spójrzmy na to po kolei.
Wypróbujmy bardzo, bardzo prosty przypadek. Co się stanie, jeśli zadzwonisz sumInts(6, 5)
? W tym przypadku, śledząc kod, zobaczysz, że funkcja po prostu zwraca 0. Tak właśnie jest, aby - nie ma żadnych liczb w zakresie. Teraz spróbuj czegoś mocniejszego. Co się dzieje, kiedy dzwonisz sumInts(5, 5)
? Cóż, oto co się dzieje:
- Ty dzwonisz
sumInts(5, 5)
. Wchodzimy do else
gałęzi, która zwraca wartość `a + sumInts (6, 5).
- Aby
sumInts(5, 5)
ustalić, co sumInts(6, 5)
jest, musimy wstrzymać to, co robimy i wykonać telefon sumInts(6, 5)
.
sumInts(6, 5)
zostanie wezwany. Wchodzi do if
gałęzi i wraca 0
. Jednak to wystąpienie sumInts
zostało wywołane przez sumInts(5, 5)
, więc wartość zwracana jest przekazywana z powrotem sumInts(5, 5)
, a nie do obiektu wywołującego najwyższego poziomu.
sumInts(5, 5)
teraz może obliczyć, 5 + sumInts(6, 5)
aby wrócić 5
. Następnie zwraca go do dzwoniącego najwyższego poziomu.
Zwróć uwagę, jak powstała tutaj wartość 5. Zaczęliśmy od jednego aktywnego połączenia do sumInts
. To odpaliło kolejne wywołanie rekurencyjne, a wartość zwrócona przez to wywołanie przekazała informację z powrotem sumInts(5, 5)
. Następnie wywołanie z sumInts(5, 5)
kolei wykonało jakieś obliczenia i zwróciło wartość z powrotem do dzwoniącego.
Jeśli spróbujesz tego z sumInts(4, 5)
, oto co się stanie:
sumInts(4, 5)
próbuje wrócić 4 + sumInts(5, 5)
. Aby to zrobić, woła sumInts(5, 5)
.
sumInts(5, 5)
próbuje wrócić 5 + sumInts(6, 5)
. Aby to zrobić, woła sumInts(6, 5)
.
sumInts(6, 5)
zwraca 0 z powrotem do sumInts(5, 5).</li>
<li>
sumInts (5, 5) now has a value for
sumInts (6, 5) , namely 0. It then returns
5 + 0 = 5`.
sumInts(4, 5)
ma teraz wartość dla sumInts(5, 5)
, a mianowicie 5. Następnie zwraca 4 + 5 = 9
.
Innymi słowy, wartość, która jest zwracana, jest tworzona przez zsumowanie wartości pojedynczo, za każdym razem biorąc jedną wartość zwróconą przez określone wywołanie rekurencyjne sumInts
i dodając bieżącą wartość a
. Kiedy rekurencja osiąga najniższy poziom, najgłębsze wywołanie zwraca 0. Jednak ta wartość nie opuszcza natychmiast rekurencyjnego łańcucha wywołań; zamiast tego po prostu przekazuje wartość z powrotem do wywołania rekurencyjnego o jedną warstwę powyżej. W ten sposób każde wywołanie rekurencyjne po prostu dodaje jeszcze jedną liczbę i zwraca ją wyżej w łańcuchu, kończąc na ogólnym sumowaniu. W ramach ćwiczenia spróbuj to wyśledzić, od sumInts(2, 5)
czego chciałeś zacząć.
Mam nadzieję że to pomoże!