Grundkurs Informatik Aufgabensammlung mit Lösungen Teil 3
Grundkurs Informatik Aufgabensammlung mit Lösungen Teil 3
Grundkurs Informatik Aufgabensammlung mit Lösungen Teil 3
Erfolgreiche ePaper selbst erstellen
Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.
<strong>Grundkurs</strong> <strong>Informatik</strong><br />
<strong>Aufgabensammlung</strong> <strong>mit</strong> <strong>Lösungen</strong><br />
<strong>Teil</strong> 3: Kapitel 9 bis 14<br />
H. Ernst<br />
Die Aufgaben sind nach folgendem Schema klassifiziert:<br />
T für Textaufgaben,<br />
M für mathematisch orientierte Aufgaben,<br />
L für Aufgaben, die logisches und kombinatorisches Denken erfordern,<br />
P für Programmieraufgaben.<br />
Nach der thematischen Kennung ist der Schwierigkeitsgrad der Aufgaben angegeben:<br />
0 bedeutet „sehr leicht“. Diese Aufgaben können un<strong>mit</strong>telbar gelöst werden, ggf. <strong>mit</strong> etwas<br />
Blättern im Buch.<br />
1 bedeutet „leicht“ und kennzeichnet Aufgaben, die innerhalb von einigen Minuten <strong>mit</strong> wenig<br />
Aufwand zu lösen sind.<br />
2 bedeutet „<strong>mit</strong>tel“. Solche Aufgaben erfordern etwas geistige Transferleistung und/oder einen<br />
größeren Arbeitsaufwand.<br />
3 bedeutet „schwer“ und ist für Aufgaben reserviert, die erheblichen Arbeitsaufwand <strong>mit</strong> kreativen<br />
Eigenleistungen erfordern.<br />
4 kennzeichnet „sehr schwere“ oder aufwändige Projekte.
3-2 Aufgaben und <strong>Lösungen</strong><br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
9 Automatentheorie und formale Sprachen<br />
9.1 Grundbegriffe der Automatentheorie<br />
Aufgabe 9.1.1 (T1)<br />
a) Was versteht man unter dem kartesischen Mengenprodukt?<br />
b) Was ist ein Akzeptor?<br />
c) Grenzen Sie die Begriffe Mealy- und Moore-Automat gegeneinander ab.<br />
d) Was ist ein endlicher Übersetzer?<br />
e) Definieren Sie den Begriff Mächtigkeit im Zusammenhang <strong>mit</strong> Automaten.<br />
f) Was ist ein Fangzustand?<br />
Lösung<br />
a) Das kartesiche Mengenprodukt A×B zweier Mengen A und B ist die Menge aller geordneten<br />
Paare der Art ab <strong>mit</strong> a∈A und b∈B. Ist beispielsweise A={x, y} und B={1, 2, 3}, dann<br />
ist A×B = { x1, x2, x3, y1, y2, y3 }.<br />
b) Ein Automat A(T,S,f) <strong>mit</strong> einem akzeptierten Sprachschatz L(A,sa,se) heißt Akzeptor oder<br />
erkennender Automat. Eine Folge t∈T* von Eingabezeichen bringt den Automaten genau<br />
vom Zustand sa in den Zustand se, wenn t∈L ist.<br />
c) Hängt die Ausgabe eines Automaten A(T,S,Y,f) sowohl vom Eingabezeichen als auch<br />
vom Zustand des Automaten ab, so nennt man den Automaten einen Mealy-Automaten:<br />
g: T×S→Y<br />
Hängt das Ausgabezeichen dagegen nur vom aktuellen internen Zustand des Automaten<br />
ab, so bezeichnet man den Automaten als einen Moore-Automaten:<br />
g: S→Y<br />
d) Ein endlicher Übersetzer oder Transduktor ist ein endlicher, übersetzender Automat, der<br />
durch einen endlichen Automaten A(T,S,Y,f) dargestellt wird. Der Automat ist endlich,<br />
wenn T, S und Y endliche Mengen sind. Ein Transduktor transformiert (übersetzt) eine<br />
eingegebene Zeichenkette t∈T* in eine Ausgabezeichenkette y∈T*. Davon zu unterscheiden<br />
sind Akzeptoren, siehe <strong>Teil</strong>aufgabe b).<br />
e) Bei einem Automaten <strong>mit</strong> endlichem Sprachschatz L wird die Anzahl der zu L gehörenden<br />
Wörter als die Mächtigkeit |L| des Sprachschatzes bezeichnet. Ein Sprachschatz <strong>mit</strong> unendlich<br />
vielen Wörtern hat die Mächtigkeit „abzählbar unendlich“. Allgemein bezeichnet<br />
man als die Mächtigkeit einer Menge die Anzahl der Elemente der Menge.<br />
f) Unter einem Fangzustand (oder Fehlerzustand) versteht man einen Zustand in einem<br />
Automaten, der durch kein Eingabezeichen verlassen werden kann.
Aufgaben und <strong>Lösungen</strong> 2-3<br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
Aufgabe 9.1.2 (T1)<br />
a) Wann nennt man eine Verknüpfung assoziativ?<br />
b) Was versteht man unter einem Erzeugendensystem?<br />
c) Was bedeutet Konkatenation?<br />
d) Definieren Sie den Begriff Automorphismus.<br />
e) Was ist die Worthalbgruppe?<br />
f) Was ist eine induzierte Halbgruppe?<br />
Lösung<br />
a) Durch ° sei eine Verknüpfung in einer algebraischen Struktur A definiert. Die Verknüpfung<br />
ist assoziativ, wenn gilt: (a ° b) ° c = a ° (b ° c) ∀ a,b,c∈A.<br />
b) Es sei F eine Halbgruppe und E eine Unterhalbgruppe von F. Es sei die Kleene’sche Hülle<br />
E* die Menge aller Elemente, die durch Verknüpfung beliebig vielen Elementen aus E entsteht.<br />
Ist E*=F, so heißt Ein Erzeugendensystem von F. In diesem Sinne sind Alphabete<br />
die Erzeugenden des zugehörigen Nachrichtenraums. Ein weiteres Beispiel sind die natürlichen<br />
Zahlen N, für welche die nur die 1 enthaltende <strong>Teil</strong>menge <strong>mit</strong> der Addition als<br />
Verknüpfung ein Erzeugendensystem ist, da sich jede natürliche Zahl als eine Summe<br />
von 1-en darstellen lässt.<br />
c) Das Zusammenhängen von Wörtern aus einem Nachrichtenraum wird als Konkatenation<br />
(Verkettung) bezeichnet.<br />
d) Eine Abbildung ϕ: F→H <strong>mit</strong> Halbgruppen (Gruppen) F und H heißt Homomorhismus von F<br />
in H, wenn gilt: ϕ(ab) = ϕ(a)ϕ(b) ∀ a,b∈F. Ist ϕ umkehrbar eindeutig, so spricht man von einem<br />
Isomorphismus. Sind außerdem F und H identisch, so liegt ein Automorphismus vor.<br />
e) Die Menge T* aller Wörter, die aus den Zeichen eines Alphabets T gebildet werden können,<br />
ist eine als Worthalbruppe bezeichnete Halbgruppe <strong>mit</strong> der Konkatenation als Verknüpfung.<br />
f) Die Menge aller Äquivalenzklassen bzw. Abbildungen eines Automaten A bildet <strong>mit</strong> der<br />
Operation „hintereinander ausführen“ eine Halbgruppe. Diese wird als die durch A induzierte<br />
Halbgruppe bezeichnet.<br />
Aufgabe 9.1.3 (L2)<br />
Gegeben sei der Automat <strong>mit</strong> T={a, b, c}<br />
und S={sa ,s1, s2, se}, dessen Übergangsfunktion durch<br />
die nebenstehende Tabelle definiert ist:<br />
Es gilt: sa=Anfangszustand, se=Endzustand<br />
a) Zeichnen Sie den Übergangsgraphen für diesen Automaten.<br />
b) Welche der folgenden Wörter gehören zum akzeptierten<br />
Sprachschatz dieses Automaten: abc, a 3 bc 3 , a 2 b 2 c 2 , a 3 b 2 c 2<br />
Lösung<br />
a) Übergangsdiagramm<br />
sa<br />
a,c<br />
a<br />
b<br />
s1<br />
c<br />
a,b<br />
b<br />
b<br />
s2<br />
c<br />
c<br />
a<br />
se<br />
4<br />
sa s1 s2 se<br />
a s1 sa s1 se<br />
b se s1 s1 sa<br />
c s2 sa se s2
3-4 Aufgaben und <strong>Lösungen</strong><br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
b) Der Automat befindet sich im Anfangszustand sa. Die Zeichen des Wortes abc werden nun<br />
von links nach rechts zeichenweise eingegeben. Dabei ergeben sich folgende Übergänge:<br />
saa→s1, s1b→s1, s1c→sa,<br />
Insgesamt findet man also saabc→sa. Der Endzustand wird offenbar nicht erreicht, dementsprechend<br />
gehört abc nicht zum akzeptierten Sprachschatz: abc∉L<br />
Für a 3 bc 3 findet man: saa 3 bc 3 →se, also gilt a 3 bc 3 ∈L.<br />
Für a 2 b 2 c 2 findet man: saa 3 bc 3 →se, also gilt a 2 b 2 c 2 ∈L.<br />
Für a 3 b 2 c 2 findet man: saa 3 bc 3 →s2, also gilt a 3 b 2 c 2 ∉L.<br />
Aufgabe 9.1.4 (L2)<br />
Geben Sie den Übergangsgraphen und die Übergangstabelle eines endlichen Automaten <strong>mit</strong><br />
T={a, b} an, dessen akzeptierter Sprachschatz L aus der Menge aller Worte aus T* besteht,<br />
die <strong>mit</strong> a beginnen und bb nicht als <strong>Teil</strong>string enthalten. Formulieren Sie außerdem L in der<br />
üblichen Mengenschreibweise.<br />
a<br />
Lösung<br />
a sa<br />
Se1<br />
sa se1 se2 sf<br />
a se1 se1 se1 sf<br />
b<br />
a b<br />
b sf se2 sf sf<br />
Einfache Lösung: L = {ax | x∈T*, bb∉x}<br />
Besser: L = {a, (a ni b) m a k | ni∈N, m,k∈N0}<br />
Aufgabe 9.1.5 (L3)<br />
Geben Sie einen Automaten als Übergangstabelle und als Übergangsdiagramm an, der alle<br />
aus den Ziffern 1 bis 4 gebildeten natürlichen Zahlen akzeptiert, deren Stellen monoton<br />
wachsen (jede folgende Ziffer ist also größer oder gleich der vorangehenden). Ein Beispiel<br />
ist etwa die Zahl 112444. Markieren Sie dabei den Anfangszustand und den Endzustand bzw.<br />
die Endzustände.<br />
Lösung<br />
sa s1 s2 s3 s4 sf<br />
1 s1 s1 sf sf sf sf<br />
2 s2 s2 s2 sf sf sf<br />
3 s3 s3 s3 s3 sf sf<br />
4 s4 s4 s4 s4 s4 sf<br />
Die Zustände s1 bis s4 sind alle Endzustände.<br />
a,b<br />
sa<br />
sf<br />
1<br />
sf<br />
1<br />
s1<br />
2<br />
2<br />
b<br />
3<br />
2<br />
s2<br />
se2<br />
3<br />
4<br />
3<br />
4<br />
3<br />
s3<br />
4<br />
4<br />
4<br />
s4
Aufgaben und <strong>Lösungen</strong> 2-5<br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
Aufgabe 9.1.6 (L4)<br />
Gegeben sei der Automat A(T,S,f) <strong>mit</strong> T={a, b}, S={sa, se, sf } und<br />
der durch die nebenstehende Tabelle definierten Zustandsübergangsfunktion f:<br />
a) Zeichnen Sie den zugehörigen Zustandsübergangsgraphen.<br />
b) Wie lautet der durch A akzeptierte Sprachschatz?<br />
c) Bestimmen Sie die Äquivalenzklassen dieses Automaten.<br />
d) Geben Sie die zugehörige induzierte Halbgruppe an.<br />
e) Gibt es Null- und Einselemente in der induzierten Halbgruppe?<br />
Lösung<br />
a) Übergangsgraph:<br />
a<br />
a a, b<br />
sa<br />
b<br />
se<br />
b<br />
sf<br />
b) Der akzeptierte Sprachschatz lautet: L = { a n ba m | n,m∈N0 }<br />
c) Die im Automaten möglichen Abbildungen lauten:<br />
sa se sf Abbildung<br />
a sa se sf* sa→sa, se→se, sf→sf<br />
b se sf sf* sa→se, se→sf, sf→sf<br />
aa sa se sf<br />
ba se sf sf<br />
ab se sf sf<br />
bb sf sf sf* sa→sf, se→sf, sf→sf<br />
aaa sa se sf<br />
baa se sf sf<br />
aba se sf sf<br />
bba sf sf sf<br />
aab sa se sf<br />
bab sf sf sf<br />
abb sf sf sf<br />
bbb sf sf sf<br />
Es gibt also drei Abbildungen, die zugehörigen Äquivalenzklassen lauten:<br />
[a] = { a n | n∈N }<br />
[b] = { a n ba m | n,m∈N0 } = L<br />
[bb] = { x∈T*\[a]\[b] }<br />
d) Die induzierte Halbgruppe lautet:<br />
1.<br />
2. [a] [b] [bb]<br />
[a] [a] [b] [bb]<br />
[b] [b] [bb] [bb]<br />
[bb] [bb] [bb] [bb]<br />
e) Es gibt ein Einselement, nämlich [a] und ein Nullelement, nämlich [bb].<br />
sa se sf<br />
a sa se sf<br />
b se sf sf
3-6 Aufgaben und <strong>Lösungen</strong><br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
Aufgabe 9.1.7 (L3)<br />
Beschreiben Sie ein Polynom soll als BNF-Produktion, als Syntax-Graph und als Automat.<br />
Ein Polynom besteht aus durch die Operatoren + und – verknüpften Termen. Ein Term besteht<br />
aus einer optionalen reellen Zahl, optional multipliziert <strong>mit</strong> einer beliebig langen Folge<br />
von multiplikativ verknüpften Variablen x. Ein Term darf nicht leer sein.<br />
Beispiele für Terme: 3.5, x, -5*x*x, x*x*x.<br />
Beispiel für ein Polynom: 2 + 4*x + x*x*x - 3.5*x*x.<br />
Die syntaktischen Variablen und dürfen für BNF-Produktionen und<br />
Syntaxgraphen als bekannt vorausgesetzt werden. Das Alphabet des Automaten sei T={r, x,<br />
*, +, -}, wobei r für eine reelle Zahl und x für eine Variable steht.<br />
Lösung<br />
::= |[{*}]<br />
::= [{+|-}]<br />
Term : Variable<br />
sa<br />
+, -, *<br />
r, x, +, -, *<br />
*<br />
Reelle Zahl<br />
+, -<br />
r, x<br />
r, x<br />
se<br />
sf s1<br />
r, +, -, *<br />
*<br />
Polynom :<br />
x<br />
-<br />
+<br />
Term<br />
sa s1 se sf<br />
x se se sf sf<br />
r se sf sf sf<br />
* sf sf s1 sf<br />
+, - sf sf sa sf
Aufgaben und <strong>Lösungen</strong> 2-7<br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
9.2 Turing-Maschinen<br />
Aufgabe 9.2.1 (T1)<br />
a) Was haben Turing-Maschinen <strong>mit</strong> Berechenbarkeit zu tun?<br />
b) Woran ist Alan Turing gestorben?<br />
c) Was ist ein linear beschränkter Automat?<br />
d) Grenzen Sie die Begriffe Automat, Kellerautomat und Turing-Maschine gegeneinander ab.<br />
e) Was ist das Spiel des Lebens?<br />
Lösung<br />
a) Eine Funktion f(x)=y <strong>mit</strong> x,y∈T* ist Turing-berechenbar, wenn es eine Turing-Maschine<br />
gibt, welche die auf dem Band gespeicherte Eingabe x in die Ausgabe y transformiert, die<br />
dann auf dem Band abgelesen werden kann. Turing-Maschinen sind Modelle für einen<br />
abstrakten Computer: alles was man prinzipiell <strong>mit</strong> einer Turing-Maschine berechnen<br />
kann, kann man auch <strong>mit</strong> einem Computer berechnen und umgekehrt.<br />
b) Alan Turing ist im Exil in Griechenland vermutlich durch Selbstmord an Gift gestorben. Er<br />
musste England verlassen, da er wegen seiner offenkundig gewordenen Homosexualität<br />
damals in einer geheimen militärischen Tätigkeit als Sicherheitsrisiko angesehen wurde.<br />
c) Ein linear beschränkter Automat ist eine Turing-Maschine, bei der nur ein durch die Länge<br />
des Eingabewortes beschränkter Bereich des Bandes verwendet wird. Die durch linear<br />
beschränkte Automaten akzeptierten Sprachen sind zu den in Kapitel 9.3 eingeführten<br />
kontextfreien Sprachen äquivalent, die eine wichtige Grundlage von Programmiersprachen<br />
bilden.<br />
d) Ein Automat hat außer den internen Zuständen keinen Speicher, er erhält von außen Eingabezeichen.<br />
Ein Kellerautomat hat einen einseitig unbegrenzten Speicher, der als Stack für Kellerzeichen<br />
verwendet wird. Er enthält ebenfalls von außen Eingabezeichen.<br />
Eine Turing-Maschine hat ein beidseitig unbegrenztes lineares Speicherband, auf dem vor<br />
Start der Turingmaschine die Eingabezeichen vorhanden sein müssen. Die Turingmaschine<br />
kann dann die Zeichen vom Band lesen, Zeichen auf das Band schreiben und<br />
schrittweise nach rechts und links den Schreib/Lese-Kopf auf dem Band bewegen.<br />
e) John von Neumann beschäftigte sich seit Anfang der 50er Jahre <strong>mit</strong> zellulären Automaten,<br />
die ebenfalls zur Simulation eines universellen Computers geeignet sind. Zunächst<br />
ging es dabei nur um die formale Beschreibung der Fähigkeit zur Selbstreproduktion. Eine<br />
populäre Variante zellulärer Automaten ist das Spiel des Lebens (Game of Life) von John<br />
Conway (1968). Da<strong>mit</strong> lassen sich interessante dynamische Strukturen generieren. Die<br />
Regeln lauten:<br />
• Ein rechteckiges Spielfeld, etwa ein Schachbrett, wird <strong>mit</strong> Spielmarken vorbesetzt.<br />
• Jede Spielmarke <strong>mit</strong> zwei oder drei Nachbarn überlebt den aktuellen Spielschritt und<br />
bleibt für die nächste Generation erhalten.<br />
• Jede Spielmarke auf einem Feld <strong>mit</strong> vier oder mehr Nachbarn stirbt an Überbevölkerung,<br />
d.h. sie wird in der nächsten Generation vom Spielfeld entfernt (gelöscht).<br />
• Jede Spielmarke auf einem Feld <strong>mit</strong> nur einem oder gar keinem Nachbarn stirbt an Einsamkeit,<br />
d.h. sie wird ebenfalls gelöscht.<br />
• Auf jedem leeren, von genau drei Nachbarn umgebenen Feld, wird in der nächsten Generation<br />
eine Spielmarke „geboren“. Alle anderen leeren Felder bleiben leer.
3-8 Aufgaben und <strong>Lösungen</strong><br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
Aufgabe 9.2.2 (L2)<br />
Es sei die folgende Turing-Maschine <strong>mit</strong><br />
den Bandzeichen {0, 1} als Übergangsdiagramm gegeben.<br />
a) Geben Sie das dazugehörige tabellarische<br />
Turing-Programm an.<br />
b) Der Schreib/Lese-Kopf stehe auf einem <strong>mit</strong> 0en<br />
vorbesetzten Band. Was bewirkt diese Turing-Maschine?<br />
Lösung<br />
a)<br />
1 0 1 R 2 2 0 1 L 1 3 0 1 L 2<br />
1 1 L 3 1 1 R 2 1 1 R 0<br />
1<br />
1<br />
1,L<br />
3<br />
1,L<br />
0 1,R<br />
1,L<br />
0<br />
2<br />
1 1,R HAL<br />
0<br />
b) Diese Turing-Maschine schreibt 6 1en auf ein anfänglich <strong>mit</strong> 0en vorbesetztes Band. Sie<br />
stoppt nach 13 Schritten unter der zweiten 1 von rechts.<br />
Es handelt sich hierbei um die Busy-Beaver-Funktion bb(3), d.h. um diejenige aus genau<br />
drei Anweisungen bestehende Turing-Maschine, welche die größte Anzahl von aufeinanderfolgenden<br />
1en auf ein anfänglich <strong>mit</strong> 0en vorbesetztes Band schreibt.<br />
Aufgabe 9.2.3 (L3)<br />
Konstruieren Sie eine Turing-Maschine <strong>mit</strong> den Bandzeichen T={-,0,1}, welche für eine zusammenhängende<br />
aus 0en und 1en bestehende Zeichenfolge auf einem <strong>mit</strong> - vorbesetzten Band<br />
die Anzahl der 1en auf gerade Parität ergänzt. Dazu wird am linken Ende der Zeichenfolge eine<br />
0 angefügt, wenn die Anzahl der 1en gerade ist und eine 1, wenn die Anzahl der 1en ungerade<br />
ist. Der Schreib/Lese-Kopf soll vor der Operation rechts neben der Zeichenfolge stehen.<br />
Beispiel: aus ----10101---- wird also ---110101---- und aus ----1001---- wird ---01001----<br />
Lösung<br />
Strategie:<br />
1. In Zustand 1 rechts neben dem String starten.<br />
In Zustand 1 nach links gehen, solange - gelesen wird.<br />
Wird in Zustand 1 eine 0 gelesen, nach Zustand 3 gehen. (Erstes Stringzeichen ist 0)<br />
Wird in Zustand 1 eine 1 gelesen, nach Zustand 2 gehen. (Erstes Stringzeichen ist 1)<br />
2. In Zustand 2 solange nach links gehen, wie 0en gelesen werden.<br />
Wird in Zustand 2 eine 1 gelesen, nach Zustand 3 gehen.<br />
Wird in Zustand 2 ein - gelesen, ist das Stringende erreicht, eine 1 schreiben, HALT.<br />
3. In Zustand 3 solange nach links gehen, wie 0en gelesen werden.<br />
Wird in Zustand 3 eine 1 gelesen, nach Zustand 2 gehen.<br />
Wird in Zustand 3 ein - gelesen, ist das Stringende erreicht, eine 0 schreiben, HALT.<br />
Ist die Turing-Maschine in Zustand 2, so ist die aktuelle Anzahl der 1en ungerade, ist sie in<br />
Zustand 3, so ist die Anzahl der 1en gerade.<br />
- - L 1 - 1 L 0 - 0 L 0<br />
1 0 0 L 3 2 0 0 L 2 3 0 0 L 3<br />
1 1 L 2 1 1 L 3 1 1 L 2<br />
1<br />
1,R
Aufgaben und <strong>Lösungen</strong> 2-9<br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
0,L<br />
0<br />
-<br />
0<br />
0,L<br />
-,L<br />
1<br />
3<br />
1<br />
1<br />
-<br />
1,L<br />
Aufgabe 9.2.4 (L3)<br />
1,L<br />
0,L<br />
1,L<br />
0<br />
1<br />
0,L<br />
2<br />
HALT<br />
-<br />
1,L HALT<br />
Konstruieren Sie eine Turing-Maschine <strong>mit</strong> den Bandzeichen T={-,0,1}, die in einer zusammenhängenden<br />
Gruppe von Einsen auf einem <strong>mit</strong> Strichen vorbesetzten Band zwischen je<br />
zwei Einsen eine Null einfügt. Zu Beginn soll der Schreib-/Lesekopf rechts von der Einsergruppe<br />
stehen. Beispiel: aus ------11111----- wird also ------101010101-----<br />
Lösung<br />
Strategie:<br />
1. In Zustand 1 rechts neben dem String starten.<br />
In Zustand 1 nach links gehen, bis zur ersten 1, also zum String-Anfang.<br />
Wurde die erste 1 gelesen, nach Zustand 2 gehen.<br />
2. In Zustand 2 einen Schritt nach links gehen.<br />
Wird dort eine 1 gelesen, diese durch 0 ersetzen und nach Zustand 3 gehen.<br />
Wird ein - oder eine 0 gelesen, HALT.<br />
3. In Zustand 3 solange nach links gehen, bis ein - gelesen wird.<br />
Den - am Stringende durch eine 1 ersetzen und nach Zustand 4 gehen.<br />
4. In Zustand 4 nach rechts gehen bis eine 0 gelesen wird.<br />
Jetzt weiter bei Punkt 1 in Zustand 1.<br />
- - L 1 - - R 0 - 1 R 4 - - R 0<br />
1 0 0 L 0 2 0 0 L 0 3 0 0 L 3 4 0 0 L 1<br />
1 1 L 2 1 0 L 3 1 1 L 3 1 1 R 4<br />
HALT<br />
-,R<br />
-<br />
-<br />
-,L<br />
0,L<br />
0<br />
1<br />
4<br />
1,R<br />
0<br />
1<br />
HALT<br />
0,L<br />
1<br />
1,L<br />
1,R<br />
-<br />
1<br />
0,L<br />
2<br />
3<br />
0,L<br />
1<br />
0<br />
- -,R<br />
HALT<br />
0 0,L<br />
1,L
3-10 Aufgaben und <strong>Lösungen</strong><br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
9.3 Einführung in die Theorie der formalen Sprachen<br />
Aufgabe 9.3.1 (T1)<br />
a) Wie kann man endliche von zyklischen Sackgassen unterscheiden?<br />
b) Wann ist eine formale Sprache eindeutig?<br />
c) Was ist ein Palindrom?<br />
d) Was versteht man unter einer kontextfreien Sprache?<br />
e) Was ist das Wortproblem?<br />
Lösung<br />
a) Kommt man bei der Analyse eines Wortes nach endlich vielen Schritten zu einem Wort,<br />
auf das keine Produktionen mehr angewendet werden können, so ist man in eine endliche<br />
Sackgasse geraten. Man kann dann die Analyse bei einer vorangehenden Verzweigungsmöglichkeit<br />
wieder aufnehmen. Bei einer zyklischen Sackgasse führt die Analyse<br />
nach einer endlichen Anzahl von Schritten wieder auf ein Wort, das in einem früheren<br />
Schritt bereits aufgetreten ist. Man kann die Sackgasse erkennen und wieder verlassen,<br />
wenn man alle Analyseschritte zwischenspeichert und immer wieder <strong>mit</strong> dem aktuellen<br />
Schritt vergleicht.<br />
b) Eine formale Sprache heißt eindeutig, wenn der zugehörige Sprachschatz eindeutig ist,<br />
d.h. wenn alle zum Sprachschatz gehörenden Wörter nur auf eine einzige, eindeutige<br />
Weise aus dem Axiom Z ableitbar sind.<br />
c) Unter einem Palindrom versteht man ein um seinen Mittelpunkt symmetrisches Wort. Es<br />
ist also vorwärts und rückwärts gelesen identisch. Beispiel: otto.<br />
d) Eine Grammatik heißt kontextfrei (context free) oder Chomsky-2-Grammatik, wenn die<br />
Produktionen nicht von einem Kontext abhängen. Die Produktionen haben dann die einfache<br />
Form:<br />
A→u <strong>mit</strong> A∈S und u∈V*\{ε}<br />
Die syntaktische Variable A wird also unabhängig von rechts oder links benachbarten<br />
Zeichen, dem Kontext, in ein beliebiges, nichtleeres Wort aus V* transformiert. Worte<br />
können also nicht kürzer werden, d.h. die Sprache ist wortlängenmonoton. Die Menge der<br />
durch kontextfreie Grammatiken erzeugten Sprachen ist <strong>mit</strong> der Menge der durch Kellerautomaten<br />
akzeptierten Sprachen identisch.<br />
e) Unter dem Wortproblem versteht man die Aufgabe, von einem gegebenen Wort zu entscheiden,<br />
ob es zu einem bestimmten Sprachschatz gehört oder nicht. Es kann sich dabei<br />
beispielsweise um den Sprachschatz einer formalen Sprache oder um den akzeptierten<br />
Sprachschatz eines Automaten handeln.<br />
Aufgabe 9.3.2 (T1)<br />
a) Was versteht man unter dem Nachbereich einer formalen Sprache?<br />
b) Unter welcher Bedingung heißt eine Produktion terminal?<br />
c) Was versteht man unter dem Kern einer formalen Sprache?<br />
d) Welche Klasse von formalen Sprachen der Chomsky-Hierarchie lässt sich durch<br />
Automaten darstellen?<br />
e) Welcher Typ von formalen Sprachen ist zu Kellerautomaten äquivalent?
Aufgaben und <strong>Lösungen</strong> 2-11<br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
Lösung<br />
a) Unter dem Nachbereich einer formalen Sprache versteht man die Menge aller Wörter, die<br />
aus dem Axiom Z∈S ableitbar sind.<br />
b) Eine Produktion heißt terminal, wenn das Ergebnis nur aus terminalen Zeichen besteht,<br />
also:<br />
u→v ist terminal, wenn v∈T*\{ε}<br />
Im engeren Sinne verlangt man noch Wortlängenmonotonie. Bei regulären Sprachen ist<br />
außerdem u∈S vorausgesetzt, d.h. u muss eine syntaktische Variable sein.<br />
c) Der Kern einer formalen Sprache besteht aus allen aus dem Axiom Z ableitbaren Wörtern,<br />
aus denen sich die Wörter des Sprachschatzes (also die Menge aller ausschließlich aus<br />
terminalen Zeichen bestehenden Wörter) ableiten lassen. Zusätzlich zum Sprachschatz<br />
umfasst der Kern also auch Worte, in denen auch Syntaktische Variablen enthalten sind.<br />
d) Reguläre Grammatiken, also solche <strong>mit</strong> terminalen und linkslinearen oder rechtslinearen<br />
Produktionen, lassen sich durch Automaten darstellen.<br />
e) Kontextfreie Sprachen, also Chomsky-2-Grammatiken sind zum akzeptierten Sprachschatz<br />
von Kellerautomaten äquivalent.<br />
Aufgabe 9.3.2 (T1)<br />
Gegeben sei die folgende formale Sprache:<br />
S = {Z, X}, T = {u, v, w}, P = { Z→uZv, Z→X, X→vXu, X→v, X→w }<br />
a) Von welchem Typ ist die Grammatik?<br />
b) Geben Sie den zugehörigen Sprachschatz an.<br />
c) Leiten Sie das Wort uv 2 wu 2 v aus dem Axiom ab. Falls es bei der Ableitung Sackgassen<br />
gibt, geben Sie bitte ein Beispiel an.<br />
Lösung<br />
a) Die Sprache ist wortlängenmonoton, da keine Produktion verkürzend wirkt. Sie ist außerdem<br />
kontextfrei und daher vom Typ Chomsky-2. Da es nichtlineare Produktionen gibt, ist<br />
die Sprache nicht vom Typ Chomsky-3.<br />
b) Der zugehörige Sprachschatz lautet: L={ u n v m xu m v n | n,m ∈N0, x={v,w} }.<br />
Die kürzesten Wörter sind: v, w, uwv, uv 2 .<br />
c) Z→uZv→uvXuv→uvvXuuv→uv 2 wu 2 v<br />
Es gibt Sackgassen, aber nur endliche. Beispiel: Z→uZv→uvXuv→uvwuv<br />
Aufgabe 9.3.2 (T1)<br />
Konstruieren Sie eine Formale Sprache L für die Menge aller korrekten arithmetischen Ausdrücke<br />
<strong>mit</strong> natürlichen Zahlen n unter Verwendung der Operationen „+“ und „ * “ sowie der üblichen<br />
Klammerung.<br />
Lösung<br />
T={0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +, *, (, )}<br />
S={Z, N, T}<br />
P={Z→T, T→T+T, T→T*T, T→(T), T→N, N→NN, N→0,1..9 }
3-12 Aufgaben und <strong>Lösungen</strong><br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
Aufgabe 9.3.2 (T1)<br />
Die Zusammenstellung eines Intercity-Zuges möge nach folgenden Regeln erfolgen:<br />
Der erste Wagen des Zugs ist ein Triebwagen, es folgen n>1 Wagen der ersten Klasse, danach<br />
folgt ein Speisewagen und danach 2n Wagen der zweiten Klasse.<br />
a) Geben Sie die Wagenfolge des kürzestmöglichen Zuges an.<br />
b) Konstruieren Sie eine formale Sprache für die Zusammenstellung von Intercity-Zügen.<br />
Verwenden Sie dazu die Menge T={t,1,s,2} von terminalen Zeichen in ihrer offensichtlichen<br />
Bedeutung sowie die Menge S={Z,W} von syntaktischen Variablen, wobei W<br />
für „Wagen“ steht.<br />
c) Von welchem Chomsky-Typ ist diese Sprache? Bitte begründen Sie Ihre Antwort.<br />
d) Wie muss man die Regeln für die Zugzusammenstellung ändern, da<strong>mit</strong> die entsprechende<br />
formale Sprache als Automat darstellbar ist?<br />
Lösung<br />
a) Mit T={t,1,s,2} lautet die Wagenfolge des kürzestmöglichen<br />
ICE-Zuges: t1s22<br />
b) Vokabular: V = T∪S <strong>mit</strong> T={t,1,s,2} und S={Z, W}<br />
Produktionen: P = {Z→tW, W→1s22, W→1W22}<br />
c) Nicht alle verwendeten Produktionen sind terminal oder linear, daher ist die Sprache nicht<br />
CH-3. Die Sprache ist wortlängenmonoton und kontextfrei und daher vom Typ CH-2.<br />
d) Problematisch ist, dass die Anzahl der Wagen zweiter Klasse doppelt so hoch sein soll wie<br />
die der ersten Klasse und dass die Anzahl der Wagen nicht beschränkt ist. Beschränkt man<br />
die Anzahl der Wagen, so ist eine Darstellung als Automat möglich. Auch ohne Beschränkung<br />
gelingt dies, wenn die Anzahl der Wagen erster Klasse unabhängig von der Anzahl der<br />
Wagen zweiter Klasse ist.<br />
Man kann zwar eine Mengen von Produktionen finden, bei der alle Produktionen terminal<br />
oder linear sind, beispielsweise<br />
P = {Z→tW, W→1s22, W→V22, V→1W}<br />
es treten jedoch linkslineare und rechtslineare Produktionen auf, so dass die resultierende<br />
Grammatik nicht regulär ist.<br />
Man könnte die Aufgabe jedoch ohne weiteres <strong>mit</strong> Hilfe eines Kellerautomaten lösen.
Aufgaben und <strong>Lösungen</strong> 2-13<br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
10 Algorithmen<br />
10 Algorithmen<br />
Aufgabe 10.1 (T1)<br />
a) Wann ist ein Algorithmus statisch finit und wann dynamisch finit?<br />
b) Erläutern Sie den Unterschied zwischen Berechenbarkeit und Komplexität.<br />
c) Was besagt die Church-Turing-These?<br />
d) Was ist der Unterschied zwischen pri<strong>mit</strong>iv-rekursiven und µ-rekursiven Funktionen?<br />
e) Was sind NP-vollständige Probleme<br />
f) Wann ist ein Algorithmus effektiv, wann effizient?<br />
Aufgabe 10.2 (T1)<br />
a) Was ist eine rekursive Relation?<br />
b) Wodurch unterscheiden sich probabilistische und heuristische Algorithmen?<br />
c) Was ist ein genetischer Algorithmus?<br />
d) Erläutern Sie das Prinzip des Backtracking.<br />
e) Erläutern Sie das Prinzip <strong>Teil</strong>e und Herrsche.<br />
f) Was ist ein gieriger Algorithmus?<br />
Aufgabe 10.3 (M3)<br />
Die Ackermann-Funktion.<br />
a) Wie ist die Ackermann-Funktion definiert?<br />
b) Ist die Ackermann-Funktion pri<strong>mit</strong>iv-rekursiv?<br />
c) Berechnen Sie ack(3,2)<br />
d) Für welches x∈N gilt ack(3,ack(0,y)) = ack(x,ack(3,y)) ?<br />
e) Zeigen Sie: ack(p,q+1) > ack(p,q)<br />
Lösung<br />
a) Die Ackermann-Funktion ack(x,y) ist folgendermaßen rekursiv definiert:<br />
ack(0,y) = y+1<br />
ack(x+1,0) = ack(x,1)<br />
ack(x+1,y+1) = ack(x,ack(x+1,y)<br />
b) Die Ackermann-Funktion ist keine pri<strong>mit</strong>iv-rekursive Funktion, sondern eine µ-rekursive<br />
Funktion, da man zeigen kann, dass sie schneller wächst als jede pri<strong>mit</strong>iv rekursive Funktion.<br />
Sie ist jedoch berechenbar, da sie eine µ-rekursive Funktion ist.<br />
Man kann auch als Begründung angeben, dass man bei der iterativen Programmierung<br />
der Ackermann-Funktion nicht <strong>mit</strong> einer klassischen FOR-Schleife auskommt. Man benötigt<br />
eine GOTO oder eine WHILE-Schleife.<br />
c) Im Folgenden wird statt ack(a,b) vereinfachend (a,b) geschrieben.<br />
Nach dem Aufschreiben der ersten Terme erkennt man das Bildungsgesetz, so dass sich<br />
die Lösung schnell finden lässt. Siehe auch Aufgabe 10.4.<br />
(3,2) = (2,(3,1)) = (2,(2,(3,0))) = (2,(2,(2,1))) =<br />
(2,(2,(1,(2,0)))) = (2,(2,(1,(1,1)))) = (2,(2,(1,(0,(1,0))))) =<br />
(2,(2,(1,(0,(0,1))))) = (2,(2,(1,(0,2)))) = (2,(2,(1,3))) =<br />
(2,(2,(0,(1,2)))) = (2,(2,(0,(0,(1,1))))) = (2,(2,(0,(0,(0,(1,0)))))) =<br />
(2,(2,(0,(0,(0,2))))) = (2,(2,(0,(0,3)))) = (2,(2,(0,4))) = (2,(2,5)) =
3-14 Aufgaben und <strong>Lösungen</strong><br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
(2,(1,(2,4))) = (2,(1,(1,(2,3)))) = (2,(1,(1,(1,(2,2))))) = (2,(1,(1,(1,(1,(2,1)))))) =<br />
(2,(1,(1,(1,(1,(1,(2,0))))))) =<br />
(2,(1,(1,(1,(1,(1,(1,1))))))) =<br />
(2,(1,(1,(1,(1,(1,(0,(1,0)))))))) =<br />
(2,(1,(1,(1,(1,(1,(0,(0,1)))))))) =<br />
(2,(1,(1,(1,(1,(1,(0,2))))))) =<br />
(2,(1,(1,(1,(1,(1,3)))))) =<br />
(2,(1,(1,(1,(1,(0,(1,2))))))) =<br />
(2,(1,(1,(1,(1,(0,(0,(1,1)))))))) =<br />
(2,(1,(1,(1,(1,(0,(0,(0,(1,0))))))))) =<br />
(2,(1,(1,(1,(1,(0,(0,(0,(0,1))))))))) =<br />
(2,(1,(1,(1,(1,(0,(0,(0,2)))))))) =<br />
(2,(1,(1,(1,(1,(0,(0,3))))))) =<br />
(2,(1,(1,(1,(1,(0,4)))))) =<br />
(2,(1,(1,(1,(1,5))))) =<br />
(2,(1,(1,(1,(0,(1,4)))))) =<br />
(2,(1,(1,(1,(0,(0,(1,3))))))) =<br />
(2,(1,(1,(1,(0,(0,(0,(1,2)))))))) =<br />
(2,(1,(1,(1,(0,(0,(0,(0,(1,1))))))))) =<br />
(2,(1,(1,(1,(0,(0,(0,(0,(0,(1,0)))))))))) =<br />
(2,(1,(1,(1,(0,(0,(0,(0,(0,(0,1)))))))))) =<br />
(2,(1,(1,(1,(0,(0,(0,(0,(0,2))))))))) =<br />
(2,(1,(1,(1,(0,(0,(0,(0,3)))))))) =<br />
(2,(1,(1,(1,(0,(0,(0,4))))))) =<br />
(2,(1,(1,(1,(0,(0,5)))))) =<br />
(2,(1,(1,(1,(0,6))))) =<br />
(2,(1,(1,(1,7)))) =<br />
(2,(1,(1,(0,(1,6))))) =<br />
…<br />
(2,(1,(1,(0,(0,(0,(0,(0,(0,(0,(1,0))))))))))) =<br />
(2,(1,(1,(0,(0,(0,(0,(0,(0,(0,(0,1))))))))))) =<br />
…<br />
(2,(1,(1,(0,8)))) =<br />
(2,(1,(1,9))) =<br />
(2,(1,(0,(1,8)))) =<br />
…<br />
(2,(1,(0,(0,(0,(0,(0,(0,(0,(0,(0,(1,0)))))))))))) =<br />
(2,(1,(0,(0,(0,(0,(0,(0,(0,(0,(0,(0,1)))))))))))) =<br />
…<br />
(2,(1,(0,10))) =<br />
(2,(1,11)) =<br />
(2,(0,(1,10))) =<br />
…<br />
(2,(0,(0,(0,(0,(0,(0,(0,(0,(0,(0,(0,(1,0))))))))))))) =<br />
(2,(0,(0,(0,(0,(0,(0,(0,(0,(0,(0,(0,(0,1))))))))))))) =<br />
…<br />
(2,(0,12)) =<br />
(2,13) =<br />
(1,(2,12)) =<br />
(1,(1,(2,11))) =<br />
(1,(1,(1,(2,10)))) =<br />
(1,(1,(1,(1,(2,9))))) =<br />
(1,(1,(1,(1,(1,(2,8)))))) =<br />
(1,(1,(1,(1,(1,(1,(2,7))))))) =<br />
(1,(1,(1,(1,(1,(1,(1,(2,6)))))))) =<br />
(1,(1,(1,(1,(1,(1,(1,(1,(2,5))))))))) =
Aufgaben und <strong>Lösungen</strong> 2-15<br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
(1,(1,(1,(1,(1,(1,(1,(1,(1,(2,4)))))))))) =<br />
(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(2,3))))))))))) =<br />
(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(2,2)))))))))))) =<br />
(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(2,1))))))))))))) =<br />
(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(2,0)))))))))))))) =<br />
(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,1)))))))))))))) =<br />
(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(0,(1,0))))))))))))))) =<br />
(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(0,(0,1))))))))))))))) =<br />
(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(0,2)))))))))))))) =<br />
(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,3))))))))))))) =<br />
(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(0,(1,2)))))))))))))) =<br />
(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(0,(0,(1,1))))))))))))))) =<br />
(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(0,(0,(0,(1,0)))))))))))))))) =<br />
(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(0,(0,(0,(0,1)))))))))))))))) =<br />
(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(0,(0,(0,2))))))))))))))) =<br />
(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(0,(0,3)))))))))))))) =<br />
(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(0,4))))))))))))) =<br />
(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,5)))))))))))) =<br />
…<br />
(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,7))))))))))) =<br />
…<br />
(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,9)))))))))) =<br />
…<br />
(1,(1,(1,(1,(1,(1,(1,(1,(1,11))))))))) =<br />
…<br />
(1,(1,(1,(1,(1,(1,(1,(1,13)))))))) =<br />
…<br />
(1,(1,(1,(1,(1,(1,(1,15))))))) =<br />
…<br />
(1,(1,(1,(1,(1,(1,17)))))) =<br />
…<br />
(1,(1,(1,(1,(1,19))))) =<br />
…<br />
(1,(1,(1,(1,21)))) =<br />
…<br />
(1,(1,(1,23))) =<br />
…<br />
(1,(1,25)) =<br />
…<br />
(1,27) =<br />
…<br />
29<br />
d) ack(3,ack(0,y)) = ack(3,y+1) = ack(2+1,y+1) = ack(2,ack(3,y))<br />
Es ist also x=2<br />
e) q0, q>1 (*)<br />
A(p,q)1<br />
p>1, q=0: A(p,0)=0 < A(0,1)=2
3-16 Aufgaben und <strong>Lösungen</strong><br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
Aufgabe 10.4 (P3)<br />
Schreiben sie ein rekursives und ein iteratives Programm zur Berechnung der Ackermann-<br />
Funktion. Vergleichen Sie die Ausführungszeiten.<br />
Lösung<br />
//***********************************************************************<br />
// Vergleich der rekursiven und iterativen<br />
// Berechnung der Ackermann-Funktion<br />
//***********************************************************************<br />
#include <br />
#include <br />
#include <br />
#define SMAX 100000<br />
//***********************************************************************<br />
// Iterative Berechnung der Ackermann-Funktion<br />
//-----------------------------------------------------------------------<br />
int ak_i(int x, int y) {<br />
int k, s[SMAX+2], sp=0; // Stack und Stack-Pointer<br />
s[sp++]=x; s[sp++]=y; // x und y in Stack eintagen<br />
while(sp!=1){<br />
//printf("\nS:"); for(k=0; k=SMAX) return -1; // Stack-Überlauf<br />
}<br />
return s[--sp]; // Ergebnis: letzter Stack-Eintrag<br />
}<br />
//***********************************************************************<br />
// Rekursive Berechnung der Ackermann-Funktion<br />
//-----------------------------------------------------------------------<br />
int ak_r(int x, int y) {<br />
if(x==0) return(y+1); // Abbruchkriterium<br />
if(y==0) return(ak_r(x-1,1)); // einfache Rekursion<br />
return(ak_r(x-1,ak_r(x,y-1))); // doppelte Rekursion<br />
}<br />
//***********************************************************************<br />
// Hauptprogramm<br />
//***********************************************************************<br />
int main() {<br />
int x=1,y=1, a=0; // Parameter deklarieren und vorbesetzen<br />
time_t t; // Zeit<br />
printf("\n\nACKERMANN-FUNKTION\n");<br />
while(x>0 && y>0) { // Solange x und y nicht 0 sind<br />
printf("\nx, y = ");<br />
scanf("%d,%d",&x,&y); // Eingabe von x und y<br />
t=clock(); // Anfangszeit merken<br />
printf("\nak_r = %d ",ak_r(x,y)); // ak(x,y) rekursiv<br />
printf("(%5.2f sec)\n",(float)difftime(clock(),t)/CLOCKS_PER_SEC);<br />
t=clock(); // Anfangszeit merken<br />
a=ak_i(x,y); // ak(x,y) iterativ<br />
if(a
Aufgaben und <strong>Lösungen</strong> 2-17<br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
Aufgabe 10.4 (M2)<br />
Beweisen Sie durch vollständige Induktion:<br />
n<br />
n<br />
3<br />
i = i<br />
⎛<br />
⎜<br />
⎝<br />
∑ ∑<br />
i=<br />
1 i=<br />
1<br />
Lösung<br />
⎞<br />
⎟<br />
⎠<br />
Aufgabe 10.5 (M2)<br />
2<br />
Der übliche Algorithmus zur Multiplikation zweier n×n-Matrizen hat die Komplexität O(n 3 ). Nach<br />
dem Verfahren von Strassen lassen sich zwei n×n-Matrizen <strong>mit</strong> der Komplexität O(n ld(7) ) multiplizieren.<br />
Für n=10 benötige der übliche Algorithmus auf einem bestimmten Rechner 0.1 Sekunden<br />
und der Strassen-Algorithmus 0.12 Sekunden.<br />
a) Um wie viele Sekunden arbeitet der Strassen-Algorithmus schneller als der übliche Algorithmus,<br />
wenn n=100 ist?<br />
b) Wie groß muss n sein, da<strong>mit</strong> der Strassen-Algorithmus auf dem gegebenen Rechner doppelt<br />
so schnell abläuft wie der konventionelle Algorithmus?<br />
Lösung<br />
a) TM = cM⋅n 3 konventioneller Algorithmus<br />
TM(10) = cM⋅10 3 sec = 0.1 sec, also cM=0.1/1000=10 -4<br />
TM(100) = 10 -4 ⋅100 3 sec = 100 sec<br />
TS = cS⋅n ld(7) = cS ⋅n 2.807<br />
TS(10) = cS⋅10 2.807 sec = 0.12 sec, also cS≈0.12/641.21 ≈ 1.87⋅10 -4<br />
TS(100) = 1.87⋅10 -4 ⋅100 2.807 sec ≈ 1.87⋅10 -4 ⋅4.1115⋅10 5 sec ≈ 76.9sec<br />
Strassen-Algorithmus<br />
Differenz: D = TM(100)-TS(100) = (100-76.9) sec = 23.1 sec<br />
Für n=100 arbeitet der Strassen-Algorithmus also um 23.1 sec schneller als der konventionelle<br />
Algorithmus.<br />
b) cM⋅n 3 = 2⋅ cS⋅n 2.807<br />
n 3-2.807 = n 0.193 = 2⋅ cS/cM = 2⋅1.87⋅10 -4 /10 -4 = 3.74<br />
n = 3.74 1/0.193 ≈ 3.74 5.1813 ≈ 929<br />
Erst für n=929 arbeitet der Strassen-Algorithmus doppelt so schnell wie der konventionelle<br />
Algorithmus.
3-18 Aufgaben und <strong>Lösungen</strong><br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
11 Suchen und Sortieren<br />
11.1 Einfache Suchverfahren<br />
Aufgabe 11.6.1 (T1)<br />
a) Was ist eine Marke im Zusammenhang <strong>mit</strong> Suchen in Arrays?<br />
b) Ist Suchen in Arrays oder in linearen Listen effizienter durchführbar?<br />
c) Was versteht man unter Radix-Suche?<br />
d) Wodurch unterscheiden sich die sequentielle und die binäre Suche?<br />
Lösung<br />
Aufgabe 11.6.2 (P3)<br />
Vergleich von binärer Suche und Interpolationssuche.<br />
• Erstellen Sie ein Array a[] <strong>mit</strong> 30 000 Integer-Zufallszahlen. Verwenden Sie dabei die<br />
Uhrzeit als Startwert für den Zufallszahlengenerator.<br />
• Ordnen Sie das Array in aufsteigender Folge <strong>mit</strong> einer beliebigen Sortierfunktion.<br />
• Schreiben Sie eine Funktion search_bin zum binären Suchen. Suchen Sie da<strong>mit</strong> in einer<br />
eine Million mal durchlaufenen Schleife nach zufällig ausgewählten Zahlen und geben Sie<br />
die <strong>mit</strong>tlere Anzahl der Intervallteilungen sowie die <strong>mit</strong>tlere Ausführungszeit aus.<br />
• Schreiben Sie eine Funktion search_int zur Interpolationssuche. Suchen Sie da<strong>mit</strong> in<br />
einer mindesten 1 Million mal durchlaufenen Schleife nach denselben Zufallszahlen wie <strong>mit</strong><br />
der binären Suche und geben Sie auch dafür die <strong>mit</strong>tlere Anzahl der Intervallteilungen sowie<br />
die benötigte Ausführungszeit aus. Warnung: es ist auf effiziente Implementierung und<br />
auf Rundungsfehler bei der Intervallteilung zu achten.<br />
• Vergleichen und interpretieren Sie die Ergebnisse für die binäre Suche und die Interpolationsuche.<br />
Lösung<br />
//************************************************************************<br />
// Vergleich der binären Suche und der Interpolationssuche<br />
//************************************************************************<br />
#include <br />
#include <br />
#include <br />
#define MAX 30000<br />
#define ESC 27<br />
void sort_insb(long int a[], long int n);<br />
long int srch_int(long int x, long int a[], long int n);<br />
long int srch_bin(long int x, long int a[], long int n);<br />
int count;<br />
//------------------------------------------------------------------------<br />
// Vergleich der binären Suche und der Interpolationssuche durch Suche<br />
// in einem <strong>mit</strong> Zufallszahlen vorbesetzten und danach geordneten Feld.<br />
//------------------------------------------------------------------------<br />
int main() {<br />
long int c=0, i, k, a[MAX], n=MAX, m=100;<br />
float sum;
Aufgaben und <strong>Lösungen</strong> 2-19<br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
}<br />
time_t t; // Zeit<br />
printf("\nVergleich von Suchalgorithmen");<br />
printf("\n=============================");<br />
srand((unsigned)time(NULL)); // init. Zufallszahlengenerator<br />
while(c!=ESC) {<br />
for(i=0; i
3-20 Aufgaben und <strong>Lösungen</strong><br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
}<br />
long int anf=0, end=n-1, m=0; // Intervall<br />
count=0; // Schrittzähler<br />
if(xa[end])<br />
return -1; // Element zu klein oder zu groß<br />
while(anf
Aufgaben und <strong>Lösungen</strong> 2-21<br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
11.3 Gestreute Speicherung (Hashing)<br />
Aufgabe 11.3.1 (T1)<br />
a) Was ist der wesentliche Vorteil von Hash-Verfahren?<br />
b) Was versteht man unter Klumpenbildung?<br />
c) Nennen Sie einen Vorteil und einen Nachteil der quadratischen im Vergleich zur linearen<br />
Kollisionsauflösung.<br />
d) Welche Forderungen stellt man üblicherweise an Hash-Funktionen?<br />
e) Wie ist der Belegungsfaktor definiert?<br />
f) Kann der Belegungsfaktor größer als 1 werden?<br />
Lösung<br />
a) Rascher Zugriff auf die Datensätze <strong>mit</strong> wenig Schlüsselvergleichen auch bei großen Datenmengen.<br />
b) Wenn durch die Kollisionsauflösung Häufungen von Einträgen im Adressraum entstehen,<br />
spricht man von Klumpenbildung. Die lineare Kollisionsauflösung neigt zur Klumpenbildung.<br />
Der Nachteil ist, dass in Klumpen eine erhöhte Anzahl von Schlüsselvergleichen erforderlich<br />
ist.<br />
c) Die quadratische Kollisionsauflösung verteilt die Einträge gleichmäßiger über den Adressraum,<br />
die Klumpenbildung ist also geringer. Die Größe des Adressraums muss eine Primzahl<br />
sein, aber auch dann wird von einer bestimmten Primäradresse ausgehend nur die<br />
hälfte des Adressraums erfasst.<br />
d)<br />
- Die Hash-Funktion soll schnell aus dem Primärschlüssel berechnet werden können.<br />
- Ein bestehende Ordnung der Primärschlüssel soll erhalten bleiben.<br />
- Die Hash-Funktion soll die Adressen gleichmäßig über die m möglichen Adressen verteilen.<br />
- Alle Schlüssel sollen <strong>mit</strong> gleicher Wahrscheinlichkeit auftreten.<br />
- Die durch die Kollisionsbehandlung berechneten Adressen sollen gleichmäßig über den<br />
Adressraum verteilt werden.<br />
e) Der Belegungsfaktor lautet µ=n/m, wobei n die Anzahl der gespeicherten Datensätze ist<br />
und m der Umfang des Adressraums.<br />
f) Der Belegungsfaktor kann größer als 1 werden, wenn die Anzahl der gespeicherten Datensätze<br />
die Größe des Adressraums übersteigt. Dies kann dann der Fall sein, wenn der<br />
Adressraum nur für die Primäradressen gilt, der Speicherplatz für die Kollisionsauflösung<br />
aber durch dynamische Speicherplatzzuweisung erfolgt, beispielsweise bei Kollisionsauflösung<br />
durch lineare Listen.
3-22 Aufgaben und <strong>Lösungen</strong><br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
Aufgabe 11.3.2 (M1)<br />
Berechnen Sie die <strong>mit</strong>tlere Anzahl S von Vergleichen zum Auffinden eines beliebigen Datensatzes<br />
in einer Hash-Tabelle, die maximal 100 000 Datensätze enthalten kann und bereits<br />
<strong>mit</strong> 86 000 Datensätzen gefüllt ist. Dabei kann von idealen Bedingungen ausgegangen werden.<br />
Lösung<br />
1 ⎛ 1 ⎞<br />
Für ideale Verhältnisse gilt: S = ln⎜<br />
⎟<br />
μ ⎝1<br />
− μ ⎠<br />
Mit µ=n/m=86000/100000=0.86 folgt: S=1.16279·ln(7.14286)=2.28618. Es sind also im Mittel etwas<br />
mehr als zwei Vergleiche erforderlich.<br />
Aufgabe 11.3.3 (M2)<br />
Es soll eine Hash-Tabelle <strong>mit</strong> einem Adressraum von m=13 angelegt werden. Die Hash-<br />
Funktion sei definiert durch die Vorschrift:<br />
h(Name) = [pos(1. Buchstabe) + pos(2. Buchstabe) ] mod 13<br />
Dabei gibt pos() die Position des betreffenden Buchstaben im Alphabet an, also pos(A)=1 etc.<br />
a) Geben Sie den Belegungsfaktor an.<br />
b) Tragen Sie die folgenden Datensätze in der angegebenen Reihenfolge unter Verwendung<br />
der linearen und der quadratischen Kollisionsauflösung in die Hash-Tabelle ein:<br />
Hammer, Feile, Nagel, Zange, Zwinge, Raspel, Schraube, Niete, Pinsel<br />
c) Berechnen Sie die <strong>mit</strong>tlere Anzahl der Vergleiche für erfolgreiche und erfolglose Suche.<br />
Lösung<br />
1 Hammer, 2 Feile, 3 Nagel, 4 Zange, 5 Zwinge, 6 Raspel, 7 Schraube, 8 Niete, 9 Pinsel,<br />
8+1 6+5 14+1 26+1 26+23 18+1 19+3 14+9 16<br />
9 11 2 1 10 6 9 10 3<br />
Hash-Adresse Name (linear) Name (quadr.)<br />
_____________________________________________________________________<br />
1 4 Zange 4 Zange<br />
2 3 Nagel 3 Nagel<br />
3 9 Pinsel 9 Pinsel<br />
4 --- ---<br />
5 --- ---<br />
6 6 Raspel 6 Raspel<br />
7 --- 8 Niete<br />
8 --- ---<br />
9 1 Hammer 1 Hammer<br />
10 5 Zwinge 5 Zwinge<br />
11 2 Feile 2 Feile<br />
12 7 Schraube ---<br />
13 8 Niete 7 Schraube<br />
Quadr. Kollisionsaufl.: h+1, h+4, h+9, etc.
Aufgaben und <strong>Lösungen</strong> 2-23<br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
Aufgabe 11.3.4 (P3)<br />
Es soll eine Datenverwaltung unter Verwendung der gestreuten Speicherung aufgebaut werden.<br />
Die Primäradresse A soll dabei möglichst einfach unter Einhaltung der lexikografischen<br />
Ordnung aus dem Primärschlüssel der Datensätze berechnet werden. Die Kollisionsbehandlung<br />
soll <strong>mit</strong> Hilfe einfach verketteter nach dem Primärschlüssel geordneter linearer Listen<br />
erfolgen. Es sollen die Operationen Einfügen, Suchen, Löschen und Auflisten von Datensätzen<br />
realisiert werden.<br />
Lösung<br />
//*************************************************<br />
//<br />
// Hashing <strong>mit</strong> Überlaufbehandlung<br />
// durch verketteten Listen<br />
//<br />
//*************************************************<br />
#include <br />
#include <br />
#include <br />
#define DIM 20<br />
#define ANZ 100<br />
int eingabe(char text[], int l);<br />
int auflisten(void);<br />
int einfuegen(void);<br />
int hash(char *text);<br />
int suchen(void);<br />
int loeschen(void);<br />
struct rec { // Datensatz<br />
char info[DIM]; // Informationsteil<br />
struct rec *pnt; // Zeiger<br />
};<br />
struct rec *hfeld[ANZ]; // Hash-Feld, Zeiger<br />
//**************************************************<br />
// Eingabefunktion <strong>mit</strong> Überlaufsicherung<br />
//--------------------------------------------------<br />
int eingabe(char text[], int len) {<br />
int i=0, e=0, c=1;<br />
if(len>=DIM) { len=DIM-1; e=-1; } // Maximallänge<br />
while(i
3-24 Aufgaben und <strong>Lösungen</strong><br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
}<br />
//**************************************************<br />
// Auflisten der Daten<br />
//--------------------------------------------------<br />
int auflisten(void) {<br />
int i, e=-1;<br />
struct rec *pnt;<br />
printf("\nDatensätze:\n");<br />
for(i=0; iinfo);<br />
pnt=pnt->pnt;<br />
}<br />
}<br />
}<br />
if(e) { printf("Datei ist leer!\n"); return(-1); }<br />
printf("\n");<br />
return(0);<br />
}<br />
//**************************************************<br />
// Suchen eines Datensatzes<br />
//--------------------------------------------------<br />
int suchen(void) {<br />
char text[DIM];<br />
struct rec *pnt;<br />
printf("\nGeben Sie den Datensatz ein: ");<br />
eingabe(text,DIM); // Zu suchender Datensatz<br />
pnt=hfeld[hash(text)]; // Primäradresse<br />
while(strcmp(text,pnt->info) && pnt) pnt=pnt->pnt;<br />
if(pnt!=NULL) printf("\nGefunden: %s\n",pnt->info);<br />
else printf("\nNicht gefunden: %s\n",text);<br />
return(0);<br />
}<br />
//**************************************************<br />
// Einfügen eines Datensatzes<br />
//--------------------------------------------------<br />
int einfuegen(void) {<br />
char text[DIM];<br />
int i;<br />
struct rec *neu, *pnt, *vor;<br />
printf("\nEingabe, beenden <strong>mit</strong> ");<br />
for(;;) {<br />
printf("\n> "); // Prompt<br />
eingabe(text,DIM); // Eingabe<br />
if(text[0]==0) return(0); // Eingabe beenden<br />
i=hash(text); // Primäradresse<br />
pnt=vor=hfeld[i];<br />
neu=(struct rec *)malloc(sizeof(struct rec));<br />
strcpy(neu->info,text);<br />
if(strcmp(text,pnt->info)pnt=hfeld[i]; // Neue Wurzel<br />
hfeld[i]=neu;<br />
}<br />
else {<br />
while(strcmp(text,pnt->info)>0 && pnt) {<br />
vor=pnt; // Einfügestelle
Aufgaben und <strong>Lösungen</strong> 2-25<br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
pnt=pnt->pnt;<br />
}<br />
vor->pnt=neu; // In Liste einfügen<br />
neu->pnt=pnt;<br />
}<br />
}<br />
}
3-26 Aufgaben und <strong>Lösungen</strong><br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
11.4 Direkte Sortierverfahren<br />
Aufgabe 11.4.1 (T1)<br />
a) Welches direkte Sortierverfahren benötigt im Mittel am wenigsten Schlüsselvergleiche?<br />
b) Welches direkte Sortierverfahren benötigt im Mittel am wenigsten Zuweisungen?<br />
c) Welches direkte Sortierverfahren arbeitet für bereits sortierte Daten am schnellsten?<br />
Aufgabe 11.4.2 (P2)<br />
Implementieren Sie die Sortierverfahren direktes Einfügen, direktes binäres Einfügen, direktes<br />
Auswählen, Bubble-Sort und Shaker-Sort. Erzeugen Sie Zufallsfolgen von double-<br />
Zahlen, sortieren Sie diese <strong>mit</strong> allen implementierten Sortierfunktionen und geben Sie die<br />
Sie jeweiligen die Laufzeiten aus.<br />
Aufgabe 11.4.3 (P3)<br />
Schreiben Sie unter Verwendung des Bubble-Sort ein Programm zum Sortieren einer linearen<br />
Liste am Platz, d.h. ohne wesentlichen zusätzlichen Speicherplatz. Der Bubble-Sort ist<br />
die einzige Sortier-Methode, die dies leistet.<br />
Lösung<br />
//***********************************************************************<br />
// Am-Platz-Sortieren einer linearen List durch Bubble Sort BUBBLE_L.C<br />
//***********************************************************************<br />
#include <br />
#include <br />
struct element { int key; struct element *next; }; // Element<br />
//***********************************************************************<br />
// Ausgabe der linearen Liste<br />
//-----------------------------------------------------------------------<br />
int list(struct element *h) {<br />
struct element *p; // Laufzeiger<br />
printf("\n");<br />
if(h->next==NULL) { printf("Liste ist leer\n"); return(-1); }<br />
p=h->next;<br />
while(p) { // durchlaufe gesamte Liste<br />
printf("%i ",p->key); // Ausgabe eines Elements<br />
p=p->next; // weiter zum nächsten Element<br />
}<br />
return(0);<br />
}<br />
//***********************************************************************<br />
// Anfügen eines Elementes<br />
//-----------------------------------------------------------------------<br />
void add(struct element *h) {<br />
char c[80]="0";<br />
struct element *p;<br />
printf("\nEingabe beenden <strong>mit</strong> q\n");<br />
while(c[0]!='q') {<br />
printf("Eingabe: ");<br />
scanf("%s",c); // Element einlesen<br />
if(c[0]!='q') {<br />
p=(struct element *) malloc(sizeof(struct element));<br />
p->key=atoi(c); // in Integer umwandeln und am Kopf einfügen
Aufgaben und <strong>Lösungen</strong> 2-27<br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
}<br />
}<br />
}<br />
return;<br />
p->next=h->next;<br />
h->next=p;<br />
//***********************************************************************<br />
// Austauschen zweier aufeinanderfolgender Elemente einer linearen Liste<br />
//-----------------------------------------------------------------------<br />
struct element *swap(struct element *p) {<br />
struct element *t; // temporäres Element<br />
t=p;<br />
p=p->next;<br />
t->next=p->next;<br />
p->next=t;<br />
return(p);<br />
}<br />
//***********************************************************************<br />
// Bubble-Sort für lineare Liste<br />
//-----------------------------------------------------------------------<br />
void sort(struct element *h) {<br />
struct element *p,*q; // Laufzeiger<br />
char go=1; // Flag wird 0, wenn die Liste sortiert ist<br />
while(go) { // solange die Liste noch nicht sortiert ist<br />
go=0;<br />
if(!(q=h->next)) return; // prüfe erstes Element<br />
if(q->key > q->next->key) { // vergleiche <strong>mit</strong> zweitem Element<br />
go=1;<br />
h->next=swap(q); // und tausche, wenn dieses kleiner ist<br />
}<br />
q=h->next; p=q->next; // betrachte die folgenden Elemente<br />
while(p->next!=NULL) { // durchlaufe die gesamte Liste<br />
if(p->key > p->next->key) { // vergleiche <strong>mit</strong> nächstem Element<br />
go=1;<br />
q->next=swap(q->next); // und tausche, wenn dieses kleiner ist<br />
}<br />
q=q->next; p=q->next; // gehe zum nächsten Element<br />
}<br />
}<br />
return;<br />
}<br />
//***********************************************************************<br />
// Hauptprogramm<br />
//***********************************************************************<br />
void main() {<br />
struct element *head; // Listenkopf<br />
printf("\n\nBUBBLE SORT IN VERKETTETER LISTE \n");<br />
head=(struct element *) malloc(sizeof(struct element));<br />
head->next=NULL;<br />
for(;;) { // Arbeitsschleife<br />
printf("\n\n1: anhängen 2: sortieren 3: auflisten 4: ende\n");<br />
switch(getch()) { // Kommando lesen<br />
case '1': add(head); break;<br />
case '2': sort(head); break;<br />
case '3': list(head); break;<br />
case '4': case 27: return;<br />
default: break;<br />
}<br />
}<br />
}
3-28 Aufgaben und <strong>Lösungen</strong><br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
11.5 Höhere Sortierverfahren<br />
Aufgabe 11.5.1 (T1)<br />
a) Was bedeutet die Aussage, dass der Quick-Sort entarten kann?<br />
b) Welches Sortierverfahren benötigt die geringste Anzahl von Schlüsselvergleichen?<br />
Aufgabe 11.5.2 (P2)<br />
Modifizieren Sie die im Text angegebene Funktion quicksort(..) so, dass<br />
sie dasselbe Interface erhält, wie die in der C-Bibliothek enthaltene Funktion qsort(..).<br />
Also int quicksort(void *a, int n, size_t w, int (*cmp)())<br />
Vergleichen Sie die Performance Ihres Programm <strong>mit</strong> der C-Funktion qsort(..).<br />
Aufgabe 11.5.3 (P2)<br />
Schreiben Sie unter Verwendung der C-Funktion qsort() ein Programm zum Sortieren<br />
eines Feldes von Zeigern, die auf Strings deuten. Die Strings sollen dabei in absteigender<br />
Reihenfolge lexikografisch sortiert werden, also c vor b vor a etc.<br />
Aufgabe 11.5.4 (M2)<br />
Ein Test habe ergeben, dass in Abhängigkeit von der Anzahl n der Daten für das Sortieren<br />
von Integer-Arrays <strong>mit</strong> dem Insertion-Sort auf einer bestimmten Maschine ti=2.8⋅n 2 ⋅10 -5 Sekunden<br />
benötigt werden. Mit dem Quick-Sort benötigt man tq=1.4n⋅ld(n)⋅10 -4 Sekunden. Von<br />
welchem n ab ist der Quick-Sort schneller als der Insertion-Sort?<br />
Lösung<br />
2.8⋅n 2 ⋅10 -5 =1.4n⋅ld(n)⋅10 -4 ist nach n aufzulösen. Es folgt:<br />
0.2⋅n=ld(n) und 2 0.2n =n<br />
Wertetabelle:<br />
n 2 0.2n<br />
10 2 2 =4<br />
20 2 4 =16<br />
22 2 4.4 ≈21.1<br />
23 2 4.6 ≈24.25 ab n=23 ist der Quick-Sort schneller
Aufgaben und <strong>Lösungen</strong> 2-29<br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
11.6 Sortieren externer Dateien<br />
Aufgabe 11.6.1 (T1)<br />
a) Was ist ein nicht-flüchtiger Speicher?<br />
b) Was ist sequentieller und halbsequentieller Speicherzugriff?<br />
c) Was versteht man unter der Zykluszeit im Zusammenhang <strong>mit</strong> Speicherzugriffen?<br />
d) Was bewirkt eine Defragmentierung?<br />
e) Was ist der Interleave-Faktor?<br />
Aufgabe 11.6.2 (T1)<br />
a) Grenzen Sie die Begriffe direktes und natürliches Mischen ab.<br />
b) Um welchen Faktor könnte das Sortieren durch Mischen schneller werden, wenn man anstelle<br />
von zwei Sequenzen jeweils vier Sequenzen mischt?<br />
c) Nennen Sie den wesentlichen Vorteil des Ein-Phasen-Mischens im Vergleich zum Zwei-<br />
Phasen-Mischen.<br />
Aufgabe 11.6.3 (L1)<br />
Sortieren Sie die Daten a = { 27, 31, 11, 42, 89, 16, 17, 14, 12, 64, 50, 61, 72, 26, 28, 32, 66, 19, 22,<br />
83, 87, 99 } nach dem in Abbildung 11.6.3 vorgeführten Muster per Hand unter Verwendung<br />
des natürlichen Mischens <strong>mit</strong> zwei Hilfsbändern b und c.<br />
Aufgabe 11.6.4 (P4)<br />
Schreiben Sie eine Funktion zum Sortieren durch Mischen eines Arrays am Platz. Es darf<br />
also kein wesentlicher zusätzlicher, von der Anzahl n der Daten abhängiger Speicherplatz<br />
verwendet werden.
3-30 Aufgaben und <strong>Lösungen</strong><br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
12 Bäume und Graphen<br />
12.1 Binärbäume<br />
Aufgabe 12.1.1 (T1)<br />
a) Was ist ein Nullbaum?<br />
b) Wodurch ist ein innerer Knoten definiert?<br />
c) Was versteht man unter Fädelung?<br />
d) Was versteht man unter Preorder, Inorder und Postorder?<br />
e) Beschreiben Sie das Ordnungskriterium von binären Suchbäumen.<br />
Lösung<br />
Aufgabe 12.1.2 (L2)<br />
Gegeben sei der nebenstehende Binärbaum.<br />
a) Geben Sie die Tiefe des Baumes an.<br />
b) Handelt es sich um einen<br />
erweiterten Binärbaum?<br />
c) Handelt es sich um einen<br />
vollständigen Binärbaum?<br />
d) Geben Sie die Durchsuchungslisten an:<br />
- Hauptreihenfolge (Preorder)<br />
- Nebenreihenfolge (Postorder)<br />
- symmetrischer Reihenfolge (Inorder)<br />
- Ebenenreihenfolge (Levelorder)<br />
Lösung<br />
4<br />
2 6<br />
1 3 5 7 9<br />
8<br />
11<br />
10 12<br />
a) Die Tiefe t eines Baumes ist definiert als die Anzahl der Knoten des längsten Astes. Hier<br />
ist also t=4.<br />
b) Für einen erweiterten Binärbaum gilt, dass jeder Knoten entweder keinen Nachfolger hat<br />
oder zwei hat. Der abgebildete <strong>Teil</strong>baum ist also kein erweiterter Binärbaum, da der Knoten<br />
10 nur einen Nachfolger hat.<br />
c) Bei einem vollständigen Binärbaum sind alle Ebenen (Niveaus), evtl. <strong>mit</strong> Ausnahme der<br />
letzten, vollständig besetzt. Hier sind die Ebenen 0, 1 und 2 vollständig besetzt, die letzte<br />
Ebene 3 ist nicht vollständig besetzt. Es handelt sich also um einen vollständigen Baum.<br />
d) Hauptreihenfolge, Preorder: Wurzel, linker <strong>Teil</strong>baum, rechter <strong>Teil</strong>baum<br />
8,4,2,1,3,6,5,7,11,10,9,12<br />
Nebenreihenfolge, Postorder: linker TB, rechter TB, Wurzel<br />
1,3,2,5,7,6,4,9,10,12,11,8<br />
Symmetrische Reihenfolge, Inorder: linker TB, Wurzel, rechter TB<br />
1,2,3,4,5,6,7,8,9,10,11,12<br />
Ebenenreihenfolge, Levelorder: Ebenenweise von links nach rechts
Aufgaben und <strong>Lösungen</strong> 2-31<br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
8,4,11,2,6,10,12,1,3,5,7,9<br />
Aufgabe 12.1.3 (P2)<br />
In einem verkettet gespeicherten Binärbaum seien numerische Werte gespeichert. Schreiben<br />
Sie ein Programm zur Durchsuchung dieses Baumes in Hauptreihenfolge, das folgende Informationen<br />
ausgibt: Anzahl der besuchten Knoten, kleinster Inhalt, größter Inhalt, Mittelwert<br />
aller Inhalte.<br />
Programmieren Sie eine rekursive und eine iterative Varianten <strong>mit</strong> folgender Knoten-Struktur:<br />
struct node { double value; struct node *l; struct node *r; };<br />
Lösung<br />
//******************************************************************<br />
// Ausgabe eines Baumes in Hauptreihenfolge (Preorder)<br />
// Rekursive Variante.<br />
// Rückgabewert: Anzahl der besuchten Knoten.<br />
//******************************************************************<br />
int tree_preorder_rec(struct node *w) {<br />
int cnt=0;<br />
if(w==NULL) return cnt; // Der Baum ist leer<br />
printf("%d ",h->info); // Behandle (drucke) den Knoten<br />
cnt++; // Knotenzähler hochzählen<br />
if(w->l) cnt+=tree_preorder(w->l); // behandle linken Nachfolger<br />
if(w->r) cnt+=tree_preorder(w->r); // behandle rechten Nachfolger<br />
return cnt;<br />
}<br />
//******************************************************************<br />
// Ausgabe eines Baumes in Hauptreihenfolge (Preorder)<br />
// Iterative Variante unter Verwendung eines Stacks.<br />
// MAXDEPTH gibt die maximale Tiefe des Baums an.<br />
// Rückgabewert: Anzahl der besuchten Knoten.<br />
//******************************************************************<br />
int tree_preorder_iter(struct node *w) {<br />
struct node h, *stack[MAXDEPTH+1]={NULL};<br />
int s=0,cnt=0;<br />
if(w==NULL) return cnt; // Der Baum ist leer<br />
h=w;<br />
do {<br />
do {<br />
if(h->r) stack[++t]=h->r; // rechten Nachfoler in Stack<br />
printf("%d ",h->info); // Behandle (drucke) den Knoten<br />
cnt++; // Knotenzähler hochzählen<br />
} while (h=h->l); // gehe zu linkem Nachfolger<br />
} while (h=stack[s--]); // hole Zeichen aus dem Stack<br />
return cnt;<br />
}
3-32 Aufgaben und <strong>Lösungen</strong><br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
Aufgabe 12.1.4 (L2)<br />
Ordnen Sie die in der Reihenfolge {6, 9, 3, 7, 13, 21, 10, 8, 11, 14, 5} gegebenen Zahlen als<br />
binären Suchbaum an.<br />
Lösung<br />
6 6 6<br />
Aufgabe 12.1.4 (P4)<br />
8 10 21<br />
Binärer Suchbaum<br />
11 14<br />
6<br />
6<br />
3 9<br />
Schreiben Sie ein Programm zum Verwalten eines binären Suchbaums. Es sollen die Funktionen<br />
Initialisieren des Baums, Eingabe, Suchen und Löschen eines Knotens und Ausgabe<br />
der Knoteninhalte in Haupt-, Neben- und symmetrischer Reihenfolge. Verwenden Sie dabei<br />
die Knotenstruktur<br />
struct node { int info; struct node *l; struct node *r; };<br />
Lösung<br />
9<br />
//************************************************************************<br />
// Programm zur Verwaltung eines binären Suchbaumes<br />
// <strong>mit</strong> Eingabe, Suchen, Löschen von Knoten und Ausgabe in<br />
// Hauptreihenfolge, symmetrischer Reihenfolge und Nebenreihenfolge.<br />
//************************************************************************<br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
#include <br />
3 9<br />
3<br />
9<br />
6 6 6<br />
3 9<br />
6<br />
7 13<br />
3 9<br />
13<br />
6<br />
7<br />
3 9<br />
5 7 13<br />
3 9<br />
13<br />
6<br />
7<br />
3 9<br />
7 13<br />
7<br />
3 9<br />
13<br />
8 10 21<br />
6<br />
7<br />
3 9<br />
7 13<br />
8 10 21<br />
11 11 14
Aufgaben und <strong>Lösungen</strong> 2-33<br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
#define CR 13<br />
#define ESC 27<br />
#define BLNK 32<br />
#define DIM 80<br />
#define MAXDEPTH 10<br />
struct node { int key; // Schlüssel (Informationsteil)<br />
struct node *l,*r; // Zeiger auf die Nachfolger<br />
}; // Baumstruktur<br />
int getkey(); // Funktionsprototypen<br />
void prelist(struct node *p);<br />
void inlist(struct node *p);<br />
void postlist(struct node *p);<br />
int prelist_iter(struct node *p);<br />
struct node *node_init(int key);<br />
int tree_search(struct node *p, int key);<br />
int tree_del(struct node *p, int key);<br />
int tree_in(struct node *root, int key);<br />
void tree_free(struct node *p);<br />
//------------------------------------------------------------------------<br />
// Hauptprogramm<br />
//------------------------------------------------------------------------<br />
int main() {<br />
printf("\nEingabe und Ausgabe von binaeren Suchbaeumen");<br />
printf("\n============================================\n");<br />
int c=0, i, e, key;<br />
char buffer[DIM+1];<br />
struct node *root=NULL, *p;<br />
while(c!=ESC) { // Arbeitsschleife<br />
printf("\n\nNeuer Baum (n):"); // Menü<br />
printf("\nKnoten eingeben (e):");<br />
printf("\nAusgabe (a):");<br />
printf("\nSuchen (s):");<br />
printf("\nLoeschen (l):");<br />
printf("\nBeenden : ");<br />
c=toupper(getkey()); // Kommando lesen<br />
if(c==ESC) return 0; // Programm beenden<br />
////////////////////////////////////////// Eingabe<br />
if(c=='N' || c=='E') {<br />
if(c=='E') e=1; else e=0;<br />
i=1;<br />
if(e) { // Eingabe eines Knotens<br />
if(root==NULL) {<br />
printf("\n\nDer Baum ist nicht initialisiert!");<br />
i=0;<br />
}<br />
else<br />
printf("\n\nEingabe eines Schluessels (Integer-Zahl):");<br />
}<br />
else { // Anlegen eines neuen Baums<br />
tree_free(root); // alten Baum löschen<br />
root=node_init(0); // Baum initialisieren<br />
if(root==NULL) { // nicht genug Speicher<br />
printf("\n\nNicht genug Speicher!");<br />
i=0;<br />
}<br />
else {<br />
printf("\n\nEs wird ein neuer Baum aufgebaut.\nEingabe ");<br />
printf("von Schluesseln (Integer-Zahlen), beenden <strong>mit</strong> :");<br />
}<br />
}
3-34 Aufgaben und <strong>Lösungen</strong><br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
if(i) while(c!=ESC) { // Schleife für Eingabe<br />
printf("\n> "); // neues Element<br />
i=0;<br />
while((c=getkey())!=CR) { // Zeichen lesen<br />
if(i==DIM) break; // Eingabe zu lang<br />
if(c>=BLNK) { // druckbares Zeichen<br />
putch(c); // .. anzeigen<br />
buffer[i++]=c; // .. in Puffer kopieren<br />
}<br />
else if(c==CR || c==ESC) break; // Eingabe beenden<br />
}<br />
if(i>0) {<br />
buffer[i]=0; // Puffer abschließen<br />
key=atoi(buffer); // Puffer in Integer konvert.<br />
i=tree_in(root,key); // Element einfügen<br />
if(i==0) { // nicht genug Speicher<br />
printf("\n\nNicht genug Speicher!");<br />
break;<br />
}<br />
}<br />
if(e) c=ESC;<br />
}<br />
}<br />
////////////////////////////////////////// Ausgabe des Baums<br />
else if(c=='A') {<br />
if(root==NULL) printf("\n\nDer Baum ist nicht initialisiert!");<br />
else if(root->l==NULL) printf("\n\nDer Baum ist leer!");<br />
else {<br />
printf("\n\nAusgabe:\n");<br />
printf("\nHauptreihenfolge: ");<br />
prelist_iter(root->l);<br />
printf("\nSym. Reihenfolge: ");<br />
inlist(root->l);<br />
printf("\nNebenreihenfolge: ");<br />
postlist(root->l);<br />
}<br />
}<br />
////////////////////////////////////////// Suchen eines Schlüssels<br />
else if(c=='S') {<br />
if(root==NULL) printf("\n\nDer Baum ist nicht initialisiert!");<br />
else if(root->l==NULL) printf("\n\nDer Baum ist leer!");<br />
else {<br />
printf("\n\nBitte zu suchenden Schluessel eingeben: ");<br />
scanf("%d",&key);<br />
i=tree_search(root,key);<br />
if(i==-1) printf("\nSchluessel %d nicht gefunden.",key);<br />
else printf("\nSchluessel %d nach %d Schritten gefunden.",key,i);<br />
}<br />
}<br />
////////////////////////////////////////// Löschen eines Schlüssels<br />
else if(c=='L') {<br />
if(root==NULL) printf("\n\nDer Baum ist nicht initialisiert!");<br />
else if(root->l==NULL) printf("\n\nDer Baum ist leer!");<br />
else {<br />
printf("\n\nBitte zu loeschenden Schluessel eingeben: ");<br />
scanf("%d",&key);<br />
i=tree_del(root,key);<br />
if(i==-1) printf("\nSchluessel %d nicht gefunden.",key);<br />
else printf("\nSchluessel %d nach %d Schritten geloescht.",key,i);<br />
}<br />
}<br />
else printf("\n\nFalsche Eingabe!"); // Falsche Eingabe<br />
c=0;
Aufgaben und <strong>Lösungen</strong> 2-35<br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
}<br />
}<br />
tree_free(root); // Speicher freigeben<br />
return 0;<br />
//------------------------------------------------------------------------<br />
// Ein Zeichen von der Tastatur lesen.<br />
// Hinweis: Manche Sonderzeichen bestehen aus zwei Byte, wobei das erste<br />
// Byte den Wert 0 oder 224 hat.<br />
// Rückgabe-Wert:<br />
// 8-Bit ASCII-Code des Zeichens oder der negative Wert des Codes,<br />
// wenn es sich um ein Sonderzeichen aus zwei Byte handelt.<br />
//------------------------------------------------------------------------<br />
int getkey() {<br />
int i;<br />
i=getch(); // erstes Byte<br />
if(kbhit()) return -getch(); // noch ein Byte?<br />
return i;<br />
}<br />
//------------------------------------------------------------------------<br />
// Ausgabe eines Baumes in Hauptreihenfolge (Preorder)<br />
//------------------------------------------------------------------------<br />
void prelist(struct node *p) {<br />
printf("%d ",p->key); // behandle Wurzel (drucke key)<br />
if(p->l) prelist(p->l); // behandle linken Nachfolger<br />
if(p->r) prelist(p->r); // behandle rechten Nachfolger<br />
}<br />
//------------------------------------------------------------------------<br />
// Ausgabe eines Baumes in symmetrischer Reihenfolge (Inorder)<br />
//------------------------------------------------------------------------<br />
void inlist(struct node *p) {<br />
if(p->l) inlist(p->l); // behandle linken Nachfolger<br />
printf("%d ",p->key); // behandle Wurzel (drucke key)<br />
if(p->r) inlist(p->r); // behandle rechten Nachfolger<br />
}<br />
//------------------------------------------------------------------------<br />
// Ausgabe eines Baumes in Nebenreihenfolge (Postorder)<br />
//------------------------------------------------------------------------<br />
void postlist(struct node *p) {<br />
if(p->l) postlist(p->l); // behandle linken Nachfolger<br />
if(p->r) postlist(p->r); // behandle rechten Nachfolger<br />
printf("%d ",p->key); // behandle Wurzel (drucke key)<br />
}<br />
//------------------------------------------------------------------------<br />
// Ausgabe eines Baumes in Hauptreihenfolge - iterative Lösung<br />
// Rückgabewert: 0 für normale Ausführung<br />
// -1 bei Stack-Überlauf<br />
//------------------------------------------------------------------------<br />
int prelist_iter(struct node *p) {<br />
struct node *s[MAXDEPTH]={NULL}; // Stack<br />
int t=0; // Stack-Pointer<br />
if(p==NULL) return 0; // der Baum ist leer<br />
while(p) { // Arbeitsschleife<br />
while(p) { // gehe nach links bis zu einem Blatt<br />
if(p->r) { // gibt es einen rechten Nachfolger?<br />
if(++t==MAXDEPTH) return -1; // Stack-Überlauf<br />
s[t]=p->r; // rechten Nachfolger in Stack<br />
}<br />
printf("%d ",p->key); // bearbeite Wurzel (drucke key)
3-36 Aufgaben und <strong>Lösungen</strong><br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
}<br />
p=p->l; // weiter zum linken Nachfolger<br />
}<br />
p=s[t--]; // Knoten aus Stack holen<br />
}<br />
return 0;<br />
//------------------------------------------------------------------------<br />
// Einfügen eines Elementes in einen Binärbaum<br />
// Rückgabewert: 0 bei normaler Ausführung<br />
// -1 wenn nicht genug Speicher<br />
//------------------------------------------------------------------------<br />
int tree_in(struct node *root, int key) {<br />
int dir=0;<br />
struct node *p, *v=root, *k=root->l;<br />
p=node_init(key); // neuen Knoten erzeugen<br />
if(p==NULL) return -1; // nicht genug Speicher<br />
while(k) { // .. Einfügestelle suchen<br />
v=k; // Vorgänger<br />
if(keykey) {<br />
k=k->l; // Verzweige nach links<br />
dir=0; // Verzweigungsrichtung merken<br />
}<br />
else { // Verzweige nach rechts<br />
k=k->r;<br />
dir=1; // Verzweigungsrichtung merken<br />
}<br />
}<br />
if(dir) v->r=p; // Einfügen von p als rechten ..<br />
else v->l=p; // .. oder linken Nachfolger<br />
}<br />
//------------------------------------------------------------------------<br />
// Suchen eines Schlüssels key in einem Binärbaum<br />
// Rückgabewert: Anzahl der Schritte<br />
// -1 wenn nicht gefunden<br />
//------------------------------------------------------------------------<br />
int tree_search(struct node *root, int key) {<br />
int cnt=1;<br />
struct node *k=root->l; // Start <strong>mit</strong> der Wurzel<br />
while(k) { // Schlüssel key suchen<br />
if(k->key==key) return cnt; // Schlüssel gefunden<br />
else if(key < k->key) k=k->l; // gehe zum linken Nachfolger<br />
else k=k->r; // gehe zum rechten Nachfolger<br />
cnt++; // Schrittzähler inkrementieren<br />
}<br />
return -1; // nicht gefunden<br />
}<br />
//------------------------------------------------------------------------<br />
// Löschen eines Schlüssels key in einem Binärbaum<br />
// Rückgabewert: Anzahl der Schritte<br />
// -1 wenn nicht gefunden<br />
//------------------------------------------------------------------------<br />
int tree_del(struct node *root, int key) {<br />
int cnt=1, dir=0;<br />
struct node *v=root, *k=root->l; // Start <strong>mit</strong> der Wurzel<br />
struct node *s, *vs; // Symm. Vorgänger s und dessen Vorg.<br />
while(k) { // Schlüssel key suchen<br />
if(k->key==key) break; // Schlüssel gefunden<br />
v=k; // Vorgänger merken<br />
if(key < k->key) { // Schlüssel vergleichen<br />
k=k->l; // gehe zum linken Nachfolger
Aufgaben und <strong>Lösungen</strong> 2-37<br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
}<br />
dir=0; // Verzweigungsrichtung merken<br />
}<br />
else {<br />
k=k->r; // gehe zum rechten Nachfolger<br />
dir=1; // Verzweigungsrichtung merken<br />
}<br />
cnt++; // Schrittzähler inkrementieren<br />
}<br />
if(k==NULL) return -1; // nicht gefunden<br />
///////////////////////////////////// Blatt löschen<br />
if(k->l==NULL && k->r==NULL) {<br />
if(dir) v->r=NULL; // Blatt ist rechter Nachfolger<br />
else v->l=NULL; // Blatt ist linker Nachfolger<br />
}<br />
///////////////////////////////////// Knoten <strong>mit</strong> einem Nachf. löschen<br />
else if(k->l==NULL && k->r!=NULL) {// k hat nur rechten Nachfolger<br />
if(dir) v->r=k->r; // k ist rechter Nachfolger von v<br />
else v->l=k->r; // k ist linker Nachfolger von v<br />
}<br />
else if(k->l!=NULL && k->r==NULL) {// k hat nur linken Nachfolger<br />
if(dir) v->r=k->l; // k ist rechter Nachfolger von v<br />
else v->l=k->l; // k ist linker Nachfolger von v<br />
}<br />
///////////////////////////////////// inneren Knoten löschen<br />
else if(k->l!=NULL && k->r!=NULL) {<br />
vs=k; // Symmetrischen Vorgänger s suchen<br />
s=k->l; // gehe einen Schritt nach links<br />
while(s->r) { // gehe nach rechts bis zum Ende<br />
vs=s; // Vorgänger vs des Sym. Vorgängers<br />
s=s->r; // Symmetrischer Vorgänger s<br />
}<br />
if(vs!=k) { // Linken Nachfolger von s an Vorg. ..<br />
vs->r=s->l; // .. des Sym. Nachf. hängen<br />
s->l=k->l; // s an die Position von k bringen<br />
}<br />
s->r=k->r; // s an die Position von k bringen<br />
if(dir) v->r=s; // k ist rechter Nachfolger von v<br />
else v->l=s; // k ist linker Nachfolger von v<br />
}<br />
free(k); // Knoten k löschen<br />
return cnt;<br />
//------------------------------------------------------------------------<br />
// Knoten eines Binärbaums initialisieren.<br />
// Rückgabewert:<br />
// Zeiger auf den Knoten oder NULL, wenn nicht genug Speicherplatz.<br />
//------------------------------------------------------------------------<br />
struct node *node_init(int key) {<br />
struct node *p;<br />
p=(struct node *)malloc(sizeof(struct node));<br />
if(p==NULL) return NULL; // nicht genug Speicher<br />
p->key=key; // Schlüssel zuweisen<br />
p->l=p->r=NULL; // keine Nachfolger<br />
return p;<br />
}<br />
//------------------------------------------------------------------------<br />
// Der durch den Baum belegte Speicherplatz wird freigegeben.<br />
// Durchlaufen in Nebenreihenfolge, d.h. es werden immer Blätter gelöscht.<br />
//------------------------------------------------------------------------<br />
void tree_free(struct node *p) {<br />
if(p==NULL) return; // der Baum ist nicht initialisiert
3-38 Aufgaben und <strong>Lösungen</strong><br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
}<br />
if(p->l) tree_free(p->l); // rekursiv zum linken Nachfolger<br />
if(p->r) tree_free(p->r); // rekursiv zum rechten Nachfolger<br />
free(p); // Knoten löschen<br />
Aufgabe 12.1.6 (L2)<br />
Ein erweiterter Binärbaum ist dadurch gekennzeichnet, dass jeder Knoten entweder keinen<br />
oder zwei Nachfolger hat. Zeigen Sie: In einem erweiterten Binärbaum gilt ne=ni+1, wobei ne<br />
die Anzahl der Blätter ist und ni die Anzahl der der inneren Knoten.<br />
Lösung<br />
Die Skizze zeigt die drei kleinsten erweiterten Binärbäume, wobei die inneren Knoten als<br />
schwarze Punkte und die externen Knoten (Blätter) als Quadrate gezeichnet sind. Für diese<br />
gilt offenbar die Behauptung ne=ni+1. Ein erweiterter Binärbaum kann nur wachsen, indem<br />
ein Blatt durch einen aus Wurzel und zwei Blättern bestehenden Baum ersetzt wird. Es<br />
kommen also immer genau ein innerer Knoten und genau ein Blatt hinzu, so dass die Behauptung<br />
weiterhin erfüllt bleibt.<br />
Aufgabe 12.1.7 (P2)<br />
Schreiben Sie eine C-Funktion, die von einem verkettet gespeicherten Binärbaum die Anzahl<br />
der Knoten <strong>mit</strong> zwei Nachfolgern angibt. Die Knoten-Struktur sei:<br />
struct node { int info; struct node *l; struct node *r; };<br />
Lösung<br />
int nodes(struct node *w) {<br />
if(!w) return 0;<br />
if(w->l!=NULL && w->r!=NULL) return nodes(w->l)+nodes(w->r)+1;<br />
else if(w->l!=NULL && w->r==NULL) return nodes(w->l);<br />
else if(w->l==NULL && w->r!=NULL) return nodes(w->r);<br />
}<br />
Aufgabe 12.1.8 (P3)<br />
Schreiben Sie ein möglichst effizientes Programm, <strong>mit</strong> dem festgestellt werden kann, ob zwei<br />
lineare Listen <strong>mit</strong> jeweils n Elementen vom Typ Integer dieselben Elemente enthalten. Dabei<br />
ist nicht vorausgesetzt, dass die Elemente in den beiden Listen in derselben Reihenfolge<br />
angeordnet sind. Verwenden Sie dazu einen binären Suchbaum. Bestimmen Sie die im Mittel<br />
zu erwartende Komplexität hinsichtlich der Anzahl der Vergleiche.<br />
Lösung<br />
Aufgabe 12.1.9 (P2)<br />
Schreiben Sie unter Verwendung eines binären Suchbaums ein Programm, das alle doppelt<br />
vorkommenden Zahlen aus einem Array von Zufallszahlen löscht. Bestimmen Sie die Komplexität<br />
des Algorithmus hinsichtlich der Anzahl der Vergleiche.<br />
Lösung
Aufgaben und <strong>Lösungen</strong> 2-39<br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
/**********************************************************************/<br />
/* Elimination von doppelt vorkommenden Zahlen in DOUBLE.C */<br />
/* einem Array unter Verwendung eines binären Suchbaums. */<br />
/**********************************************************************/<br />
#include <br />
#define DIM 100<br />
struct node { int key; /* Info-<strong>Teil</strong> */<br />
struct node *l,*r; /* Zeiger auf die Nachfolger */<br />
}; /* Baumstruktur */<br />
/**********************************************************************/<br />
/* Sortiertes Kopieren eines Baumes auf ein Array (symm. Reihenfolge).*/<br />
/*--------------------------------------------------------------------*/<br />
void tree_copy(struct node *p, int a[]) {<br />
static n=0; /* Index für Array */<br />
if(p->l) tree_copy(p->l,a); /* Bearbeite linken <strong>Teil</strong>baum */<br />
a[n++]=p->key; /* Kopiere die Wurzel nach a */<br />
if(p->r) tree_copy(p->r,a); /* Bearbeite rechten <strong>Teil</strong>baum */<br />
return;<br />
}<br />
/**********************************************************************/<br />
/* Aufbau eines binären Suchaums aus einem Array a <strong>mit</strong> n Elementen. */<br />
/* Bereits vorhandene Elemente werden nicht doppelt eingetragen. */<br />
/* Rückgabewert ist die Anzahl der Elemente. */<br />
/*--------------------------------------------------------------------*/<br />
int tree_build(struct node *p, int a[], int n) {<br />
int i, r, m=0;<br />
struct knoten *v, *k;<br />
p->key=a[0]; /* Wurzel einfügen */<br />
for(i=1; ikey) break; /* Knoten schon vorhanden */<br />
v=k;<br />
if(a[i]>k->key) { k=k->r; r=1; }<br />
else { k=k->l; r=0; }<br />
}<br />
if(k==NULL) { /* Einfügen */<br />
k=(struct knoten *)malloc(sizeof(struct knoten)); /*neuer Knoten*/<br />
k->key=a[i];<br />
k->l=k->r=NULL;<br />
if(r) v->r=k; /* als rechten Nachfolger einfügen */<br />
else v->l=k; /* als linken Nachfolger einfügen */<br />
m++;<br />
}<br />
}<br />
return(m);<br />
}<br />
/**********************************************************************/<br />
/* Hauptprogramm */<br />
/**********************************************************************/<br />
void main() {<br />
int i,n;<br />
int a[DIM];<br />
struct node *head;<br />
srand(1);<br />
for(i=0; i
3-40 Aufgaben und <strong>Lösungen</strong><br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
}<br />
for(i=0; il=head->r=NULL;<br />
n=tree_build(head,a,DIM); /* Baum ohne Doppeleinträge aufbauen */<br />
tree_copy(head,a); /* Baum auf Array zurückkopieren */<br />
printf("\nErgebnis:\n");<br />
for(i=0; i
Aufgaben und <strong>Lösungen</strong> 2-41<br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
Bei der Ordnung als Min-Heap steht das kleinste Element in der Wurzel, alle weiteren Elemente<br />
sind größer oder gleich dem jeweiligen Vorgänger. Man erhält also bei schrittweisem Einfügen<br />
der Elemente:<br />
4 4 2<br />
2<br />
2<br />
2<br />
2<br />
6 6 4 6 4 5 4 5 2 5 2<br />
7<br />
7 6<br />
7 6 4 7 6 4 14<br />
2<br />
1<br />
1<br />
5 2<br />
2 2<br />
2 2<br />
7 6 4 14 5 6 4 14 5 3 4 14<br />
12<br />
12 7<br />
12 7 6<br />
1<br />
2 2<br />
1<br />
2 2<br />
1<br />
2 2<br />
5 3 4 14 5 3 4 14 5 3 4 14<br />
12 7 6 13<br />
1<br />
12 7 6 13 8<br />
12 7 6 13 8 10<br />
2 2<br />
5 3 4 14<br />
12 7 6 13 8 10 16 Min-Heap<br />
Aufgabe 12.1.12 (L2)<br />
Geben Sie ein Beispiel für einen Max-Heap <strong>mit</strong> 5 Knoten an, der gleichzeitig ein binärer<br />
Suchbaum ist. Dabei dürfen auch identische Schlüssel zugelassen werden.<br />
Lösung<br />
Bei einem binären Suchbaum gilt für den Schlüssel eines jeden Knotens, dass der Schlüssel<br />
des linken Nachfolgers kleiner (oder gleich) und der Schlüssel des rechten Nachfolgers größer<br />
(oder gleich) ist. Bei einem Max-Heap gilt eine schwächere Ordnungsbeziehung. Es wird<br />
nur gefordert, dass der Schlüssel eines jeden Knotens <strong>mit</strong><br />
Ausnahme der Wurzel kleiner (oder gleich) dem Schlüssel<br />
des Vorgängers ist. Lässt man identische Schlüssel zu,<br />
so kann man Heaps konstruieren, die gleichzeitig binäre Such-<br />
bäume sind, wie der nebenstehende Baum <strong>mit</strong> fünf Knoten zeigt.<br />
3<br />
2 3<br />
1 2
3-42 Aufgaben und <strong>Lösungen</strong><br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
12.2 Vielwegbäume<br />
Aufgabe 12.2.1 (T2)<br />
a) Was ist bei Vielwegbäumen ein Bruder?<br />
b) Wodurch unterscheidet sich ein BB-Baum von einem (2,4)-Baum?<br />
c) Was ist ein (a,b)-Baum?<br />
d) Was ist ein B*-Baum?<br />
e) Was ist eine Hecke?<br />
Lösung<br />
b) Sowohl ein BB-Baum als auch ein (2,4)-Baum ist ein B-Baum. Die Bezeichnung BB-<br />
Baum steht für (1,2)-Baum, jede Seite umfasst daher mindestens ein Element und höchstens<br />
zwei. Ein BB-Baum <strong>mit</strong> n Elementen hat ca. die doppelte Tiefe wie ein (2,4)-Baum<br />
<strong>mit</strong> derselben Anzahl von Elementen. Ein BB-Baum ist weniger für die Arbeit auf externen<br />
Speichern geeignet, sondern eher dann, wenn er komplett im Hauptspeicher gehalten<br />
werden kann.<br />
Aufgabe 12.2.2 (L2)<br />
Ordnen Sie die in der Reihenfolge {6, 9, 3, 7, 13, 21, 10, 8, 11, 14, 5} gegebenen Zahlen als<br />
(2,4)-Baum.<br />
Lösung<br />
6 6 9 3 6 9 3 6 7 9<br />
3 6<br />
3 6<br />
3 6<br />
3 6<br />
3 5 6<br />
7<br />
9 13<br />
7<br />
9 10 13 21<br />
7 10<br />
3 6<br />
8 9 11 13 21<br />
7 10<br />
3 6<br />
8 9 11 13 14 21<br />
7 10<br />
8 9 11 13 14 21<br />
7 10<br />
8 9<br />
7<br />
(2,4)-Baum<br />
9 13 21<br />
13 21
Aufgaben und <strong>Lösungen</strong> 2-43<br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
Aufgabe 12.2.3 (L3)<br />
In den unten abgebildeten (2,4)-Baum soll das Element <strong>mit</strong> Schlüssel 3 eingefügt werden.<br />
Zeichnen Sie den resultierenden Baum. Löschen Sie anschließend Das Element <strong>mit</strong> dem<br />
Schlüssel 6 und zeichnen Sie den resultierenden Baum erneut.<br />
2 4 6 8<br />
Lösung<br />
10 20 30 40<br />
12 14 16 18 22 24 26 28 32 34 36 32 42 44 46 48<br />
20<br />
4 10 30 40<br />
2 3 12 14 16 18 22 24 26 28 42 44 46 48<br />
Aufgabe 12.2.4 (L3)<br />
6 8 32 34 36 38<br />
Wie viele Elemente können in einem (2,4)-Baum der Tiefe 3 maximal und minimal enthalten<br />
sein? Die Wurzel (Niveau 0) hat die Tiefe 1.<br />
Lösung<br />
Die Maximale Anzahl ergibt sich, wenn alle Seiten der drei Niveaus <strong>mit</strong> jeweils 4 Elementen<br />
voll besetzt sind. Da dann jede Seite fünf Nachfolgeseiten hat, folgt:<br />
Niveau 0: n0=4, Niveau 1: n1=5·4, Niveau 2: n2=5·5·4, Summe: n=124<br />
Die minimale Anzahl von Elementen in einem (2,4)-Baum der Tiefe 3 ergibt sich, wenn die<br />
Wurzel (Niveau 0) <strong>mit</strong> nur einem Element besetzt ist. Daraus folgt dann, dass Niveau 1 aus<br />
nur zwei Seiten besteht. Diese können dann minimal <strong>mit</strong> je zwei Elementen besetzt sein, so<br />
dass jede Seite aus Niveau 1 auf je drei Seiten in Niveau 2 verweiset. Die Seiten in Niveau 2<br />
können wiederum minimal <strong>mit</strong> zwei Elementen besetzt sein. Insgesamt ergibt sich:<br />
Niveau 0: n0=1, Niveau 1: n1=2·2, Niveau 2: n2=2·3·3, Summe: n=17<br />
Aufgabe 12.2.5 (P4)
3-44 Aufgaben und <strong>Lösungen</strong><br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
Schreiben Sie ein Programm zum Verwalten eines (2,4)-Baums. Es sollen die Funktionen<br />
Initialisieren des Baums, Eingabe, Suchen und Löschen von Einträgen und Ausgabe der<br />
Inhalte realisiert werden.
Aufgaben und <strong>Lösungen</strong> 2-45<br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
12.3 Graphen<br />
Aufgabe 12.3.1 (T1)<br />
a) Wann ist ein Graph schlicht?<br />
b) Was ist ein Euler’scher Kreis?<br />
c) Was ist ein Hamilton’scher Kreis?<br />
d) Was ist der Grad eines Knotens?<br />
e) Was ist ein Wald?<br />
f) Was ist ein Kuratowski-Graph?<br />
Lösung<br />
Aufgabe 12.3.2 (L3)<br />
Gegeben sei der nebenstehende Graph.<br />
a) Geben Sie die Knotenliste und die Kantenliste an.<br />
b) Geben Sie den Forward Star an.<br />
c) Geben Sie die Adjazenzmatrix und die<br />
Erreichbarkeitsmatrix an.<br />
d) Listen Sie, beginnend <strong>mit</strong> Knoten E, die Knoten in der<br />
Reihenfolge gemäß Tiefensuche und Breitensuche auf.<br />
e) Zeichnen Sie den Graphen so um, dass die Knoten von links<br />
nach rechts in einer topologischen Sortierung angeordnet sind.<br />
f) Geben Sie den minimalen Spannbaum an.<br />
g) Welche der folgenden Eigenschaften treffen auf den Graphen zu: eben, zusammenhängend,<br />
stark zusammenhängend, kreisfrei, schlicht, gerichtet, vollständig, bewertet?<br />
Lösung<br />
Knotenliste Kantenliste<br />
A H<br />
B A, D, F<br />
C F<br />
D -<br />
E B, D<br />
F H<br />
G D<br />
H G<br />
Adjazenzmatrix<br />
v<br />
o<br />
n<br />
nach<br />
A B C D E F G H<br />
A 0 0 0 0 0 0 0 1<br />
B 1 0 0 1 0 1 0 0<br />
C 0 0 0 0 0 1 0 0<br />
D 0 0 0 0 0 0 0 0<br />
E 0 1 0 1 0 0 0 0<br />
F 0 0 0 0 0 0 0 1<br />
G 0 0 0 1 0 0 0 0<br />
H 0 0 0 0 0 0 1 0<br />
Tiefensuche: EBAHGFD<br />
A<br />
E<br />
3<br />
2<br />
B<br />
1<br />
4<br />
F<br />
5<br />
1<br />
C<br />
3<br />
3<br />
H<br />
1<br />
D<br />
G<br />
2
3-46 Aufgaben und <strong>Lösungen</strong><br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
Breitensuche: EBDAFHG<br />
Topologische Sortierung<br />
Minimal spannender Baum:<br />
Eigenschaften: Ja Nein<br />
eben<br />
zusammenhängend<br />
stark zusammenhängend<br />
kreisfrei<br />
schlicht<br />
gerichtet<br />
vollständig<br />
bewertet<br />
Aufgabe<br />
C E B A F H G D<br />
A<br />
E<br />
3<br />
2<br />
B<br />
1<br />
4<br />
5<br />
F<br />
a) Welche Bedingung muss erfüllt sein, da<strong>mit</strong> ein vollständiger Graph genau n(n-1)/2 Kanten<br />
hat?<br />
b) Beweisen Sie durch vollständige Induktion, dass ein vollständiger Graph mindestens<br />
n(n-1)/2 Kanten hat.<br />
c) Zeigen Sie: Jeder kreisfreie Graph <strong>mit</strong> n Knoten enthält höchstens n-1 Kanten.<br />
d) Auf welche Eigenschaft muss man die Adjazenzmatrix eines Graphen testen, um zu<br />
entscheiden, ob er schlingenfrei ist?<br />
e) Wie groß ist die minimale und wie groß ist die maximale Anzahl von Kanten eines schlichten,<br />
zusammenhängenden Graphen <strong>mit</strong> n Knoten?<br />
f) Wie viele Knoten hat ein ungerichteter, schlichter, vollständiger Graph <strong>mit</strong> 465 Kanten?<br />
g) Wie viele Kanten hat ein nicht-ebener, zusammenhängender Graph <strong>mit</strong> n Knoten<br />
mindestens?<br />
Lösung<br />
Aufgabe 12.3.4 (P2)<br />
Schreiben Sie eine C-Funktion zur topologischen Sortierung eines Digraphen <strong>mit</strong> n Knoten.<br />
Gegeben seien die Kantenliste als Array klist und die global deklarierte Adjazenzmatrix<br />
a[n][n]. Die Funktion soll als Ergebnis die Indizes der Knoten bezüglich klist in der sortierten<br />
Reihenfolge in ein Integer-Array tlist eintragen.<br />
Lösung<br />
#define DIM 20<br />
int a[DIM][DIM]; // Global deklarierte Adjazenzmatrix<br />
1<br />
C<br />
3<br />
3<br />
H<br />
1<br />
D<br />
G<br />
2
Aufgaben und <strong>Lösungen</strong> 2-47<br />
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />
int tsort(int tlist[]; int klist[]; int n) {<br />
int i, j, k=0, grad[DIM];<br />
if(n=DIM) return(-1);<br />
for(i=0; i