28.12.2013 Aufrufe

Übungen zu Algorithmen - Universität Osnabrück

Übungen zu Algorithmen - Universität Osnabrück

Übungen zu Algorithmen - Universität Osnabrück

MEHR ANZEIGEN
WENIGER ANZEIGEN

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

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!