Sie sind auf Seite 1von 14

Praktikum: Grundlagen der Programmierung

Prof. Dr. Alexander Pretschner, J. Kranz, G. Hagerer WS 2018/19


Übungsblatt 8 Abgabefrist: s. Moodle
Σ Punkte: 20 | Σ Bonuspunkte: 3

Aufgabe 8.1 (P) Vererbung


Erstellen Sie eine Klasse Rechteck als Klasse für alle Rechtecke mit Attributen für Länge
und Höhe. Legen Sie eine Version der Methode void resize(int dx, int dy), welche
die Länge und Höhe des Rechtecks modifiziert. Erstellen Sie eine Klasse Quadrat, welche
von Rechteck erbt. Welche semantischen Probleme enstehen für Quadrat.resize() und
was sind mögliche Lösungen?

Aufgabe 8.2 (P) Generics + Polymorphie = ♥


Betrachten Sie den folgenden Code:

1 public class Poly {


2 static class A {
3 protected A a;
4 public A(A a) { this.a = a; }
5 public void f(A a) { System.out.println("A.f(A)"); this.a.f(a); }
6 public void f(B<A> a) { System.out.println("A.f(B)"); a.f(this.a); }
7 }
8

9 static class B<T> extends A {


10 public T t;
11 public B(Object o) { super(null); }
12 public B() { super(null); a = new C<B<T>>(this); }
13 public void f(B<A> a) { System.out.println("B.f(B<A>)"); }
14 }
15

16 static class C<T extends A> extends B<T> {


17 public T t;
18 public C(T t) { super(null); a = this; this.t = t; }
19 public void f(A a) { System.out.println("C.f(A)"); }
20 public void f(B<A> a) { System.out.println("C.f(B<A>)"); a.f(t); }
21 }
22

23 public static void main(String[] args) {


24 B<Integer> b1 = new B<Integer>();
25 B<A> b2 = new B<A>();
26 A a = new A(b1);
27 C<A> c = new C<A>(a);
28

29 a.f(b1); // Aufruf 1
30 a.f(b2); // Aufruf 2
31

1
32 B<C<A>> b3 = new B<C<A>>();
33 B<A> b4;
34

35 (b2 = b3).f(a); // Aufruf 3


36 (b4 = c).t.f(a); // Aufruf 4
37 }
38 }

Geben Sie für jeden der markierten Aufrufe die Ausgabe an. Gehen Sie davon aus, dass
nur ein Aufruf im Programm vorhanden ist; die anderen seien jeweils auskommentiert.
Es kann jeweils auch ein Compiler- oder Laufzeitfehler auftreten. Geben Sie bei einem
Laufzeitfehler an, wo genau bzw. wieso dieser auftritt. Begründen Sie bei Aufruf 3 kurz
das Verhalten des Java-Compilers anhand eines Beispiels.

Aufgabe 8.3 (P) Exceptions


Betrachten Sie folgenden Ausschnitt aus der Klassenhierarchie von Exceptions in Java
und folgende Java-Implementierung der Klasse ExceptionTest:

import java.io.*;
public class ExceptionTest {
public static void main (String[] args) {
try {
Exception
// ...
}
catch (EOFException e) {
System.out.println("EOFException");
IOException }
catch (IOException e) {
System.out.println("IOException");
}
EOFException FileNotFoundException catch (Exception e) {
System.out.println("Exception");
}
System.out.println("ENDE");
}
}
1. An der durch „...“ gekennzeichneten Stelle im try-Block stehe ein Programm-
stück, durch das Exceptions vom Typen EOFException, IOException oder aber
FileNotFoundException geworfen werden können.
Was wird bei Ausführung der main-Methode ausgegeben, falls dabei im try-Block
i) als erstes eine Ausnahme vom Typ EOFException geworfen wird,
ii) als erstes eine Ausnahme vom Typ FileNotFoundException geworfen wird,
oder
iii) gar keine Ausnahme geworfen wird?
2. Was wird bei Ausführung der main-Methode ausgegeben, falls dabei im try-Block
als erste Ausnahme eine Division durch 0 auftritt?

Aufgabe 8.4 (P) Rekursion: Quersumme


Schreiben Sie ein Programm Quersumme.java, das die Quersumme 1 einer einzugebenden
1
Sehe http://de.wikipedia.org/wiki/Quersumme

