Rekurencja

/*
SilniaDZ.java

Mówimy, że wywołanie metody jest wywołaniem rekurencyjnym,
jeśli metoda wywołuje samą siebie. Innymi słowy, jeżeli w
ciele danej metody znajduje się wywołanie tej samej metody,
to jest to wywołanie rekurencyjne. W poniższym programie
rekurencyjnie wywoływana jest metoda silnia, która oblicza
wartość silni na podstawie wzoru rekurencyjnego:

0! = 1
n! = n*(n-1)!
*/

public class SilniaDZ
{
  public static void main(String[] args)
  {
    final int N = 3;
    System.out.println(N + "! = " + silnia(N));
  }

  static long silnia(int n)
  {
    if (n == 0)
    {
      return 1;
    }
    else
    {
      return n*silnia(n-1); // silnia(n-1) wywołanie rekurencyjne
    }
  }
}
3! = 6
Press any key to continue...

Analiza wywołań rekurencyjnych

Zadanie 1

Dokonaj analizy wywołania silnia(3) w programie SilniaDZ.

1. Przebieg z góry do dołu

2. Przebieg z dołu do góry

Uwaga: Kolejne wywołania metody silnia zostały ponumerowane przy pomocy indeksów dolnych.

Graf wywołań rekurencyjnych

silnia1(3) --> silnia2(2) --> silnia3(1) --> silnia4(0)

Opis wywołań

Opis wywołań podamy na przykładzie wywołania silnia(0) i silnia(1)

  • silnia(0)    - wywołujemy metodę silnia z parametrem aktualnym 0

                 - w momencie wywołania metody silnia parametr formalny n przyjmuje wartość
                   parametru aktualnego, czyli 0

                 - wchodzimy do ciała metody silnia, napotykamy instrukcję if..else, sprawdzamy
                   warunek logiczny z nagłówka instrukcji, warunek jest prawdziwy, bo 0 = 0,
                   a zatem wchodzimy do pierwszego ciała instrukcji

                 - w pierwszym ciele instrukcji if..else zwracamy wartość 1
                   
  • silnia(1)    - wywołujemy metodę silnia z parametrem aktualnym 1
 
                 - w momencie wywołania metody silnia parametr formalny n przyjmuje wartość
                   parametru aktualnego, czyli 1
 
                 - wchodzimy do ciała metody silnia, napotykamy instrukcję if..else, sprawdzamy
                   warunek logiczny z nagłówka instrukcji, warunek jest fałszywy, bo 1 ≠ 0, 
                   a zatem pomijamy pierwsze ciało i wchodzimy do drugiego ciała instrukcji
 
                 - w drugim ciele instrukcji if..else zwracamy wartość wyrażenie 1*silnia(0) 
                   w którym pojawia się wywołanie rekurencyjne silnia(0)
/* Polinomial.java */

public class Polinomial
{
  public static void main(String[] args)
  {
    double[] a = {1,2,3,4}; // 1 + 2x + 3x^2 + 4x^3
    double x = 0.5;
    
    System.out.println("w(" + x + ") = " + w(a,x));
  }
  
  static double potega(double x, int n)
  {
    if (n == 1)
    {
      return x;
    }
    else
    {
      return x * potega(x, n-1);
    }
  }
  
  static double w(double[] a, double x)
  {
    if (a.length == 0) return 0;
    
    return polinomial(a, x, a.length-1);
  }
  
  static double polinomial(double[] a, double x, int i)
  {
    if (i == 0)
    {
      return a[0];
    }
    else
    {
      return polinomial(a, x, i-1) + a[i] * potega(x, i);
    }
  }
}
w(0.5) = 3.25
Press any key to continue...

Zadanie 2

Dokonaj analizy wywołania w(a,0.5) w programie Polinomial.

       0  1  2  3
a --> [1][2][3][4]

a.length = 4

Analizujemy wywołanie w(a,0.5).

• w1(a, 0.5) = 3.25
  4 == 0   false
  return polinomial2(a, 0.5, 3) = 3.25
• polinomial2(a, 0.5, 3) = 3.25
  return polinomial3(a, 0.5, 2) + a[3] * potega9(0.5, 3) = 2.75 + 4.0 * 0.125 = 2.75 + 0.5 = 3.25
• polinomial3(a, 0.5, 2) = 2.75
  return polinomial4(a, 0.5, 1) + a[2] * potega7(0.5, 2) = 2.0 + 3.0 * 0.25 = 2.75
