Silnia bez petli for

0

W ksiazce, ktora czytam jest taki oto kod jak ponizej. Oblicza silnie ale bez uzycia for, zamiast tego jest ponowne wywolanie tej samej metody. Czy to duzo lepsza praktyka niz zwyczajnie uzycie for ()...? Przyznam szczerze, ze nie przyszloby mi do glowy, zeby w ten sposob do tego podejsc :)

package stacktrace;

import java.util.*;

public class StackTraceTest {

	public static int factorial(int n){
		System.out.println("factorial(" + n + "):");
		Throwable t = new Throwable();
		StackTraceElement [] frames = t.getStackTrace();
		for (StackTraceElement f : frames){
			System.out.println(f);
		}
		int r;

// tu kawalek obliczajcy silnie 
		if (n<=1) r = 1;
		else r = n * factorial(n-1);


		System.out.println("return " + r);
		return r;
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner in = new Scanner(System.in);
		System.out.println("Enter n: ");
		int n = in.nextInt();
		factorial(n);

	}

}
0

Zarówno iteracyjne, jak i rekurencyjne rozwiązania mają swoje plusy i minusy.

Polecam poczytać coś w tym stylu: http://stackoverflow.com/questions/11171648/factorial-method-recursive-or-iterative-java

2

Zależy od języka, są języki, w których nie da się inaczej niż rekurencją (tak się nazywa wywoływanie metody przez samą siebie). Jednak a propos Javy to takie podejście nie ma sensu z racji, że JVM nie ma tzw. tail call optimization. Chodzi o to, że istnieje metoda optymalizacji wywołań rekurencyjnych. Załóżmy twój scenariusz. Wywołania wyglądają tak:

factorial(5)
  factorial(4)
    factorial(3)
      factorial(2)
        factorial(1)
        1
      2
    6
  24
120

Jednak jeśli napiszemy tę metodę w taki sposób:

public static int _factorial(int n, int acc) {
    if n < 1 {
        return acc;
    } else {
        return factorial(n - 1, acc * n);
    }
}

public static int factorial(int n) {
    return _factorial(n, 1);
}

To sprytny kompilator może zauważyć, że tylko zmieniają się parametry, więc zamiast wywoływać funkcję (i zwiększać stos) to tylko podmieni argumenty i wróci na początek funkcji, więc wyjdzie coś takiego:

factorial(5)
    _factorial(5, 1)
    _factorial(4, 5)
    _factorial(3, 20)
    _factorial(2, 60)
    _factorial(1, 120)
    _factorial(0, 120)
120

Zamiast zagłębiania się wywołań mamy płaską strukturę, która jest równoważna pętli. Takie "sprytne rozwiązanie" nazywa się właśnie TCO (tail call optimization, optymalizacja wywołań ogonowych).

Jednakowoż jak wyżej wspomniałem maszyna wirtualna Javy (JVM) nie posiada takowego usprawnienia, przez co napisany w taki sposób kod jest mniej wydajny i bardzo łatwo może doprowadzić do, tzw. przepełnienia stosu, czyli zbyt głębokiego wejścia w rekurencję, co spowoduje rzucenie wyjątku i przerwanie programu.

0

W przypadku funkcji factorial rzucenie wyjątku można uznać za korzystne. Wynik otrzymany iteracyjnie i tak będzie błędny dla n > 12, rzucenie wyjątku ustrzeże nas przed przeoczeniem tego błędu. Na mojej maszynie StackOverflowError leci dla n = 9319.

0

Chodzi w zadaniu pewnie tylko o to, żebyś zakumał o co chodzi z rekurencją. Są pewne problemy, które rozwiązuje się tylko rekurencyjnie, bo inaczej nie ma to żadnego sensu.

1 użytkowników online, w tym zalogowanych: 0, gości: 1