2
Zahl zahl
1. iterativ in der Funktion static int quersummeIt(int zahl)
2. rekursiv in der Funktion static int quersummeRec(int zahl)
berechnet und anschließend ausgibt.
Hinweise:

• Der Modulo-Operator ist bei dieser Aufgabe sehr hilfreich.

• Zur Eingabe der Zahl können Sie Terminal.readInt() verwenden.

Aufgabe 8.5 (P) Rekursion: Leibniz-Reihe


Gegeben sei folgende Rekursionsgleichung zur Annäherung von π = 3, 1415 . . .

 4
+ fn−1 : falls (n mod 2) ≡ 0
fn = 2∗n+1
− 4 + fn−1 : falls (n mod 2) ≡ 1
2∗n+1

wobei f0 = 4.

• Schreiben Sie ein Programm, das nach Eingabe des Index n die Annäherung fn für
Pi rekursiv berechnet und ausgibt.

• Schreiben Sie ein Programm, das abhänig von einem Schwellwert  die Anzahl Schrit-
te n berechnet, so dass:

|π 0 − π| < 

wo π 0 ist die berechnete Annäherung von π. Führen Sie Ihre Implementierung einmal
mit  = 0.01 und einmal mit  = 0.001 aus.

• Nach wievielen Schritten ist die Annäherung π 0 auf die ersten sieben Nachkommas-
tellen genau?

Aufgabe 8.6 (P) Rekursion: Binomialkoeffizient


 
Der Binomialkoeffizient nk gibt an, auf wieviele verschiedene Arten man k Elemente aus
einer Menge von n verschiedenen Elementen auswählen kann (ohne Zurücklegen und ohne
Beachtung der Reihenfolge).

n−1 n−1 0
! ! ! ! ! !
n n n
Für 0 ≤ k ≤ n gilt: = + ; = 1; = =1
k k−1 k 0 n 0

Schreiben Sie ein Programm Binom.java.


 
1. Es soll eine Funktion static long binom(int n, int k) enthalten, welche n
k
rekursiv berechnet und das Ergebnis zurückgibt.

3
2. Erweitern Sie Ihr Programm um eine Funktion static double sechsRichtige(),
welche die Wahrscheinlichkeit zurückgibt, 6 Zahlen aus der Menge {1...49} richtig
zu tippen.

Die Hausaufgabenabgabe erfolgt über Moodle. Bitte geben Sie Ihren Code als UTF8-
kodierte (ohne BOM) Textdatei(en) mit der Dateiendung .java ab. Geben Sie keine
Projektdateien Ihrer Entwicklungsumgebung ab. Geben Sie keinen kompilierten Code
ab (.class-Dateien). Sie dürfen Ihren Code als Archivdatei abgeben, dabei allerdings
ausschließlich das ZIP-Format nutzen. Achten Sie darauf, dass Ihr Code kompiliert.
Hausaufgaben, die sich nicht im vorgegebenen Format befinden, werden nur mit Punkt-
abzug oder gar nicht bewertet.
Aufgabe 8.7 (H) Mathematische Ausdrücke [3 Punkte]
In dieser Aufgabe sollen auf der Grundlage der in der Vorlesung behandelten Klassen-
hierarchie mathematische Ausdrücke in Form von Ausdrucksbäumen repräsentiert und
berechnet werden. Diese mathematischen Ausdrücke sollen zusätzlich zu den bereits im-
plementierten arithmetischen Ausdrücken (binäres +, −, ∗, /; unäres −) nun auch logische
Operatoren (binäres und (∧), oder (∨); unäre Negation (¬)) umfassen. Ausgangspunkt
hierzu ist die abstrakte Klasse Expression, die einen (abstrakten) mathematischen Aus-
druck repräsentiert, der sowohl arithmetischer als auch logischer Natur sein kann. Misch-
formen zwischen arithmetischen und logischen Operatoren sind zunächst nicht erlaubt (s.
nächste Aufgabe). Zusätzlich zur abstrakten Oberklasse sollen zwei abstrakte Unterklas-
sen implementiert werden, die binäre Operationen (Klasse BinOp) und unäre Operationen
(Klasse UnOp) repräsentieren. Außerdem soll es eine konkrete Unterklasse für Konstanten
geben (Klasse Const), die sowohl Integers als auch Booleans repräsentieren können
muss.
Die abstrakte Oberklasse Expression soll lediglich über die abstrakte Methode evaluate()
und die (abstrakte oder nicht abstrakte) Methode toString() verfügen, die jeweils erst in
den Unterklassen eine Implementierung erhalten. Die Methode evaluate() wertet einen
(arithmetischen oder logischen) Ausdruck aus und gibt das Ergebnis zurück2 .

