Übungen zu Algorithmen - Universität Osnabrück
Übungen zu Algorithmen - Universität Osnabrück
Übungen zu Algorithmen - Universität Osnabrück
Erfolgreiche ePaper selbst erstellen
Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.
Institut für Informatik <strong>Universität</strong> <strong>Osnabrück</strong>, 26.11.2013<br />
Prof. Dr. Oliver Vornberger<br />
http://www-lehre.inf.uos.de/~ainf<br />
Nils Haldenwang, M.Sc.<br />
Testat bis 04.12.2013, 14:00 Uhr<br />
Nicolas Neubauer, M.Sc.<br />
<strong>Übungen</strong> <strong>zu</strong> <strong>Algorithmen</strong><br />
Wintersemester 2013/2014<br />
Blatt 6: Komplexität und Backtracking<br />
Aufgabe 6.1: Fragen (30 Punkte)<br />
Beantworten Sie Ihrer Tutorin bzw. Ihrem Tutor Fragen <strong>zu</strong>r Vorlesung.<br />
Aufgabe 6.2: Laufzeiten (15 Punkte)<br />
Betrachten Sie die im Anhang befindliche Java-Klasse Laufzeiten.java<br />
a) Was berechnen die einzelnen Methoden in Abhängigkeit von n (n ≥ 1) ?<br />
b) Welche asymptotische Laufzeit haben die einzelnen Methoden, ausgedrückt in der O-Notation,<br />
in Abhängigkeit von n? Begründen Sie Ihre Antworten!<br />
Musterlösung:<br />
/************************ LaufzeitenLoes.java *************************/<br />
import AlgoTools.IO;<br />
/**<br />
* Komplexitaet von Methoden in der O-Notation<br />
*/<br />
public class LaufzeitenLoes {<br />
/** Berechnet den ganzzahligen Logarithmus dualis von n. O(log_2(n)) */<br />
public static int a(int n) {<br />
int z = 0;<br />
}<br />
while (n > 1) {<br />
n /= 2;<br />
z++;<br />
}<br />
return z;<br />
/** Berechnet das Quadrat von n. Laufzeit O(n) */
public static int b(int n) {<br />
int i = 0;<br />
int b = 1;<br />
}<br />
while (++i < n) {<br />
b = b + 2 * i + 1;<br />
}<br />
return b;<br />
/** Berechnet die ganzzahlige Quadratwurzel von n. O(Wurzel(n)) */<br />
public static int c(int n) {<br />
int t = 1, z = 0;<br />
}<br />
while (n > 0) {<br />
n -= t;<br />
t += 2;<br />
z++;<br />
}<br />
return z;<br />
/** Berechnet Wurzel vom Logarithmus von n. O(log2(n))<br />
* <strong>zu</strong>naechst Logarithmus, dann daraus die Wurzel<br />
* log2(n)+Wurzel(log2(n)) O(log2(n))<br />
*/<br />
public static int d(int n) {<br />
return c(a(n));<br />
}<br />
/** berechnet Quadrat von Wurzel n.<br />
* <strong>zu</strong>naechst Wurzel, dann davon das Quadrat<br />
* Wurzel(n)+Wurzel(n)=2*Wurzel(n) --> O(Wurzel(n))<br />
*/<br />
public static int e(int n) {<br />
return b(c(n));<br />
}<br />
/** berechnet Wurzel vom Quadrat von n. O(n)<br />
* <strong>zu</strong>naechst Quadrat, dann daraus die Wurzel<br />
* n+Wurzel(n^2)=n+n --> O(n)<br />
*/<br />
public static int f(int n) {<br />
return c(b(n));<br />
}<br />
/** berechnet Wurzel n mal Wurzel n. O(n)<br />
2
* Wurzel(n)*Wurzel(n)*2=2*n --> O(n)<br />
*/<br />
public static int g(int n) {<br />
int z = 0;<br />
for (int i = 1; i
}<br />
}<br />
IO.print("a(" + n + ") =");<br />
IO.println(a(n), 8);<br />
IO.print("c(" + n + ") =");<br />
IO.println(c(n), 8);<br />
IO.print("b(" + n + ") =");<br />
IO.println(b(n), 8);<br />
IO.print("d(" + n + ") =");<br />
IO.println(d(n), 8);<br />
IO.print("e(" + n + ") =");<br />
IO.println(e(n), 8);<br />
IO.print("f(" + n + ") =");<br />
IO.println(f(n), 8);<br />
IO.print("g(" + n + ") =");<br />
IO.println(g(n), 8);<br />
IO.print("h(" + n + ") =");<br />
IO.println(h(n), 8);<br />
IO.print("i(" + n + ") =");<br />
IO.println(i(n), 8);<br />
IO.print("j(" + n + ") =");<br />
IO.println(j(n), 8);<br />
Aufgabe 6.3: Zusicherung (15 Punkte)<br />
Betrachten Sie die im Anhang befindliche Java-Klasse Zusicherung.java<br />
a) Welches Problem löst die Methode main dieser Klasse?<br />
b) Zeigen Sie die partielle Korrektheit mit Hilfe von Zusicherungen, die Sie als Kommentare in<br />
den Programmtext einfügen (siehe Programmtext).<br />
c) Zeigen Sie die totale Korrektheit durch den Nachweis der Terminierung.<br />
d) Geben Sie die asymptotische Laufzeit in O-Notation an.<br />
Musterlösung:<br />
4
************************** ZusicherungLoes.java ***************************/<br />
import AlgoTools.IO;<br />
/**<br />
* Beweis der Korrektheit mit Hilfe der Zusicherungsmethode<br />
*<br />
* a) Berechnet das Quadrat der Eingabezahl n.<br />
* b) Zeigen Sie die partielle Korrektheit mit Hilfe von Zusicherungen im<br />
* Programmtext.<br />
* c) Zeigen Sie die totale Korrektheit durch den Nachweis der Terminierung.<br />
* d) Geben Sie die asymptotische Laufzeit in O-Notation an.<br />
*<br />
*/<br />
public class ZusicherungLoes {<br />
public static void main(String[] args) {<br />
int i = 0, h = 1, z = 0, n;<br />
/* i = 0 und h = 1 und z = 0 */<br />
do {<br />
n = IO.readInt("n= ");<br />
} while (n < 0);<br />
/* n >= 0 und i = 0 und h = 1 und z = 0 */<br />
/* z = i * i und h = 2 * i + 1 und i
}<br />
}<br />
/* z = i * i und h = 2 * i + 1 und i = 0 unveraendert bleibt und i <strong>zu</strong> Beginn<br />
*
Sei f (m) ≤ m · log(m) + 1 für m ≤ n bewiesen.<br />
Schritt:<br />
Zu zeigen: f (n + 1) ≤ (n + 1) · log(n + 1) + 1<br />
( ) (n + 1)<br />
f (n + 1) = 3 · f<br />
3<br />
( ( ) )<br />
(n + 1) (n + 1)<br />
≤ 3 · · log + 1<br />
3<br />
3<br />
= (n + 1) · log(n + 1) − (n + 1) · log(3) + 3<br />
≤ (n + 1) · log(n + 1)<br />
Schluss:<br />
Damit gilt die Behauptung für alle n ∈ N.<br />
Aufgabe 6.5: Sudoku (30 Punkte)<br />
Sudoku ist ein Zahlenpuzzle. Es besteht aus einem Quadrat, das in 3 × 3 Unterquadrate bzw. Blöcke<br />
eingeteilt ist. Jedes Unterquadrat ist wieder in 3 × 3 Felder eingeteilt, sodass das Gesamtquadrat also<br />
81 Felder (= 9 · 9 Felder) bzw. 9 Reihen und 9 Spalten mit je 9 Feldern besitzt.<br />
In einige dieser Felder sind schon <strong>zu</strong> Beginn Ziffern (1 bis 9) eingetragen. Das Puzzle muss nun so<br />
vervollständigt werden, dass<br />
• in jeder Zeile,<br />
• in jeder Spalte und<br />
• in jedem der neun Blöcke jede Ziffer von 1 bis 9 genau einmal auftritt.<br />
Betrachten Sie die im Anhang befindliche Java-Klasse Sudoku.java. Vervollständigen Sie diese<br />
Klasse <strong>zu</strong> einem Javaprogramm, welches ein eingelesenes Sudoku löst. Implementieren Sie da<strong>zu</strong> die<br />
rekursive Methode fuelleFeld, die <strong>zu</strong>nächst ein freies Feld sucht und nacheinander die Zahlen von<br />
1 bis 9 einsetzt und auf Korrektheit prüft. Lagern Sie die Prüfung in die Methode gueltigeZahl<br />
aus. Ist eine korrekte Zahl gefunden wird diese eingetragen und es erfolgt ein rekursiver Aufruf, um<br />
das nächste freie Feld <strong>zu</strong> bearbeiten.<br />
Kann in einem freien Feld keine korrekte Zahl positioniert werden, ist der eingeschlagene Lösungsweg<br />
falsch. In diesem Fall wird dieses Feld als frei markiert (Wert 0 eintragen), der aktuelle Rekursionsaufruf<br />
beendet und es wird beim vorherigen Schritt weiter getestet.<br />
Verwenden Sie die bereits vorgegebene Strukturierung des Programmes in Methoden.<br />
Ein korrektes Sudoku besitzt immer genau eine Lösung. Wenn alle Felder korrekt belegt sind ist das<br />
Sudoku also gelöst.<br />
7
Hinweis: Die beschriebene Vorgehensweise wird Backtracking genannt. Backtracking ist eine Programmierstrategie,<br />
die nach dem Prinzip Trial and Error (Versuch und Irrtum) vorgeht. Man wählt<br />
einen von mehreren Lösungswegen aus und verfolgt ihn über weitere Entscheidungen so lange, bis<br />
die Lösung gefunden worden ist oder der Weg sich als definitiv falsch herausgestellt hat. Ist dies<br />
der Fall, kehrt man <strong>zu</strong>r letzten Entscheidung <strong>zu</strong>rück und wählt einen anderen Weg. Auf diese Weise<br />
werden von Entscheidung <strong>zu</strong> Entscheidung so viele Wege ausprobiert, bis einer ans Ziel führt.<br />
Im Anhang finden Sie zwei Beispielsudokus, die Sie mit dem Pipe-Operator Ihrem Programm wie<br />
folgt übergeben können:<br />
java Sudoku < test_sudoku_1.txt<br />
Musterlösung:<br />
import AlgoTools.IO;<br />
/**<br />
* SudokuSolver, der die Loesung eines Sudokus mittels Backtracking ermittelt.<br />
*/<br />
public class SudokuSolver {<br />
/**<br />
* rekursive Methode, die das naechste freie Kaestchen im Sudoku-Feld sucht<br />
* und dort alle erlaubten Zahlen einsetzt und sich dann selbst aufruft.<br />
*<br />
* @param sudoku<br />
* Feld, welches gefuellt werden soll<br />
* @param spalte<br />
* Spaltennumer im Array<br />
* @param zeile<br />
* Zeilennummer im Array<br />
* @return ob Loesung gefunden oder nicht<br />
*/<br />
private static boolean fuelleFeld(int[][] sudoku, int spalte, int zeile) {<br />
/*<br />
* freies Feld suchen<br />
*/<br />
while (zeile < 9 && sudoku[zeile][spalte] != 0) {<br />
spalte++;<br />
if (spalte > 8) {<br />
zeile++;<br />
spalte = 0;<br />
}<br />
}<br />
/*<br />
* Rekursionsverankerung: Am Ende angekommen, fertig!<br />
8
}<br />
*/<br />
if (zeile > 8) {<br />
return true;<br />
} else {<br />
/*<br />
* Fuer das aktuelle Feld alle Zahlen einmal durchtesten<br />
*/<br />
for (int number = 1; number
if (sudoku[zeile][x] == zahl) {<br />
return false;<br />
}<br />
}<br />
/*<br />
* teste Spalte<br />
*/<br />
for (int y = 0; y < 9; y++) {<br />
if (sudoku[y][spalte] == zahl) {<br />
return false;<br />
}<br />
}<br />
/*<br />
* Teste Kaestchen. Da<strong>zu</strong> obere linke Ecke des aktuellen Kaestchens<br />
* berechnen<br />
*/<br />
for (int y = (zeile / 3) * 3; y < (zeile / 3) * 3 + 3; y++) {<br />
for (int x = (spalte / 3) * 3; x < (spalte / 3) * 3 + 3; x++) {<br />
if (sudoku[y][x] == zahl) {<br />
return false;<br />
}<br />
}<br />
}<br />
}<br />
return true;<br />
/**<br />
* Gibt das Sudoku-Feld ordentlich formatiert aus<br />
*<br />
* @param field<br />
* Feld das ausgegeben werden soll<br />
*/<br />
private static void druckeSudoku(int[][] field) {<br />
for (int yPos = 0; yPos < 9; yPos++) {<br />
if (yPos != 0 && yPos % 3 == 0)<br />
IO.println("------+-------+------ ");<br />
for (int xPos = 0; xPos < 9; xPos++) {<br />
if (xPos != 0 && xPos % 3 == 0)<br />
IO.print("| ");<br />
IO.print(field[yPos][xPos] + " ");<br />
}<br />
IO.println();<br />
}<br />
}<br />
public static void main(String[] argv) {<br />
10
}<br />
}<br />
// Sudoku-Feld als<br />
// zweidimensionales Array<br />
int[][] sudoku;<br />
sudoku = new int[9][];<br />
IO.println("Bitte geben Sie die Zeilen des <strong>zu</strong> loesenden Sudokus ein");<br />
int[] eingabe;<br />
// Einlesen des Sudokus<br />
for (int i = 1; i < 10; i++) {<br />
do {<br />
eingabe = IO.readInts("Zeile " + i + ": ");<br />
} while (eingabe.length != 9);<br />
sudoku[i - 1] = eingabe;<br />
}<br />
// Feld einmal ausgeben<br />
druckeSudoku(sudoku);<br />
//Loesung ermitteln, oben links anfangen<br />
if(fuelleFeld(sudoku, 0, 0)){<br />
IO.println("Die Loesung: ");<br />
druckeSudoku(sudoku);<br />
}<br />
11