HEINZ NIXDORF INSTITUT Universität Paderborn Technische Informatik für Ingenieure – WS 2010/2011 Musterlösung Übungsblatt Nr. 12 24. Januar 2011 Übungsgruppenleiter: Matthias Fischer Mouns Almarrani Rafał Dorociak Michael Feldmann Thomas Gewering Benjamin Koch Dominik Lüke Alexander Teetz Simon Titz Simon Oberthür Seite 1(4) Aufgabe 1: (Rekursion) a) Erstellen Sie ein Java-Programm, welches die Ackermann-Funktion rekursiv berechnet. Die Ackermann-Funktion sei wie folgt rekursiv definiert: ackermann(0, m) = m+1 ackermann(n, 0) = ackermann(n - 1, 1) ackermann(n, m) = ackermann(n - 1, ackermann(n, m - 1)) Lösung: „Die Ackermann-Funktion ist ein prominentes Beispiel für eine rekursive Funktion, die jetzt - und noch die nächsten Jahrzehnte - Informatiker im Studium beschäftigt. Sie ist nach F. Wilhelm Ackermann (1886-1962) benannt. Viele Funktionen der mathematischen Praxis sind primitiv rekursiv, und David Hilbert stellte 1926 die Frage, ob alle Funktionen, deren Argumente und Werte natürliche Zahlen sind, primitiv rekursiv sind. Die Ackermann-Funktion steigt sehr stark an und ist für Theoretiker ein Beispiel dafür, dass es berechenbare Funktionen gibt, die aber nicht primitiv rekursiv sind. Im Jahre 1928 zeigte Ackermann dies an einem Beispiel: der Ackermann-Funktion.6 Sie wächst stärker als es Substitution und Rekursion ermöglichen und nur für kleine Argumente lassen sich die Funktionswerte noch ohne Rekursion berechnen. Darin bestand auch die Beweisidee von Ackermann, eine Funktion zu definieren, die schneller wächst als alle primitiv rekursiven Funktionen.“ [http://www.tutego.com/java/articles/Ackermann-Funktion.html] public class Ackermann { public static int ackermann(int n, int m) { Out.println( "Ackermann(" + n + "," + m + ")" ); if (n == 0) return( m+1 ); if (m == 0) return( ackermann(n-1,1) ); return ackermann( n-1, ackermann( n, m-1 ) ); } public static void main (String[] args) { Out.print("Geben Sie zwei natuerliche Zahlen ein: "); int n = In.readInt(); int m = In.readInt(); int erg = ackermann(n,m); Out.println( "Resultat: Ackermann("+n+","+m+")=" + erg); } } Technische Informatik für Ingenieure Seite 2(4) Für große Zahlen übersteigt die Funktion aber schnell alle Berechnungsmöglichkeiten. Zum Vergleich: Die Ackermannfunktion berechnet beim Zahlenpaar (4,7) etwa 0,2*10^20. Unter der hier verwendeten Definition der Ackermannfunktion ergibt sich folgende Tabelle: A(x,y) y=0 y=1 y=2 y=3 y=4 y=5 x=0 1 2 3 4 5 6 x=1 2 3 4 5 6 7 x=2 3 5 7 9 11 13 x=3 5 13 29 61 125 253 x=4 13 65533 265536-3 2265536-3 a(3,a(4,4)) x=5 65533 a(4,65533) a(4, a(4,65533)) a(4,a(5,2)) a(3, 2265536-3) a(4,a(5,3)) b) Geben Sie die Schritte der Rekursion für den Aufruf ackermann(1,2) an. ackermann(1,2) 4 ackermann( 0, ackermann(1,1) ) 3 ackermann( 0, ackermann(1,0) ) 2 ackermann(0,1) a(4,a(5,4)) Technische Informatik für Ingenieure Aufgabe 2: Seite 3(4) (Rekursion) Schreiben Sie ein Programm, das zwei natürliche Zahlen n und k einließt und rekursiv den Binomialkoeffizienten „n über k” gemäß der folgenden Definition berechnet und ausgibt: Kleine Illustration: Der Binomialkoeffizient gibt die Anzahl der Möglichkeiten an, die sich ergeben, wenn man aus einer Menge von n Elementen k Elemente ohne Zurücklegen herausnimmt. Der Binomialkoeffizient aus 49 „über“ 6 beispielsweise ergibt 13 983 816. Die Wahrscheinlichkeit beim Lotto 6 Richtige zu haben ist also 1 : 13 983 816. Der Binomialkoeffizient von 4 über 3 berechnet sich gemäß der obigen Formel folgendermaßen: Lösung: public class Binom { static int binom(int n, int k) { if ( k == 0 || k == n ) return 1; else return binom(n-1, k-1) + binom(n-1,k); } public static void main(String[] args) { Out.print("Bitte geben Sie eine natürliche Zahl ein: "); int n = In.readInt(); Out.print("Bitte geben Sie eine natürliche Zahl ein: "); int k = In.readInt(); if ( n < 0 || k < 0 ) { Out.print("Es sind nur natürliche Zahlen erlaubt! "); return; } int erg = binom(n,k); Out.print(n + " über " + k + " beträgt: " + erg); } } Technische Informatik für Ingenieure Aufgabe 3: Seite 4(4) (Rekursion) Der größte gemeinsame Teiler zweier natürlicher Zahlen n und m, kann nach folgender Definition rekursiv berechnet werden: Schreiben Sie ein Programm, das zwei natürliche Zahlen n und m einließt und rekursiv den größten gemeinsamen Teiler berechnet und ausgibt. Lösung: public class GGT { public static void main(String[] args) { Out.print("Bitte geben Sie eine natürliche Zahl ein: "); int n = In.readInt(); Out.print("Bitte geben Sie eine natürliche Zahl ein: "); int m = In.readInt(); if ( n < 0 || m < 0 ) { Out.print("Es sind nur natürliche Zahlen erlaubt! "); return; } int erg = ggT(n,m); Out.print("ggT von " + n + " und " + m + " ist: " + erg); } public static int ggT(int n, int m) { if ( n == m ) return n; else if ( n > m ) return ggT( n-m, m ); else return ggT( n, m-n ); } }