1. Machen Sie sich klar, dass in dieser Aufgabe gleichzeitig zwei Arten von Expressions
behandelt werden müssen, nämlich arithmetische (mit Integers parametriert) und
logische (mit Booleans parametriert). Benutzen Sie deshalb bei der Definition von
Expression sowie deren Unterklassen einen entsprechenden Typparameter.
Definieren Sie dann die drei genannten abstrakten und eine konkrete Klasse und
implementieren Sie die jeweiligen Konstruktoren. Beachten Sie, dass Konstanten nun
Integers oder Booleans sein können. Beachten Sie auch, dass abstrakte Klassen
durchaus über Konstruktoren verfügen können und dass, wenn Konstruktoren in
einer abstrakten Klasse C explizit implementiert sind, Sie in den Unterklassen von
C ggf. explizite Konstruktoren definieren müssen, die den Konstruktor von C explizit
als erste Zeile aufrufen.
2. Spezialisieren Sie dann für jeden arithmetischen (+, -, *, /) und jeden logischen (∧,
∨, ¬) Ausdruck die abstrakten Klassen BinOp und UnOp in jeweils eine konkrete
Unterklasse. Überlegen Sie sich, wie Sie die Konstruktoren dieser Klassen möglichst
einfach halten können.
2
In einer alten Version wurde gefordert, das Ergabnis auf der Konsole auszugeben. Dies macht keinen
Sinn, gibt aber auch keinen Abzug.

4
3. Für die Implementierung der evaluate()-Methode in den Unterklassen der Klasse
Expression gibt es mindestens zwei Möglichkeiten.
• Die einfache Möglichkeit besteht darin, die evaluate()-Methode in jeder kon-
kreten Unterklasse von Expression zu implementieren, also in Const sowie
den konkreten Unterklassen von UnOp und BinOp.
• Eine andere Möglichkeit besteht darin, evaluate() in den abstrakten Klas-
sen UnOp und BinOp unter Zuhilfenahme einer neuen abstrakten Funktion fun
zu implementieren, die erst in den Unterklassen von UnOp und BinOp imple-
mentiert wird. fun(o1,o2) könnte dann für arithmetische binäre Ausdrücke
dahingehend definiert werden, dass o1 + o2, o1 - o2, o1 * o2 oder o1/o2
zurückgegeben wird. Für binäre logische Ausdrücke sollte o1 && o2 bzw. o1
|| o2 berechnet werden. Für die arithmetische Negation sollte eine Methode
fun(o) entsprechend -o zurückgeben und für die logische Negation !o. Für
Const müssen Sie natürlich eine explizite Implementierung angeben.

Implementieren Sie eine der beiden Möglichkeiten, sodass Sie danach mit der Metho-
de evaluate() beliebige arithmetische oder logische Ausdrücke berechnen können.

Beispiel:
(new MulOp<Integer>(new Const<Integer>(3), new AddOp<Integer>(new
Const<Integer>(1), new Const<Integer>(2)))).evaluate()
soll 9 zurückgeben und
(new AndOp<Boolean>(new Const<Boolean>(true),new OrOp<Boolean>(new
Const<Boolean>(false), new Const<Boolean>(true)))).evaluate()
soll true zurückgeben.
4. Implementieren Sie die toString()-Methode, so dass alle mathematischen Aus-
drücke richtig geklammert ausgegeben werden.
5. Betrachten Sie das in Abbildung 1 dargestellte Beispiel und schreiben Sie eine
main-Methode in einer Klasse ATest, die mindestens drei weitere Aufrufbäume in-
stanziiert. Die Aufrufbäume sollen jeweils aus mindestens fünf Objekten vom Typ
Expression bestehen. Testen Sie auf diesen Aufrufbäumen die Methoden evaluate()
und toString().

*" &&"

3" +" 1"

1" 2" 0"

Abbildung 1: Beispiel für einen mathematischen Ausdruck in Form eines Ausdrucksbau-


mes.

5
Dateien zur Abgabe:
Expression.java, Const.java, BinOp.java, UnOp.java, AddOp.java, MulOp.java,
DivOp.java, SubOp.java, AndOp.java, NegOp.java, OrOp.java, ATest.java

