30.11.2012 Aufrufe

Grundkurs Informatik Aufgabensammlung mit Lösungen Teil 3

Grundkurs Informatik Aufgabensammlung mit Lösungen Teil 3

Grundkurs Informatik Aufgabensammlung mit Lösungen Teil 3

MEHR ANZEIGEN
WENIGER ANZEIGEN

Erfolgreiche ePaper selbst erstellen

Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.

<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

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

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!