• polinomial4(a, 0.5, 1) = 2.0
  return polinomial5(a, 0.5, 0) + a[1] * potega6(0.5, 1) = 1.0 + 2.0 * 0.5 = 2.0
• polinomial5(a, 0.5, 0) = 1.0
  return a[0] = 1.0
• potega6(0.5, 1) = 0.5
  return 0.5
• potega7(0.5, 2) = 0.25
  return 0.5 * potega8(0.5, 1) = 0.5 * 0.5 = 0.25
• potega8(0.5, 1) = 0.5
  return 0.5
• potega9(0.5, 3) = 0.125
  return 0.5 * potega10(0.5, 2) = 0.5 * 0.25 = 0.125
• potega10(0.5, 2) = 0.25
  return 0.5 * potega11(0.5, 1) = 0.5 * 0.5 = 0.25
• potega11(0.5, 1) = 0.5
  return 0.5

Uwaga: Kolejne wywołania metod zostały ponumerowane przy pomocy indeksów dolnych.

/*
FiboDZ.java

W poniższym programie metoda fibo wyznacza n-ty wyraz
ciągu Fibonacciego na podstawie wzoru rekurencyjnego

fibo(n) = 1  dla n = 1
fibo(n) = 1  dla n = 2
fibo(n) = fibo(n-1) + fibo(n-2)  dla n > 2

Dwa pierwsze wyrazy ciągu Fibonacciego mają wartość
1 a każdy kolejny wyraz jest sumą dwóch poprzednich.

Ciąg Fibonacciego: 1, 1, 2, 3, 5, 8, 13, 21, ...
*/

public class FiboDZ
{
  public static void main(String[] args)
  {
    final int N = 5;
    System.out.println("fibo(" + N + ") = " + fibo(N));
  }

  static int fibo(int n)
  {
    if (n == 1 || n == 2)
    {
      return 1;
    }
    else
    {
      return fibo(n-1) + fibo(n-2); // dwa wywołania rekurencyjne
    }
  }
}
fibo(5) = 5
Press any key to continue...

Zadanie 3

Dokonaj analizy wywołania fibo(5) w programie FiboDZ.

Analiza bez wyróżniania przebiegów

Graf wywołań rekurencyjnych

                        fibo1(5)
                          / \
                         /   \
                 fibo2(4)      fibo7(3)
                /\                   /\
               /  \                 /  \
       fibo3(3)    fibo6(2)  fibo8(2)   fibo9(1)
         /\
        /  \
fibo4(2)    fibo5(1)

Zadanie 4

Dokonaj analizy wywołania fibo(5) w programie FiboDZ.

1. Przebieg z góry do dołu

2. Przebieg z dołu do góry

3. Przebieg z góry do dołu

4. Przebieg z dołu do góry

5. Przebieg z góry do dołu

6. Przebieg z dołu do góry

7. Przebieg z góry do dołu

8. Przebieg z dołu do góry

9. Przebieg z góry do dołu

10. Przebieg z dołu do góry

11. Przebieg z dołu do góry

Graf wywołań rekurencyjnych

                        fibo1(5)
                          / \
                         /   \
                 fibo2(4)      fibo7(3)
                /\                   /\
               /  \                 /  \
       fibo3(3)    fibo6(2)  fibo8(2)   fibo9(1)
         /\
        /  \
fibo4(2)    fibo5(1)
/*
FiboDZ2.java

Poniższy program wypisuje, jak wyglądają kolejne
wywołania rekurencyjne. Na ich podstawie możemy
zweryfikować analizę wywołań rekurencyjnych.
*/

public class FiboDZ2
{
  private static int wywolanie;

  public static void main(String[] args)
  {
    final int N = 5; fibo(N);
  }

  static int fibo(int n)
  {
    wywolanie++;

    System.out.println("fibo" + wywolanie + "(" + n + ") = " + f(n));

    if (n == 1 || n == 2)
    {
      return 1;
    }
    else
    {
      return fibo(n-1) + fibo(n-2); // dwa wywołania rekurencyjne
    }
  }

  static int f(int n)
  {
    if (n == 1 || n == 2)
    {
      return 1;
    }
    else
    {
      return f(n-1) + f(n-2);
    }
  }
}
fibo1(5) = 5
fibo2(4) = 3
fibo3(3) = 2
fibo4(2) = 1
fibo5(1) = 1
fibo6(2) = 1
fibo7(3) = 2
fibo8(2) = 1
fibo9(1) = 1
Press any key to continue...

Strona główna