Aufgabe 8.8 (H) Relationale Operatoren [3 Punkte]


Erweitern Sie die Implementierung dahingehend, dass auch relationale Operatoren (<, >
, ==) behandelt werden können. Machen Sie sich klar, dass diese relationalen Operatoren
einen Rückgabetypen (etwa Boolean) haben, der unterschiedlich von den Argumenttypen
(etwa Integer) ist, im Gegensatz zu arithmetischen und logischen Ausdrücken, bei denen
Argument- und Rückgabetypen identisch sind (etwa Integer bzw. Boolean). Kopieren
Sie und benennen Sie alle Klassen für Ausdrücke, abstrakte und konkrete logische und
arithmetische Operatoren um, indem Sie ein Präfix ZA voranstellen. Implementieren Sie
dann drei zusätzliche konkrete Klassen ZAGTOp für die Implementierung von >, ZALTOp
für < und ZAEQOp für == in den Dateien ZAGTOp.java, ZALTOp.java und ZAEQOp.java.
Betrachten Sie das in Abbildung 2 dargestellte Beispiel und schreiben Sie eine main-
Methode in einer Klasse ZATest, die mindestens drei weitere Aufrufbäume instanziert.
Die Aufrufbäume sollen jeweils aus mindestens fünf Objekten vom Typ ZAExpression be-
stehen. Testen Sie auf diesen Aufrufbäumen die Methoden evaluate() und toString().

Beispiel:

1 ZAExpression<Boolean> exp = new ZAEQOp<Integer, Boolean>(new


,→ ZAMulOp<Integer, Integer>(new ZAConst<Integer>(3),
2 new ZAAddOp<Integer, Integer>(new ZAConst<Integer>(1), new
,→ ZAConst<Integer>(2))), new ZAConst<Integer>(9));
3 System.out.println(exp.evaluate());

soll true ausgeben.

=="

*" 9"

3" +"

1" 2"

Abbildung 2: Beispiel für einen mathematischen Ausdruck mit relationalen Operator in


Form eines Ausdrucksbaumes.

Dateien zur Abgabe:


ZAExpression.java, ZAConst.java, ZABinOp.java, ZAUnOp.java, ZAAddOp.java,

6
ZAMulOp.java, ZADivOp.java, ZASubOp.java, ZAAndOp.java, ZANegOp.java,
ZAOrOp.java, ZAGTOp.java, ZALTOp.java, ZAEQOp.java, ZATest.java.

Aufgabe 8.9 (H) PageRank Rekursiv [6 Punkte]


Ziel dieser Aufgabe ist die Implementierung des PageRank Algorithmus, der durch Google
allgemeine Bekanntheit erlangt hat. PageRank bewertet dabei nicht die Ähnlichkeit einer
Suchanfrage zu den erfassten Dokumenten. Stattdessen ist PageRank ein Verfahren, mit
dem die verschiedenen Seiten (bzw. Dokumente) an Hand ihrer Struktur bewertet bzw.
gewichtet werden. Hierzu wird jedem Dokument ein Gewicht zugeteilt, das sich aus der
Linkstruktur zwischen den Dokumenten berechnet. PageRank analysiert also alle ein- und
ausgehenden Links der Dokumente und gewichtet die einzelnen Dokumente auf Grundlage
dieser Links. Das Grundprinzip dabei lautet: je mehr Links auf ein Dokument verweisen,
desto höher ist das Gewicht dieses Dokumentes. Je höher dabei das Gewicht des verwei-
senden Dokumentes ist, desto größer ist auch das Gewicht der Seite auf die verwiesen
wird. Dokumente auf die von vielen anderen Dokumenten verwiesen wird haben somit ein
größeres Gewicht und damit eine höhere Popularität.
Implementieren Sie daher die rekursive Methode

public double pageRankRec(int[][] C, int i, double d, int recDepth)

in der Klasse LinkedDocumentCollection, die den PageRank-Wert des LinkedDocuments


an Stelle i der LinkedDocumentCollection annähert und zurückgibt. Bezüglich der Para-
meter ist C die Übergangsmatrix, i der Index des Dokuments, für welches der PageRank-
Wert zu berechnen ist, d ein Dämpfungsfaktor und recDepth die Rekursionstiefe des
aktuellen Methodenaufrufs. Die Parameter werden später genauer erläutert. Nähern Sie
die PageRank Werte gemäß den folgenden Ausführungen so lange an die korrekten Page-
Rank Werte an, bis diese auf 10−6 mit den korrekten Werten übereinstimmen. Finden Sie
dafür eine geeignete maximale Rekursionstiefe, die als Abbruchbedingung dienen soll und
gleichzeitig der Anforderung hinsichtlich Genauigkeit Rechnung trägt. Beachten Sie au-
ßerdem, dass das Programm bis zur Terminierung auf handelsüblichen Computern nicht
länger als eine Minute zur Berechnung dauern darf. Außerdem soll die Methode von außen
über eine einfachere Methode

public double[] pageRankRec(double d)

aufgerufen werden, welche so zu implementieren ist, dass sie die PageRank-Werte aller
Dokumente berechnet und dabei die zu übergebenen Parameter entweder weiterreicht
oder, sofern notwendig, sinnvoll initialisiert und jeweils übergibt.
Die folgenden Ausführungen zu PageRank beruhen auf den Ausführungen der Webseite
"PageRank Algorithm - The Mathematics of Google Search"3 .
Das Prinzip des PageRank-Algorithmus wird im Folgenden an Hand eines Beispiels er-
klärt. Hierfür gehen wir davon aus, dass es vier Dokumente A, B, C und D gibt, deren
Fließtext die folgenden Links enthält:
A B C D
link:B link:C link:A link:C link:D link:D link:C
3
http://www.math.cornell.edu/∼mec/Winter2009/RalucaRemus/Lecture3/lecture3.html

7
Hiermit ergibt sich die Linkstruktur aus Abbildung 3a.

A B

C D
(a) Linkstruktur

Abbildung 3

Jedes Dokument kann also auf andere Dokumente verweisen, woraus sich die Verlinkungs-
matrix C wie folgt ergibt:
A B C D
0 1 0 0 A
 
 1 0 0 0  B
C=
 
1 1 0 1 C
 
 
0 1 1 0 D

Jede Matrixzelle ci,j enthält hier die Information, ob eine Verlinkung von Dokument j auf
Dokument i vorliegt (1) oder nicht (0). Insofern enthält eine Spalte die ausgehenden und
eine Zeile die eingehenden Verlinkungen pro Dokument.
Beachten Sie: Verweist ein Dokument auf kein anderes Dokument (d.h. das Dokument
hat 0 ausgehende Links), so wird dieses Dokument so behandelt als ob es Links zu allen
anderen Dokumenten hätte.
Zusätzlich wird ein sogenannter Dämpfungsfaktor d, ein Wert zwischen 0 und 1, einge-
führt. Dieser ist notwendig, da nicht notwendigerweise alle Dokumente über Links zusam-
menhängen müssen. In diesem Fall sorgt der Dämpfungsfaktor dafür, dass tatsächlich alle
Dokumente einen PageRank Wert > 0 erhalten. Der Dämpfungsfaktor hat üblicherweise
den Wert 0.85.
Setzt man die gegebenen Variablen in die rekursive Formel 4

1−d P Rj
P Ri = +d·
X
Pn−1
n j∈Li k=0 Ck,j

ein, so erhält man für den PageRank-Wert vom i-ten LinkedDocument einen neuen PageRank-
Wert P Ri . Hierbei ist Li die Menge der Indizes der Dokumente, die einen Link aufs i-te
4
https://de.wikipedia.org/wiki/PageRank

8
Dokument beinhalten. P Rj selber ist wiederum ein rekursiver Verweis auf die Formel
selbst.
Die PageRank Werte der Dokumente werden in einem Vektor P R dargestellt und zu
Beginn auf dieselben Werte ( n1 ) gesetzt, im Beispiel ist also der PageRank Vektor P R zu
Beginn
0.25
 
 0.25 
PR =  .
 
 0.25 
0.25

Aktualisiert man nun mehrfach in rekursiver Abfolge jeden Wert von PR mittels der
angegebenen Formel, so nähert sich das Ergebnis dieser Rekursion einem Vektor P R∗ an.
Dieser Vektor P R∗ entspricht den PageRank-Werten der Dokumente.
Im Beispiel entspricht dieser Ergebnisvektor

0.0547
 
 0.0607 
P R∗ =  .
 
 0.4485 
0.4359

Das bedeutet dass A ein PageRank-Gewicht von 5, 47%, B von 6, 07%, C von 44, 85% und
D von 43, 56% hat.
Auf der Webseite "PageRank Explained with Javascript"5 können Sie spielerisch eine
Linkstruktur zwischen Dokumenten erstellen und die PageRank Werte der Dokumente
berechnen lassen. N.B. Ziel dieser Aufgabe ist P R∗ zu annähren bis für alle i ∈ {1, ..., n}
die folgende Bedingung erfüllt ist:

|P Ri − P Ri0 | ≤ 10−6 .

Dateien zur Abgabe: Author.java, Date.java, DocumentCollectionCell.java,


DocumentCollection.java, Document.java, LinkedDocumentCollection.java,
LinkedDocument.java, Review.java, WordCount.java, WordCountsArray.java

Aufgabe 8.10 (H) PageRank Iterativ (Nikolaus-Nuss) [3 Bonuspunkte]


Ziel dieser Aufgabe ist die iterative Implementierung des PageRank-Algorithmus. Dies
bedeutet, dass hier der Algorithmus entgegen seiner ursprünglichen Definition nicht re-
kursiv berechnet wird. Dies ist insofern hilfreich, als dass der rekursive Algorithmus bei
großen Dokumentzahlen zu langsam konvergiert und der hier zu implementierende itera-
tive Algorithmus im Allgemeinen effizienter ist.

1. Um den PageRank der LinkedDocuments einer LinkedDocumentCollection zu be-


rechnen, werden wir später Matrizen mit Vektoren multiplizieren müssen. Kopieren
Sie daher den Code für die Methode

private static double[] multiply(double[][] matrix, double[] vector)

aus Aufgabe 5.8 (H) in die Klasse LinkedDocumentCollection.


5
https://www2.cs.duke.edu/csed/principles/pagerank/

9
2. Schreiben Sie in der Klasse LinkedDocumentCollection die Methode

public double[] pageRank(double dampingFactor),

die die PageRank Werte der LinkedDocuments der LinkedDocumentCollection an-


nähert und ein Array dieser PageRank Werte als Rückgabewert hat. Der Parameter
dampingFactor ist dabei ein Dämpfungsfaktor, der in der vorhergehenden Aufga-
be bereits eingeführt wurde. Nähern Sie die PageRank Werte gemäß den folgenden
Ausführungen so lange an die korrekten PageRank Werte an, bis diese auf  = 10−6
genau sind.
In Ergänzung zu den Erklärungen und dem Beispiel von der vorhergehenden Aufgabe
werden im Folgenden zusätzliche Formeln und Definitionen eingeführt, welche die
iterative Umsetzung unterstützen sollen.

1/3
A B
1/2

1/2 1/3 1/3

1/1
C 1/1
D
(a) Linkstruktur mit Gewichtung
in Erweiterung zu Abbildung 3a

Abbildung 4

Jedes Dokument verteilt gemäß Abbildung 4a sein Gewicht gleichmäßig auf alle von
ihm verlinkten Dokumente. So verweist A auf zwei Dokumente, die jeweils beide
mit 12 gewichtet werden. B verweist dagegen auf drei Dokumente, die jeweils mit 13
gewichtet werden. Allgemein gilt: Hat ein Dokument k ausgehende Links, so wird
jedes der k verlinkten Dokumente mit k1 gewichtet. Dies resultiert im Beispiel in den
Gewichtungen in Abbildung 4a.
Diese Gewichtungen bilden die Übergangsmatrix A:
A B C D
0 01
0 A
 
3
 1
0 0 0  B
A= 2
 
1 1
0 1 C
 
2 3
 
0 3 1
1
0 D

So besagt beispielsweise die erste Spalte der Matrix, dass Dokument A jeweils mit
Gewicht 12 auf die Dokumente B und C verweist und mit Gewicht 0 (also gar nicht)
auf die Dokumente A und D. Die dritte Spalte besagt dagegen, dass Dokument C mit
Gewicht 1 auf Dokument D verweist und sonst auf keine weitere Dokumente. Eine
Person die nach dem Zufallsprinzip einem der Verweise in Dokument A folgt, wählt
demnach mit Wahrscheinlichkeit 21 entweder den Verweis zu Dokument B oder C.

10
Beachten Sie: Verweist ein Dokument auf kein anderes Dokument (d.h. das Do-
kument hat 0 ausgehende Links), so wird dieses Dokument so behandelt als ob es
Links zu allen anderen Dokumenten hätte.
Mit dem aus der vorhergehenden Aufgabe eingeführten Dämpfungsfaktor d berech-
net sich die PageRank Matrix M als

M =d·A+ 1−d
n
· En .

Dabei ist n die Anzahl der Dokumente und En eine Matrix mit n Zeilen und n
Spalten, die an jeder Position eine 1 enthält. Der Wert der Matrix M in Zeile i und
Spalte j, Mij berechnet sich daher als

Mij = d · Aij + 1−d


n

Im Beispiel ist die PageRank Matrix

0.0375 0.3208 0.0375 0.0375


 
 0.4625 0.0375 0.0375 0.0375 
M =
 
0.4625 0.3208 0.0375 0.8875

 
0.0375 0.3208 0.8875 0.0375

Die PageRank-Werte der Dokumente werden so initialisiert und dargestellt wie in


der vorhergehenden Aufgabe bereits erwähnt. Multipliziert man nun mehrfach die
Matrix M mit sich selbst und anschließend mit dem Vektor v, d.h. M k v, so nä-
hert sich das Ergebnis dieser mehrfachen Multiplikation einem Vektor v ∗ an. Dieser
Vektor v ∗ entspricht den PageRank Werten der Dokumente. 6
Ziel dieser Aufgabe ist, so wie in der vorhergehenden Aufgabe auch, v ∗ zu annähren
bis für alle i ∈ {1, ..., n} es gilt:

|(M k+1 v)i − (M k v)i | ≤  = 10−6 .

Dateien zur Abgabe: Author.java, Date.java, DocumentCollectionCell.java,


DocumentCollection.java, Document.java, LinkedDocumentCollection.java,
LinkedDocument.java, Review.java, TestIt.java, WordCount.java,
WordCountsArray.java

Aufgabe 8.11 (H) Sortieren der Suchergebnisse [3 Punkte]


Auf den vorherigen Übungsblättern wurde die Ähnlichkeit zwischen Dokumenten und
einer Suchanfrage. Damit eine Suchmaschine gute Resultate liefert, müssen Ähnlichkeit
und PageRank sinnvoll kombiniert werden. Auf diese Weise stehen diejenigen Dokumente
in der Trefferliste oben, die sowohl eine große Ähnlichkeit mit der Suchanfrage, als auch
eine hohen PageRank Wert haben. Diese Kombination aus Ähnlichkeit zur Suchanfrage
und PageRank-Wert nennen wir "Relevanz".
Für ein LinkedDocument d, eine Suchanfrage q und eine LinkedDocumentCollection dc
(d ∈ dc) sei die Ähnlichkeit zwischen d und q gegeben durch similarity(d, q, dc) (vgl.
6
Die Matrix M k drückt die Wahrscheinlichkeit aus, mit der nach k Schritten einem bestimmten Verweis
in einem bestimmten Dokument gefolgt wird. Dies werden Sie noch ausführlich in anderen Vorlesungen
behandeln.

11
Übung 4). Der PageRank Wert von d in der LinkedDocumentCollection dc ist gegeben
durch pageRank(d, dc). Dann berechnet sich die Relevanz des LinkedDocuments d als

relevance(d, q, dc) = g · similarity(d, q, dc) + (1 − g) · pageRank(d, dc)

Dabei ist g (0 ≤ g ≤ 1) ein Gewichtungsfaktor, der den Einfluß der Ähnlichkeit bzw. des
PageRank Wertes auf die Relevanz beeinflusst. Der Gewhichtungsfaktor hat üblicherweise
den Wert 0.6.
Setzen Sie daher in der Suchmaschinenimplementierung folgendes um;

1. Implementieren Sie in der Klasse LinkedDocumentCollection die Methode

private double[] sortByRelevance(double dampingFactor, double


weightingFactor),

welche die LinkedDocumentCollection nach der Relevanz der darin enthaltenen


LinkedDocuments absteigend sortiert und die Relevanz der Dokumente zurückgibt.

2. Ändern Sie das Kommando query in der Klasse TestIt so ab, dass die Dokumente
sortiert nach ihrer Relevanz ausgegeben werden.

Dateien zur Abgabe: Author.java, Date.java, DocumentCollectionCell.java,


DocumentCollection.java, Document.java, LinkedDocumentCollection.java,
LinkedDocument.java, Review.java, WordCount.java, WordCountsArray.java

Aufgabe 8.12 (H) Die TestIt-Klasse [1 Punkte]


Erweitern Sie die main-Methode der Klasse TestIt der vorherigen Übung (Hausaufga-
benblatt 7) wie folgt:

1. Testen Sie Ihre main-Methode ähnlich dem unten stehenden Beispiel. Führen Sie
Tests mit mindestens zwei unterschiedlichen Dokumentenstrukturen mit jeweils min-
destens fünf Dokumenten durch. Schreiben Sie ein Protokoll der Ein- und Ausgabe
Ihres Programmes als Kommentar in die Datei TestIt.java.
Beispiel: Seien die Dateien a.txt, c.txt, d.txt, e.txt wie folgt gegeben:
a.txt
es war einmal link:b.txt link:c.txt
c.txt
once upon a time link:d.txt

d.txt
erase una vez link:c.txt
e.txt
c era una volta link:b.txt
Die Ausführung der main-Methode in der Klasse TestIt sieht dann wie folgt aus:

12
> add b.txt:link:a.txt link:e.txt
> crawl
> pageRank
b.txt; PageRank: 0.14897680763983684
a.txt; PageRank: 0.0933151432469308
c.txt; PageRank: 0.34291508382082525
d.txt; PageRank: 0.32147782204547976
e.txt; PageRank: 0.0933151432469308
> query einmal
1. a.txt; Relevanz: 0.285917709527423
2. c.txt; Relevanz: 0.1371660335283301
3. d.txt; Relevanz: 0.1285911288181919
4. b.txt; Relevanz: 0.05959072305593474
5. e.txt; Relevanz: 0.03732605729877232
> exit
Dateien zur Abgabe: TestIt.java

Aufgabe 8.13 (H) Rekursive Determinantenberechnung [4 Punkte]


In Aufgabe 5.9 (H) haben Sie einen iterativen Algorithmus zur Determinantenberechnung
implementiert. Ziel der vorliegenden Aufgabe ist eine rekursive Implementierung, das heißt
die Verwendung von Prozeduren, welche sich selbst aufrufen, bis eine Abbruchbedingung
erfüllt ist. Erstellen Sie dafür zunächst eine Klasse RecursiveDeterminant, welche die
im Folgenden definierten Funktionen enthalten soll. Gehen Sie anschließend wie folgt vor:

1. Implementieren Sie die Methode

public static int det2x2(int[][] matrix),

welche die Determinante einer 2×2-Matrix berechnet. Die dafür erforderliche Formel
7
lautet


a b
|A| = = ad − bc. (1)

c d

2. Implementieren Sie die Methode

public static int det3x3(int[][] matrix),

welche die Determinante einer 3×3-Matrix nach dem folgenden Prinzip 7 berechnet:


a b c         

|A| = d e f = a  e f − b d  f + c d e  (2)


g h i  h i g  i g h 

e f d f d e
=a −b +c (3)

h i g i g h

Erstellen Sie zu diesem Zweck zwei Hilfsmethoden


7
Quelle: https://en.wikipedia.org/wiki/Determinant

13
public static int[][] removeRow(int[][] matrix, int rowIndex)

sowie

public static int[][] removeColumn(int[][] matrix, int colIndex),

welche die übergebene Matrix matrix kopieren, die i-te Zeile bzw. Spalte von der
Kopie entfernen und ebendiese Kopie zurückgeben. Verwenden Sie diese Hilfsmetho-
den, um die Matrix wie angegeben von 3 × 3 auf 2 × 2 zu verkleinern.
Anmerkung: Die Hilfsmethoden sollen auch in der Lage sein, quadratische n × n-
Matrizen entsprechend auf (n − 1) × (n − 1) zu verkleinern.

3. Erstellen Sie die Methode detNxN(). Die Methode soll die Determinante einer be-
liebigen quadratischen n × n-Matrix berechnen. Ihr Algorithmus sollte so vorgehen,
dass die Methode detNxN() sich selbst aufruft, um die Determinante einer Subma-
trix der Größe (n − 1) × (n − 1) zu berechnen. Eine Submatrix gewinnen Sie, indem
Sie jeweils eine Zeile und eine Spalte wie vorhergehend erläutert entfernen.

Testen Sie Ihre Implementierung in einer geeigneten main()-Methode.


Dateien zur Abgabe: RecursiveDeterminant.java

14

Das könnte Ihnen auch gefallen