Sie sind auf Seite 1von 47

Grundkurs Informatik

Aufgabensammlung mit Lösungen

Teil 3: Kapitel 9 bis 14

H. Ernst

Die Aufgaben sind nach folgendem Schema klassifiziert:

T

für Textaufgaben,

M

für mathematisch orientierte Aufgaben,

L

für Aufgaben, die logisches und kombinatorisches Denken erfordern,

P

für Programmieraufgaben.

Nach der thematischen Kennung ist der Schwierigkeitsgrad der Aufgaben angegeben:

0 bedeutet „sehr leicht“. Diese Aufgaben können unmittelbar gelöst werden, ggf. mit etwas Blättern im Buch.

1 bedeutet „leicht“ und kennzeichnet Aufgaben, die innerhalb von einigen Minuten mit wenig Aufwand zu lösen sind.

2 bedeutet „mittel“. Solche Aufgaben erfordern etwas geistige Transferleistung und/oder ei- nen größeren Arbeitsaufwand.

3 bedeutet „schwer“ und ist für Aufgaben reserviert, die erheblichen Arbeitsaufwand mit krea- tiven Eigenleistungen erfordern.

3-2

Aufgaben und Lösungen

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

9 Automatentheorie und formale Sprachen

9.1 Grundbegriffe der Automatentheorie

Aufgabe 9.1.1 (T1)

a) Was versteht man unter dem kartesischen Mengenprodukt?

b) Was ist ein Akzeptor?

c) Grenzen Sie die Begriffe Mealy- und Moore-Automat gegeneinander ab.

d) Was ist ein endlicher Übersetzer?

e) Definieren Sie den Begriff Mächtigkeit im Zusammenhang mit Automaten.

f) Was ist ein Fangzustand?

Lösung

a) Das kartesiche Mengenprodukt A×B zweier Mengen A und B ist die Menge aller geordne- ten Paare der Art ab mit aA und bB. Ist beispielsweise A={x, y} und B={1, 2, 3}, dann ist A×B = { x1, x2, x3, y1, y2, y3 }.

b) Ein Automat A(T,S,f) mit einem akzeptierten Sprachschatz L(A,sa,se) heißt Akzeptor oder erkennender Automat. Eine Folge tT* von Eingabezeichen bringt den Automaten genau vom Zustand sa in den Zustand se, wenn tL ist.

c) Hängt die Ausgabe eines Automaten A(T,S,Y,f) sowohl vom Eingabezeichen als auch vom Zustand des Automaten ab, so nennt man den Automaten einen Mealy-Automaten:

g: T×SY

Hängt das Ausgabezeichen dagegen nur vom aktuellen internen Zustand des Automaten ab, so bezeichnet man den Automaten als einen Moore-Automaten:

g: SY

d) Ein endlicher Übersetzer oder Transduktor ist ein endlicher, übersetzender Automat, der durch einen endlichen Automaten A(T,S,Y,f) dargestellt wird. Der Automat ist endlich, wenn T, S und Y endliche Mengen sind. Ein Transduktor transformiert (übersetzt) eine eingegebene Zeichenkette tT* in eine Ausgabezeichenkette yT*. Davon zu unter- scheiden sind Akzeptoren, siehe Teilaufgabe b).

e) Bei einem Automaten mit endlichem Sprachschatz L wird die Anzahl der zu L gehörenden Wörter als die Mächtigkeit |L| des Sprachschatzes bezeichnet. Ein Sprachschatz mit un- endlich vielen Wörtern hat die Mächtigkeit „abzählbar unendlich“. Allgemein bezeichnet man als die Mächtigkeit einer Menge die Anzahl der Elemente der Menge.

f) Unter einem Fangzustand (oder Fehlerzustand) versteht man einen Zustand in einem Automaten, der durch kein Eingabezeichen verlassen werden kann.

Aufgaben und Lösungen

2-3

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

Aufgabe 9.1.2 (T1)

a) Wann nennt man eine Verknüpfung assoziativ?

b) Was versteht man unter einem Erzeugendensystem?

c) Was bedeutet Konkatenation?

d) Definieren Sie den Begriff Automorphismus.

e) Was ist die Worthalbgruppe?

f) Was ist eine induzierte Halbgruppe?

Lösung

a) Durch ° sei eine Verknüpfung in einer algebraischen Struktur A definiert. Die Verknüpfung

ist assoziativ, wenn gilt: (a ° b) ° c = a ° (b ° c) a,b,cA.

b) Es sei F eine Halbgruppe und E eine Unterhalbgruppe von F. Es sei die Kleene’sche Hülle E* die Menge aller Elemente, die durch Verknüpfung beliebig vielen Elementen aus E ent- steht. Ist E*=F, so heißt Ein Erzeugendensystem von F. In diesem Sinne sind Alphabete die Erzeugenden des zugehörigen Nachrichtenraums. Ein weiteres Beispiel sind die na- türlichen Zahlen N, für welche die nur die 1 enthaltende Teilmenge mit der Addition als Verknüpfung ein Erzeugendensystem ist, da sich jede natürliche Zahl als eine Summe von 1-en darstellen lässt.

c) Das Zusammenhängen von Wörtern aus einem Nachrichtenraum wird als Konkatenation (Verkettung) bezeichnet.

d) Eine Abbildung ϕ: FH mit Halbgruppen (Gruppen) F und H heißt Homomorhismus von F in H, wenn gilt: ϕ(ab) = ϕ(a)ϕ(b) a,bF. Ist ϕ umkehrbar eindeutig, so spricht man von ei- nem Isomorphismus. Sind außerdem F und H identisch, so liegt ein Automorphismus vor.

e) Die Menge T* aller Wörter, die aus den Zeichen eines Alphabets T gebildet werden kön- nen, ist eine als Worthalbruppe bezeichnete Halbgruppe mit der Konkatenation als Ver- knüpfung.

f) Die Menge aller Äquivalenzklassen bzw. Abbildungen eines Automaten A bildet mit der Operation „hintereinander ausführen“ eine Halbgruppe. Diese wird als die durch A indu- zierte Halbgruppe bezeichnet.

Aufgabe 9.1.3 (L2)

Gegeben sei der Automat mit T={a, b, c} und S={sa ,s1, s2, se}, dessen Übergangsfunktion durch die nebenstehende Tabelle definiert ist:

Es gilt: sa=Anfangszustand, se=Endzustand

a) Zeichnen Sie den Übergangsgraphen für diesen Automaten.

b) Welche der folgenden Wörter gehören zum akzeptierten Sprachschatz dieses Automaten: abc, a 3 bc 3 , a 2 b 2 c 2 , a 3 b 2 c 2

Lösung

a) Übergangsdiagramm

b a a,c c a,b c sa se a s1 s2 c 4 b
b
a
a,c
c
a,b
c
sa
se
a s1
s2
c
4
b

b

sa

s1

s2

se

a s1

sa

s1

se

b se

s1

s1

sa

c s2

sa

se

s2

3-4

Aufgaben und Lösungen

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

b) Der Automat befindet sich im Anfangszustand s a . Die Zeichen des Wortes abc werden nun von links nach rechts zeichenweise eingegeben. Dabei ergeben sich folgende Übergänge:

s a as 1 , s 1 bs 1 , s 1 cs a ,

Insgesamt findet man also s a abcs a . Der Endzustand wird offenbar nicht erreicht, dement- sprechend gehört abc nicht zum akzeptierten Sprachschatz: abcL

Für a 3 bc 3 findet man: s a a 3 bc 3 s e , also gilt a 3 bc 3 L.

Für a 2 b 2 c 2 findet man: s a a 3 bc 3 s e , also gilt a 2 b 2 c 2 L.

Für a 3 b 2 c 2 findet man: s a a 3 bc 3 s 2 , also gilt a 3 b 2 c 2 L.

Aufgabe 9.1.4 (L2)

Geben Sie den Übergangsgraphen und die Übergangstabelle eines endlichen Automaten mit T={a, b} an, dessen akzeptierter Sprachschatz L aus der Menge aller Worte aus T* besteht, die mit a beginnen und bb nicht als Teilstring enthalten. Formulieren Sie außerdem L in der üblichen Mengenschreibweise.

Lösung

sa

se1

se2

sf

a se1

se1

se1

sf

b sf

se2

sf

sf

Einfache Lösung: L = {ax | xT*, bbx}

Besser: L = {a, (a ni b) m a k | n i N, m,kN 0 }

a,b

sa a Se1 b a b sf se2 b
sa
a Se1
b
a b
sf
se2
b

a

Aufgabe 9.1.5 (L3)

Geben Sie einen Automaten als Übergangstabelle und als Übergangsdiagramm an, der alle aus den Ziffern 1 bis 4 gebildeten natürlichen Zahlen akzeptiert, deren Stellen monoton wachsen (jede folgende Ziffer ist also größer oder gleich der vorangehenden). Ein Beispiel ist etwa die Zahl 112444. Markieren Sie dabei den Anfangszustand und den Endzustand bzw. die Endzustände.

Lösung

 

sa

s1

s2

s3

s4

sf

1 s1

 

s1

sf

sf

sf

sf

2 s2

s2

s2

sf

sf

sf

3 s3

s3

s3

s3

sf

sf

4 s4

s4

s4

s4

s4

sf

Die Zustände s1 bis s4 sind alle Endzustände.

4 sf 3 1 2 3 4 1 sa s1 2 s2 3 s3 4
4
sf
3
1
2
3
4
1
sa
s1
2 s2
3 s3
4 s4
2
4
3
4

Aufgaben und Lösungen

2-5

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

Aufgabe 9.1.6 (L4)

Gegeben sei der Automat A(T,S,f) mit T={a, b}, S={sa, se, sf } und der durch die nebenstehende Tabelle definierten Zustandsübergangsfunktion f:

a) Zeichnen Sie den zugehörigen Zustandsübergangsgraphen.

b) Wie lautet der durch A akzeptierte Sprachschatz?

c) Bestimmen Sie die Äquivalenzklassen dieses Automaten.

d) Geben Sie die zugehörige induzierte Halbgruppe an.

e) Gibt es Null- und Einselemente in der induzierten Halbgruppe?

sa se sf a sa se sf b se sf sf
sa
se
sf
a sa
se
sf
b se
sf
sf

Lösung

a)

Übergangsgraph:

a b sa
a
b
sa

b)

c)

 

sa

se

sf

Abbildung

a

sa

se

sf*

sasa, sese, sfsf

b

se

sf

sf*

sase, sesf,

sfsf

aa

sa

se

sf

ba

se

sf

sf

ab

se

sf

sf

bb

sf

sf

sf*

sasf, sesf,

sfsf

aaa

sa

se

sf

baa

se

sf

sf

aba

se

sf

sf

bba

sf

sf

sf

aab

sa

se

sf

bab

sf

sf

sf

abb

sf

sf

sf

bbb

sf

sf

sf

se
se
a b sf
a
b sf

Der akzeptierte Sprachschatz lautet: L = { a n ba m | n,mN 0 }

Die im Automaten möglichen Abbildungen lauten:

a, b

Es gibt also drei Abbildungen, die zugehörigen Äquivalenzklassen lauten:

[a]

= { a n |

nN }

[b]

= { a n ba m |

n,mN 0 } = L

[bb] = { xT*\[a]\[b] }

d) Die induzierte Halbgruppe lautet:

1. 2.
1.
2.

[a]

[b]

[bb]

[a]

[a]

[b]

[bb]

[b]

[b]

[bb]

[bb]

[bb]

[bb]

[bb]

[bb]

e) Es gibt ein Einselement, nämlich [a] und ein Nullelement, nämlich [bb].

3-6

Aufgaben und Lösungen

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

Aufgabe 9.1.7 (L3)

Beschreiben Sie ein Polynom soll als BNF-Produktion, als Syntax-Graph und als Automat. Ein Polynom besteht aus durch die Operatoren + und – verknüpften Termen. Ein Term be- steht aus einer optionalen reellen Zahl, optional multipliziert mit einer beliebig langen Folge von multiplikativ verknüpften Variablen x. Ein Term darf nicht leer sein. Beispiele für Terme: 3.5, x, -5*x*x, x*x*x. Beispiel für ein Polynom: 2 + 4*x + x*x*x - 3.5*x*x.

Die syntaktischen Variablen <Reelle Zahl> und <Variable> dürfen für BNF-Produktionen und Syntaxgraphen als bekannt vorausgesetzt werden. Das Alphabet des Automaten sei T={r, x, *, +, -}, wobei r für eine reelle Zahl und x für eine Variable steht.

Lösung

<Term> ::= <Reelle Zahl>|<Variable>[{*<Variable>}]

<Polynom> ::= <Term>[{+|-<Term>}] * Term : Variable Reelle Zahl
<Polynom> ::= <Term>[{+|-<Term>}]
*
Term
:
Variable
Reelle Zahl

Polynom

:

- + Term
-
+
Term
+, - sa se r, x +, -, * * r, x sf s1 r,
+, -
sa
se
r, x
+, -, *
*
r, x
sf
s1
r, +, -, *
r, x, +, -, *

x

 

sa

s1

se

sf

x

se

se

sf

sf

r

se

sf

sf

sf

*

sf

sf

s1

sf

+, -

sf

sf

sa

sf

Aufgaben und Lösungen

2-7

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

9.2 Turing-Maschinen

Aufgabe 9.2.1 (T1)

a) Was haben Turing-Maschinen mit Berechenbarkeit zu tun?

b) Woran ist Alan Turing gestorben?

c) Was ist ein linear beschränkter Automat?

d) Grenzen Sie die Begriffe Automat, Kellerautomat und Turing-Maschine gegeneinander ab.

e) Was ist das Spiel des Lebens?

Lösung

a) Eine Funktion f(x)=y mit x,yT* ist Turing-berechenbar, wenn es eine Turing-Maschine gibt, welche die auf dem Band gespeicherte Eingabe x in die Ausgabe y transformiert, die dann auf dem Band abgelesen werden kann. Turing-Maschinen sind Modelle für einen abstrakten Computer: alles was man prinzipiell mit einer Turing-Maschine berechnen kann, kann man auch mit einem Computer berechnen und umgekehrt.

b) Alan Turing ist im Exil in Griechenland vermutlich durch Selbstmord an Gift gestorben. Er musste England verlassen, da er wegen seiner offenkundig gewordenen Homosexualität damals in einer geheimen militärischen Tätigkeit als Sicherheitsrisiko angesehen wurde.

c) Ein linear beschränkter Automat ist eine Turing-Maschine, bei der nur ein durch die Länge des Eingabewortes beschränkter Bereich des Bandes verwendet wird. Die durch linear beschränkte Automaten akzeptierten Sprachen sind zu den in Kapitel 9.3 eingeführten kontextfreien Sprachen äquivalent, die eine wichtige Grundlage von Programmierspra- chen bilden.

d) Ein Automat hat außer den internen Zuständen keinen Speicher, er erhält von außen Ein- gabezeichen. Ein Kellerautomat hat einen einseitig unbegrenzten Speicher, der als Stack für Kellerzei- chen verwendet wird. Er enthält ebenfalls von außen Eingabezeichen. Eine Turing-Maschine hat ein beidseitig unbegrenztes lineares Speicherband, auf dem vor Start der Turingmaschine die Eingabezeichen vorhanden sein müssen. Die Turingma- schine kann dann die Zeichen vom Band lesen, Zeichen auf das Band schreiben und schrittweise nach rechts und links den Schreib/Lese-Kopf auf dem Band bewegen.

e) John von Neumann beschäftigte sich seit Anfang der 50er Jahre mit zellulären Automa- ten, die ebenfalls zur Simulation eines universellen Computers geeignet sind. Zunächst ging es dabei nur um die formale Beschreibung der Fähigkeit zur Selbstreproduktion. Eine populäre Variante zellulärer Automaten ist das Spiel des Lebens (Game of Life) von John Conway (1968). Damit lassen sich interessante dynamische Strukturen generieren. Die Regeln lauten:

Ein rechteckiges Spielfeld, etwa ein Schachbrett, wird mit Spielmarken vorbesetzt.

Jede Spielmarke mit zwei oder drei Nachbarn überlebt den aktuellen Spielschritt und bleibt für die nächste Generation erhalten.

Jede Spielmarke auf einem Feld mit vier oder mehr Nachbarn stirbt an Überbevölke- rung, d.h. sie wird in der nächsten Generation vom Spielfeld entfernt (gelöscht).

Jede Spielmarke auf einem Feld mit nur einem oder gar keinem Nachbarn stirbt an Ein- samkeit, d.h. sie wird ebenfalls gelöscht.

Auf jedem leeren, von genau drei Nachbarn umgebenen Feld, wird in der nächsten Ge- neration eine Spielmarke „geboren“. Alle anderen leeren Felder bleiben leer.

3-8

Aufgaben und Lösungen

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

Aufgabe 9.2.2 (L2)

Es sei die folgende Turing-Maschine mit den Bandzeichen {0, 1} als Übergangsdiagramm gegeben.

a) Geben Sie das dazugehörige tabellarische Turing-Programm an. b) Der Schreib/Lese-Kopf stehe auf einem mit 0en vorbesetzten Band. Was bewirkt diese Turing-Maschine?

Lösung

 

a)

1

0 1 R 2

2

0 1 L 1

3

0 1 L 2

1 1 L 3

1 1 R 2

1 1 R 0

1,L 0 1,R 0 1,R 1 2 1 1 1,L 1,L 0 3 HAL 1
1,L
0 1,R
0
1,R
1
2
1
1
1,L
1,L
0
3
HAL
1
1,R

b) Diese Turing-Maschine schreibt 6 1en auf ein anfänglich mit 0en vorbesetztes Band. Sie stoppt nach 13 Schritten unter der zweiten 1 von rechts. Es handelt sich hierbei um die Busy-Beaver-Funktion bb(3), d.h. um diejenige aus genau drei Anweisungen bestehende Turing-Maschine, welche die größte Anzahl von aufeinan- derfolgenden 1en auf ein anfänglich mit 0en vorbesetztes Band schreibt.

Aufgabe 9.2.3 (L3)

Konstruieren Sie eine Turing-Maschine mit den Bandzeichen T={-,0,1}, welche für eine zusam- menhängende aus 0en und 1en bestehende Zeichenfolge auf einem mit - vorbesetzten Band die Anzahl der 1en auf gerade Parität ergänzt. Dazu wird am linken Ende der Zeichenfolge eine 0 angefügt, wenn die Anzahl der 1en gerade ist und eine 1, wenn die Anzahl der 1en ungerade ist. Der Schreib/Lese-Kopf soll vor der Operation rechts neben der Zeichenfolge stehen.

Beispiel: aus ----10101---- wird also ---110101---- und aus ----1001---- wird ---01001----

Lösung

Strategie:

1. In Zustand 1 rechts neben dem String starten. In Zustand 1 nach links gehen, solange - gelesen wird. Wird in Zustand 1 eine 0 gelesen, nach Zustand 3 gehen. (Erstes Stringzeichen ist 0) Wird in Zustand 1 eine 1 gelesen, nach Zustand 2 gehen. (Erstes Stringzeichen ist 1)

2. In Zustand 2 solange nach links gehen, wie 0en gelesen werden. Wird in Zustand 2 eine 1 gelesen, nach Zustand 3 gehen. Wird in Zustand 2 ein - gelesen, ist das Stringende erreicht, eine 1 schreiben, HALT.

3. In Zustand 3 solange nach links gehen, wie 0en gelesen werden. Wird in Zustand 3 eine 1 gelesen, nach Zustand 2 gehen. Wird in Zustand 3 ein - gelesen, ist das Stringende erreicht, eine 0 schreiben, HALT.

Ist die Turing-Maschine in Zustand 2, so ist die aktuelle Anzahl der 1en ungerade, ist sie in Zustand 3, so ist die Anzahl der 1en gerade.

 

-

- L 1

-

1 L 0

-

0 L 0

1

0

0 L 3

2

0

0 L 2

3

0

0 L 3

1

1 L 2

1

1 L 3

1

1 L 2

Aufgaben und Lösungen

2-9

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

0,L

-,L - 0,L 1 1 1,L 0 0 - 1,L 2 0,L 1,L 1 1
-,L
-
0,L
1
1
1,L
0
0
- 1,L
2
0,L
1,L
1
1 1,L
3
HALT
- 0,L
0

HALT

Aufgabe 9.2.4 (L3)

Konstruieren Sie eine Turing-Maschine mit den Bandzeichen T={-,0,1}, die in einer zusam- menhängenden Gruppe von Einsen auf einem mit Strichen vorbesetzten Band zwischen je zwei Einsen eine Null einfügt. Zu Beginn soll der Schreib-/Lesekopf rechts von der Einser- gruppe stehen. Beispiel: aus ------11111----- wird also ------101010101-----

Lösung

Strategie:

1. In Zustand 1 rechts neben dem String starten. In Zustand 1 nach links gehen, bis zur ersten 1, also zum String-Anfang. Wurde die erste 1 gelesen, nach Zustand 2 gehen.

2. In Zustand 2 einen Schritt nach links gehen. Wird dort eine 1 gelesen, diese durch 0 ersetzen und nach Zustand 3 gehen. Wird ein - oder eine 0 gelesen, HALT.

3. In Zustand 3 solange nach links gehen, bis ein - gelesen wird. Den - am Stringende durch eine 1 ersetzen und nach Zustand 4 gehen.

4. In Zustand 4 nach rechts gehen bis eine 0 gelesen wird. Jetzt weiter bei Punkt 1 in Zustand 1.

- - L 1 - - R 0 - 1 R 4 - R 0
-
- L 1
-
- R 0
- 1 R 4
- R 0
1 2
0
0 L 0
0 0 L 0
3
0 0 L 3
4
1
1 L 2
1 0 L 3
1 1 L 3
-
0 0 L 1
1 1 R 4
HALT
-,L
0,L
0
-
-
-,R
1
2
1
1,L
0
0,L
1
0,L
0,L
1
0
1,L
-,R
-
1,R
-
HALT
4
3
1
0
1,R
0,L

HALT

3-10

Aufgaben und Lösungen

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

9.3 Einführung in die Theorie der formalen Sprachen

Aufgabe 9.3.1 (T1)

a) Wie kann man endliche von zyklischen Sackgassen unterscheiden?

b) Wann ist eine formale Sprache eindeutig?

c) Was ist ein Palindrom?

d) Was versteht man unter einer kontextfreien Sprache?

e) Was ist das Wortproblem?

Lösung

a) Kommt man bei der Analyse eines Wortes nach endlich vielen Schritten zu einem Wort, auf das keine Produktionen mehr angewendet werden können, so ist man in eine endliche Sackgasse geraten. Man kann dann die Analyse bei einer vorangehenden Verzwei- gungsmöglichkeit wieder aufnehmen. Bei einer zyklischen Sackgasse führt die Analyse nach einer endlichen Anzahl von Schritten wieder auf ein Wort, das in einem früheren Schritt bereits aufgetreten ist. Man kann die Sackgasse erkennen und wieder verlassen, wenn man alle Analyseschritte zwischenspeichert und immer wieder mit dem aktuellen Schritt vergleicht.

b) Eine formale Sprache heißt eindeutig, wenn der zugehörige Sprachschatz eindeutig ist, d.h. wenn alle zum Sprachschatz gehörenden Wörter nur auf eine einzige, eindeutige Weise aus dem Axiom Z ableitbar sind.

c) Unter einem Palindrom versteht man ein um seinen Mittelpunkt symmetrisches Wort. Es ist also vorwärts und rückwärts gelesen identisch. Beispiel: otto.

d) Eine Grammatik heißt kontextfrei (context free) oder Chomsky-2-Grammatik, wenn die Produktionen nicht von einem Kontext abhängen. Die Produktionen haben dann die einfa- che Form:

Au mit AS und uV*\{ε}

Die syntaktische Variable A wird also unabhängig von rechts oder links benachbarten Zeichen, dem Kontext, in ein beliebiges, nichtleeres Wort aus V* transformiert. Worte können also nicht kürzer werden, d.h. die Sprache ist wortlängenmonoton. Die Menge der durch kontextfreie Grammatiken erzeugten Sprachen ist mit der Menge der durch Keller- automaten akzeptierten Sprachen identisch.

e) Unter dem Wortproblem versteht man die Aufgabe, von einem gegebenen Wort zu ent- scheiden, ob es zu einem bestimmten Sprachschatz gehört oder nicht. Es kann sich dabei beispielsweise um den Sprachschatz einer formalen Sprache oder um den akzeptierten Sprachschatz eines Automaten handeln.

Aufgabe 9.3.2 (T1)

a) Was versteht man unter dem Nachbereich einer formalen Sprache?

b) Unter welcher Bedingung heißt eine Produktion terminal?

c) Was versteht man unter dem Kern einer formalen Sprache?

d) Welche Klasse von formalen Sprachen der Chomsky-Hierarchie lässt sich durch Automaten darstellen?

e) Welcher Typ von formalen Sprachen ist zu Kellerautomaten äquivalent?

Aufgaben und Lösungen

2-11

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

Lösung

a) Unter dem Nachbereich einer formalen Sprache versteht man die Menge aller Wörter, die aus dem Axiom ZS ableitbar sind.

b) Eine Produktion heißt terminal, wenn das Ergebnis nur aus terminalen Zeichen besteht, also:

uv ist terminal, wenn vT*\{ε}

Im engeren Sinne verlangt man noch Wortlängenmonotonie. Bei regulären Sprachen ist außerdem uS vorausgesetzt, d.h. u muss eine syntaktische Variable sein.

c) Der Kern einer formalen Sprache besteht aus allen aus dem Axiom Z ableitbaren Wörtern, aus denen sich die Wörter des Sprachschatzes (also die Menge aller ausschließlich aus terminalen Zeichen bestehenden Wörter) ableiten lassen. Zusätzlich zum Sprachschatz umfasst der Kern also auch Worte, in denen auch Syntaktische Variablen enthalten sind.

d) Reguläre Grammatiken, also solche mit terminalen und linkslinearen oder rechtslinearen Produktionen, lassen sich durch Automaten darstellen.

e) Kontextfreie Sprachen, also Chomsky-2-Grammatiken sind zum akzeptierten Sprachschatz von Kellerautomaten äquivalent.

Aufgabe 9.3.2 (T1)

Gegeben sei die folgende formale Sprache:

S = {Z, X},

a) Von welchem Typ ist die Grammatik?

b) Geben Sie den zugehörigen Sprachschatz an.

c) Leiten Sie das Wort uv 2 wu 2 v aus dem Axiom ab. Falls es bei der Ableitung Sackgassen gibt, geben Sie bitte ein Beispiel an.

T = {u, v, w},

P = { ZuZv, ZX, XvXu, Xv, Xw }

Lösung

a) Die Sprache ist wortlängenmonoton, da keine Produktion verkürzend wirkt. Sie ist außer- dem kontextfrei und daher vom Typ Chomsky-2. Da es nichtlineare Produktionen gibt, ist die Sprache nicht vom Typ Chomsky-3.

b) Der zugehörige Sprachschatz lautet: L={ u n v m xu m v n | n,m N 0 , x={v,w} }. Die kürzesten Wörter sind: v, w, uwv, uv 2 .

c) ZuZvuvXuvuvvXuuvuv 2 wu 2 v Es gibt Sackgassen, aber nur endliche. Beispiel: ZuZvuvXuvuvwuv

Aufgabe 9.3.2 (T1)

Konstruieren Sie eine Formale Sprache L für die Menge aller korrekten arithmetischen Ausdrü- cke mit natürlichen Zahlen n unter Verwendung der Operationen „+“ und „ * “ sowie der üblichen Klammerung.

Lösung

T={0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +, *, (, )}

S={Z, N, T}

3-12

Aufgaben und Lösungen

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

Aufgabe 9.3.2 (T1)

Die Zusammenstellung eines Intercity-Zuges möge nach folgenden Regeln erfolgen:

Der erste Wagen des Zugs ist ein Triebwagen, es folgen n>1 Wagen der ersten Klasse, da- nach folgt ein Speisewagen und danach 2n Wagen der zweiten Klasse.

a) Geben Sie die Wagenfolge des kürzestmöglichen Zuges an.

b) Konstruieren Sie eine formale Sprache für die Zusammenstellung von Intercity-Zügen. Verwenden Sie dazu die Menge T={t,1,s,2} von terminalen Zeichen in ihrer offen- sichtlichen Bedeutung sowie die Menge S={Z,W} von syntaktischen Variablen, wobei W für „Wagen“ steht.

c) Von welchem Chomsky-Typ ist diese Sprache? Bitte begründen Sie Ihre Antwort.

d) Wie muss man die Regeln für die Zugzusammenstellung ändern, damit die entsprechende formale Sprache als Automat darstellbar ist?

Lösung

a) Mit T={t,1,s,2} lautet die Wagenfolge des kürzestmöglichen ICE-Zuges: t1s22

b) Vokabular:

V = TS mit T={t,1,s,2} und S={Z, W}

Produktionen: P = {ZtW, W1s22, W1W22}

c) Nicht alle verwendeten Produktionen sind terminal oder linear, daher ist die Sprache nicht CH-3. Die Sprache ist wortlängenmonoton und kontextfrei und daher vom Typ CH-2.

d) Problematisch ist, dass die Anzahl der Wagen zweiter Klasse doppelt so hoch sein soll wie die der ersten Klasse und dass die Anzahl der Wagen nicht beschränkt ist. Beschränkt man die Anzahl der Wagen, so ist eine Darstellung als Automat möglich. Auch ohne Beschrän- kung gelingt dies, wenn die Anzahl der Wagen erster Klasse unabhängig von der Anzahl der Wagen zweiter Klasse ist.

Man kann zwar eine Mengen von Produktionen finden, bei der alle Produktionen terminal oder linear sind, beispielsweise

P = {ZtW, W1s22, WV22, V1W}

es treten jedoch linkslineare und rechtslineare Produktionen auf, so dass die resultierende Grammatik nicht regulär ist.

Man könnte die Aufgabe jedoch ohne weiteres mit Hilfe eines Kellerautomaten lösen.

Aufgaben und Lösungen

2-13

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

10 Algorithmen

10 Algorithmen

Aufgabe 10.1 (T1)

a) Wann ist ein Algorithmus statisch finit und wann dynamisch finit?

b) Erläutern Sie den Unterschied zwischen Berechenbarkeit und Komplexität.

c) Was besagt die Church-Turing-These?

d) Was ist der Unterschied zwischen primitiv-rekursiven und µ-rekursiven Funktionen?

e) Was sind NP-vollständige Probleme

f) Wann ist ein Algorithmus effektiv, wann effizient?

Aufgabe 10.2 (T1)

a) Was ist eine rekursive Relation?

b) Wodurch unterscheiden sich probabilistische und heuristische Algorithmen?

c) Was ist ein genetischer Algorithmus?

d) Erläutern Sie das Prinzip des Backtracking.

e) Erläutern Sie das Prinzip Teile und Herrsche.

f) Was ist ein gieriger Algorithmus?

Aufgabe 10.3 (M3)

Die Ackermann-Funktion.

a) Wie ist die Ackermann-Funktion definiert?

b) Ist die Ackermann-Funktion primitiv-rekursiv?

c) Berechnen Sie ack(3,2)

d) Für welches xN gilt ack(3,ack(0,y)) = ack(x,ack(3,y)) ?

e) Zeigen Sie: ack(p,q+1) > ack(p,q)

Lösung

a) Die Ackermann-Funktion ack(x,y) ist folgendermaßen rekursiv definiert:

ack(0,y)

= y+1

ack(x+1,0)

= ack(x,1)

ack(x+1,y+1) = ack(x,ack(x+1,y)

b) Die Ackermann-Funktion ist keine primitiv-rekursive Funktion, sondern eine µ-rekursive Funktion, da man zeigen kann, dass sie schneller wächst als jede primitiv rekursive Funk- tion. Sie ist jedoch berechenbar, da sie eine µ-rekursive Funktion ist. Man kann auch als Begründung angeben, dass man bei der iterativen Programmierung der Ackermann-Funktion nicht mit einer klassischen FOR-Schleife auskommt. Man benö- tigt eine GOTO oder eine WHILE-Schleife.

c) Im Folgenden wird statt ack(a,b) vereinfachend (a,b) geschrieben. Nach dem Aufschreiben der ersten Terme erkennt man das Bildungsgesetz, so dass sich die Lösung schnell finden lässt. Siehe auch Aufgabe 10.4. (3,2) = (2,(3,1)) = (2,(2,(3,0))) = (2,(2,(2,1))) = (2,(2,(1,(2,0)))) = (2,(2,(1,(1,1)))) = (2,(2,(1,(0,(1,0))))) = (2,(2,(1,(0,(0,1))))) = (2,(2,(1,(0,2)))) = (2,(2,(1,3))) = (2,(2,(0,(1,2)))) = (2,(2,(0,(0,(1,1))))) = (2,(2,(0,(0,(0,(1,0)))))) = (2,(2,(0,(0,(0,2))))) = (2,(2,(0,(0,3)))) = (2,(2,(0,4))) = (2,(2,5)) =

3-14

Aufgaben und Lösungen

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

(2,(1,(2,4))) = (2,(1,(1,(2,3)))) = (2,(1,(1,(1,(2,2))))) = (2,(1,(1,(1,(1,(2,1)))))) = (2,(1,(1,(1,(1,(1,(2,0))))))) = (2,(1,(1,(1,(1,(1,(1,1))))))) = (2,(1,(1,(1,(1,(1,(0,(1,0)))))))) = (2,(1,(1,(1,(1,(1,(0,(0,1)))))))) = (2,(1,(1,(1,(1,(1,(0,2))))))) = (2,(1,(1,(1,(1,(1,3)))))) = (2,(1,(1,(1,(1,(0,(1,2))))))) = (2,(1,(1,(1,(1,(0,(0,(1,1)))))))) = (2,(1,(1,(1,(1,(0,(0,(0,(1,0))))))))) = (2,(1,(1,(1,(1,(0,(0,(0,(0,1))))))))) = (2,(1,(1,(1,(1,(0,(0,(0,2)))))))) = (2,(1,(1,(1,(1,(0,(0,3))))))) = (2,(1,(1,(1,(1,(0,4)))))) = (2,(1,(1,(1,(1,5))))) = (2,(1,(1,(1,(0,(1,4)))))) = (2,(1,(1,(1,(0,(0,(1,3))))))) = (2,(1,(1,(1,(0,(0,(0,(1,2)))))))) = (2,(1,(1,(1,(0,(0,(0,(0,(1,1))))))))) = (2,(1,(1,(1,(0,(0,(0,(0,(0,(1,0)))))))))) = (2,(1,(1,(1,(0,(0,(0,(0,(0,(0,1)))))))))) = (2,(1,(1,(1,(0,(0,(0,(0,(0,2))))))))) = (2,(1,(1,(1,(0,(0,(0,(0,3)))))))) = (2,(1,(1,(1,(0,(0,(0,4))))))) = (2,(1,(1,(1,(0,(0,5)))))) = (2,(1,(1,(1,(0,6))))) = (2,(1,(1,(1,7)))) = (2,(1,(1,(0,(1,6))))) =

(2,(1,(1,(0,(0,(0,(0,(0,(0,(0,(1,0))))))))))) = (2,(1,(1,(0,(0,(0,(0,(0,(0,(0,(0,1))))))))))) =

(2,(1,(1,(0,8)))) =

(2,(1,(1,9))) =

(2,(1,(0,(1,8)))) =

(2,(1,(0,(0,(0,(0,(0,(0,(0,(0,(0,(1,0)))))))))))) = (2,(1,(0,(0,(0,(0,(0,(0,(0,(0,(0,(0,1)))))))))))) =

(2,(1,(0,10))) =

(2,(1,11)) =

(2,(0,(1,10))) =

(2,(0,(0,(0,(0,(0,(0,(0,(0,(0,(0,(0,(1,0))))))))))))) = (2,(0,(0,(0,(0,(0,(0,(0,(0,(0,(0,(0,(0,1))))))))))))) =

(2,(0,12)) = (2,13) = (1,(2,12)) = (1,(1,(2,11))) = (1,(1,(1,(2,10)))) = (1,(1,(1,(1,(2,9))))) = (1,(1,(1,(1,(1,(2,8)))))) = (1,(1,(1,(1,(1,(1,(2,7))))))) = (1,(1,(1,(1,(1,(1,(1,(2,6)))))))) = (1,(1,(1,(1,(1,(1,(1,(1,(2,5))))))))) =

Aufgaben und Lösungen

2-15

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

(1,(1,(1,(1,(1,(1,(1,(1,(1,(2,4)))))))))) = (1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(2,3))))))))))) = (1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(2,2)))))))))))) = (1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(2,1))))))))))))) = (1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(2,0)))))))))))))) = (1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,1)))))))))))))) = (1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(0,(1,0))))))))))))))) = (1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(0,(0,1))))))))))))))) = (1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(0,2)))))))))))))) = (1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,3))))))))))))) = (1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(0,(1,2)))))))))))))) = (1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(0,(0,(1,1))))))))))))))) = (1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(0,(0,(0,(1,0)))))))))))))))) = (1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(0,(0,(0,(0,1)))))))))))))))) = (1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(0,(0,(0,2))))))))))))))) = (1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(0,(0,3)))))))))))))) = (1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(0,4))))))))))))) = (1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,5)))))))))))) =

(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,7))))))))))) =

(1,(1,(1,(1,(1,(1,(1,(1,(1,(1,9)))))))))) =

(1,(1,(1,(1,(1,(1,(1,(1,(1,11))))))))) =

(1,(1,(1,(1,(1,(1,(1,(1,13)))))))) =

(1,(1,(1,(1,(1,(1,(1,15))))))) =

(1,(1,(1,(1,(1,(1,17)))))) =

(1,(1,(1,(1,(1,19))))) =

(1,(1,(1,(1,21)))) =

(1,(1,(1,23))) =

(1,(1,25)) =

(1,27) =

29

d) ack(3,ack(0,y)) = ack(3,y+1) = ack(2+1,y+1) = ack(2,ack(3,y)) Es ist also x=2

e) q<A(p,q) für alle p>0, q>1 (*)

A(p,q)<A(p,q+1) ist zu zeigen p=0: A(0,q)=2q<2(q+1)=A(0,q+1) p>0: Man setzt in (*) für p: p-1 und q=A(p,q) A(p,q)<A(p-1,A(p,q))=A(p,q+1) für p>1, q>1 p>1, q=0: A(p,0)=0 < A(0,1)=2

3-16

Aufgaben und Lösungen

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

Aufgabe 10.4 (P3)

Schreiben sie ein rekursives und ein iteratives Programm zur Berechnung der Ackermann- Funktion. Vergleichen Sie die Ausführungszeiten.

Lösung

//*********************************************************************** // Vergleich der rekursiven und iterativen // Berechnung der Ackermann-Funktion //*********************************************************************** #include <stdlib.h> #include <stdio.h> #include <time.h> #define SMAX 100000

//*********************************************************************** // Iterative Berechnung der Ackermann-Funktion

//-----------------------------------------------------------------------

int ak_i(int x, int y) { int k, s[SMAX+2], sp=0; s[sp++]=x; s[sp++]=y;

// Stack und Stack-Pointer // x und y in Stack eintagen

while(sp!=1){

//printf("\nS:"); for(k=0; k<sp; k++) printf("%3d",s[k]); getch();

}

}

// x und y aus Stack holen // nur y+1 in Stack eintagen // x-1 und 1 in Stack

else {s[sp++]=x-1; s[sp++]=x; s[sp++]=y-1; } // x-1, x, y-1 in Stack

if(sp>=SMAX) return -1;

y=s[--sp]; x=s[--sp]; if(x==0) s[sp++]=y+1; else if(y==0) { s[sp++]=x-1; s[sp++]=1; }

// Stack-Überlauf

return s[--sp];

// Ergebnis: letzter Stack-Eintrag

//*********************************************************************** // Rekursive Berechnung der Ackermann-Funktion

//-----------------------------------------------------------------------

int ak_r(int x, int y) { if(x==0) return(y+1); if(y==0) return(ak_r(x-1,1));

// Abbruchkriterium // einfache Rekursion // doppelte Rekursion

return(ak_r(x-1,ak_r(x,y-1)));

}

//*********************************************************************** // Hauptprogramm //*********************************************************************** int main() {

int x=1,y=1, a=0; time_t t;

printf("\n\nACKERMANN-FUNKTION\n"); while(x>0 && y>0) { printf("\nx, y = "); scanf("%d,%d",&x,&y); t=clock(); printf("\nak_r = %d ",ak_r(x,y));

// Parameter deklarieren und vorbesetzen // Zeit

// Solange

x und y nicht 0 sind

// Eingabe von x und y // Anfangszeit merken // ak(x,y) rekursiv

printf("(%5.2f sec)\n",(float)difftime(clock(),t)/CLOCKS_PER_SEC);

t=clock();

// Anfangszeit merken

a=ak_i(x,y);

// ak(x,y) iterativ

if(a<0) printf("\nStack-Überlauf!!"); else printf("\nak_i = %d ",a); printf("(%5.2f sec)\n",(float)difftime(clock(),t)/CLOCKS_PER_SEC);

}

}

Aufgaben und Lösungen

2-17

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

Aufgabe 10.4 (M2)

Beweisen Sie durch vollständige Induktion:

n

i =

1

i

3

= ⎜

n

=

i

1

i

Lösung

2

Aufgabe 10.5 (M2)

Der übliche Algorithmus zur Multiplikation zweier n×n-Matrizen hat die Komplexität O (n 3 ). Nach dem Verfahren von Strassen lassen sich zwei n×n-Matrizen mit der Komplexität O(n ld(7) ) multipli- zieren. Für n=10 benötige der übliche Algorithmus auf einem bestimmten Rechner 0.1 Sekunden und der Strassen-Algorithmus 0.12 Sekunden.

a) Um wie viele Sekunden arbeitet der Strassen-Algorithmus schneller als der übliche Algorith- mus, wenn n=100 ist?

b) Wie groß muss n sein, damit der Strassen-Algorithmus auf dem gegebenen Rechner dop- pelt so schnell abläuft wie der konventionelle Algorithmus?

Lösung

a)

T M = c M n 3

T

T

T

T

T

Differenz: D = T M (100)-T S (100) = (100-76.9) sec = 23.1 sec Für n=100 arbeitet der Strassen-Algorithmus also um 23.1 sec schneller als der konventionelle Algorithmus.

konventioneller Algorithmus

Strassen-Algorithmus

M (10) = c M 10 3 sec = 0.1 sec,

also c M =0.1/1000=10 -4

M (100) = 10 -4 100 3 sec = 100 sec

S = c S n ld(7) = c S n 2.807

S (10) = c S 10 2.807 sec = 0.12 sec, also c S 0.12/641.21 1.8710 -4

S (100) = 1.8710 -4 100 2.807 sec 1.8710 -4 4.111510 5 sec 76.9sec

b) c M n 3 = 2c S n 2.807

n

n

Erst für n=929 arbeitet der Strassen-Algorithmus doppelt so schnell wie der konventionelle Algorithmus.

3-2.807 = n 0.193 = 2c S /c M = 21.8710 -4 /10 -4 = 3.74

= 3.74 1/0.193 3.74 5.1813 929

3-18

Aufgaben und Lösungen

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

11

Suchen und Sortieren

11.1 Einfache Suchverfahren

Aufgabe 11.6.1 (T1)

a) Was ist eine Marke im Zusammenhang mit Suchen in Arrays?

b) Ist Suchen in Arrays oder in linearen Listen effizienter durchführbar?

c) Was versteht man unter Radix-Suche?

d) Wodurch unterscheiden sich die sequentielle und die binäre Suche?

Lösung

Aufgabe 11.6.2 (P3)

Vergleich von binärer Suche und Interpolationssuche.

Erstellen Sie ein Array a[] mit 30 000 Integer-Zufallszahlen. Verwenden Sie dabei die Uhrzeit als Startwert für den Zufallszahlengenerator.

Ordnen Sie das Array in aufsteigender Folge mit einer beliebigen Sortierfunktion.

Schreiben Sie eine Funktion search_bin zum binären Suchen. Suchen Sie damit in einer eine Million mal durchlaufenen Schleife nach zufällig ausgewählten Zahlen und geben Sie die mittlere Anzahl der Intervallteilungen sowie die mittlere Ausführungszeit aus.

Schreiben Sie eine Funktion search_int zur Interpolationssuche. Suchen Sie damit in einer mindesten 1 Million mal durchlaufenen Schleife nach denselben Zufallszahlen wie mit der binären Suche und geben Sie auch dafür die mittlere Anzahl der Intervallteilungen so- wie die benötigte Ausführungszeit aus. Warnung: es ist auf effiziente Implementierung und auf Rundungsfehler bei der Intervallteilung zu achten.

Vergleichen und interpretieren Sie die Ergebnisse für die binäre Suche und die Interpolati- onsuche.

Lösung

//************************************************************************ // Vergleich der binären Suche und der Interpolationssuche //************************************************************************ #include <stdio.h> #include <stdlib.h> #include <time.h> #define MAX 30000 #define ESC 27

void sort_insb(long int a[], long int n); long int srch_int(long int x, long int a[], long int n); long int srch_bin(long int x, long int a[], long int n);

int count;

//------------------------------------------------------------------------

// Vergleich der binären Suche und der Interpolationssuche durch Suche // in einem mit Zufallszahlen vorbesetzten und danach geordneten Feld.

//------------------------------------------------------------------------

int main() { long int c=0, i, k, a[MAX], n=MAX, m=100; float sum;

Aufgaben und Lösungen

2-19

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

time_t t;

printf("\nVergleich von Suchalgorithmen");

printf("\n=============================");

srand((unsigned)time(NULL)); while(c!=ESC) {

// Zeit

// init. Zufallszahlengenerator

for(i=0; i<MAX; i++) a[i]=rand();

printf("\n\nSortieren

t=clock(); sort_insb(a,n); printf("beendet nach %5.3f sec",

");

// mit Zufallszahlen vorbesetzen

// benötigte Zeit

(float)(difftime(clock(),t)/CLOCKS_PER_SEC));

printf("\n\nBinaere Suche:"); t=clock();

sum=0.0;

for(k=0; k<m*n; k++) { srch_bin(rand(),a,n); sum+=count;

}

printf(" %6.3f sec",(float)(difftime(clock(),t)/CLOCKS_PER_SEC)); printf("\nMittlere Anzahl der Schritte: %5.2f",sum/m/n);

// Binäre Suche

// mittlere Anzahl der Schritte

}

}

printf("\n\nInterpolations-Suche:"); // Interpolationssuche t=clock();

sum=0.0;

for(k=0; k<m*n; k++) { srch_int(rand(),a,n); sum+=count;

}

printf(" %6.3f msec",(float)(difftime(clock(),t)/CLOCKS_PER_SEC)); printf("\nMittlere Anzahl der Schritte: %5.2f",sum/m/n); printf("\n\nWeiter mit beliebiger Taste, Beenden mit <ESC>"); c=getch();

// mittlere Anzahl der Schritte

return 0;

//------------------------------------------------------------------------

// Binäre Suche nach einem Element x in einem aufsteigend // geordneten Integer-Feld a der Dimension n. // Rückgabewert: Index des gefundenen Elements

//

//------------------------------------------------------------------------

long int srch_bin(long int x, long int a[], long int n) {

oder -1, wenn nicht gefunden.

long int anf=0, end=n-1, m=0;

count=0;

while(anf<=end && x!=a[m]) {

m=(anf+end)/2;

if(x<a[m]) end=m-1; else anf=m+1; count++;

// Intervall // Schrittzähler // Solange Element x nicht gefunden // halbiere Intervall // suche im linken Intervall // suche im rechten Intervall

}

}

if(a[m]==x) return m;

return -1;

// Element gefunden // Element nicht gefunden

//------------------------------------------------------------------------

// Interpolationssuche nach einem Element x in einem aufsteigend // geordneten Integer-Feld a der Dimension n. // Rückgabewert: Index des gefundenen Elements

//

//------------------------------------------------------------------------

long int srch_int(long int x, long int a[], long int n) {

oder -1, wenn nicht gefunden.

3-20

Aufgaben und Lösungen

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

}

long int anf=0, end=n-1, m=0;

count=0;

if(x<a[anf] || x>a[end]) return -1; while(anf<end) {

// Intervall // Schrittzähler

// Element zu klein oder zu groß // solange Element x nicht gefunden

m=anf+(end-anf)*(x-a[anf]+1)/(a[end]-a[anf]+1);

if(m<anf) m=anf; else if(m>end) m=end; if(x<a[m]) end=m-1; else anf=m+1; count++;

// Bereichsgrenzen prüfen

// suche im linken Intervall // suche im rechten Intervall

}

if(a[m]==x) return m; if(a[anf]==x) return anf; if(a[end]==x) return end; return -1;

// Element gefunden // Element gefunden (Sonderfall 1) // Element gefunden (Sonderfall 2)

//------------------------------------------------------------------------

// Sortieren eines Int-Arrays a mit Dimension n in aufsteigende Ordnung // durch direktes Einfügen mit binärer Suche der Einfügestelle.

//------------------------------------------------------------------------

void sort_insb(long int a[], long int n) { long int i, j, ug, og, x;

for(i=1; i<n; i++) { x=a[i]; ug=0; og=i-1; ug=0; og=i-1; while(ug<=og) {

j=(ug+og)/2;

if(x<a[j]) og=j-1; else ug=j+1;

}

for(j=i; j>ug; j--)

a[j]=a[j-1];

a[ug]=x;

}

return;

}

// Schleife durch das Array // einzusortierendes Element // Grenzen des sortierten Bereichs // Binäre Suche der Einfügestelle

// Elemente verschieben // x einfügen

Aufgaben und Lösungen

2-21

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

11.3 Gestreute Speicherung (Hashing)

Aufgabe 11.3.1 (T1)

a) Was ist der wesentliche Vorteil von Hash-Verfahren?

b) Was versteht man unter Klumpenbildung?

c) Nennen Sie einen Vorteil und einen Nachteil der quadratischen im Vergleich zur linearen Kollisionsauflösung.

d) Welche Forderungen stellt man üblicherweise an Hash-Funktionen?

e) Wie ist der Belegungsfaktor definiert?

f) Kann der Belegungsfaktor größer als 1 werden?

Lösung

a)

Rascher Zugriff auf die Datensätze mit wenig Schlüsselvergleichen auch bei großen Da- tenmengen.

b)

Wenn durch die Kollisionsauflösung Häufungen von Einträgen im Adressraum entstehen, spricht man von Klumpenbildung. Die lineare Kollisionsauflösung neigt zur Klumpenbil- dung. Der Nachteil ist, dass in Klumpen eine erhöhte Anzahl von Schlüsselvergleichen er- forderlich ist.

c)

Die quadratische Kollisionsauflösung verteilt die Einträge gleichmäßiger über den Adress- raum, die Klumpenbildung ist also geringer. Die Größe des Adressraums muss eine Prim- zahl sein, aber auch dann wird von einer bestimmten Primäradresse ausgehend nur die hälfte des Adressraums erfasst.

d)

- Die Hash-Funktion soll schnell aus dem Primärschlüssel berechnet werden können.

- Ein bestehende Ordnung der Primärschlüssel soll erhalten bleiben.

- Die Hash-Funktion soll die Adressen gleichmäßig über die m möglichen Adressen verteilen.

- Alle Schlüssel sollen mit gleicher Wahrscheinlichkeit auftreten.

- Die durch die Kollisionsbehandlung berechneten Adressen sollen gleichmäßig über den

Adressraum verteilt werden.

e) Der Belegungsfaktor lautet µ=n/m, wobei n die Anzahl der gespeicherten Datensätze ist und m der Umfang des Adressraums.

f) Der Belegungsfaktor kann größer als 1 werden, wenn die Anzahl der gespeicherten Da- tensätze die Größe des Adressraums übersteigt. Dies kann dann der Fall sein, wenn der Adressraum nur für die Primäradressen gilt, der Speicherplatz für die Kollisionsauflösung aber durch dynamische Speicherplatzzuweisung erfolgt, beispielsweise bei Kollisionsauf- lösung durch lineare Listen.

3-22

Aufgaben und Lösungen

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

Aufgabe 11.3.2 (M1)

Berechnen Sie die mittlere Anzahl S von Vergleichen zum Auffinden eines beliebigen Daten- satzes in einer Hash-Tabelle, die maximal 100 000 Datensätze enthalten kann und bereits mit 86 000 Datensätzen gefüllt ist. Dabei kann von idealen Bedingungen ausgegangen wer- den.

Lösung

Für ideale Verhältnisse gilt:

S =

1

μ

ln

1

1 μ

Mit µ=n/m=86000/100000=0.86 folgt: S=1.16279·ln(7.14286)=2.28618. Es sind also im Mittel et- was mehr als zwei Vergleiche erforderlich.

Aufgabe 11.3.3 (M2)

Es soll eine Hash-Tabelle mit einem Adressraum von m=13 angelegt werden. Die Hash- Funktion sei definiert durch die Vorschrift:

h(Name) = [pos(1. Buchstabe) + pos(2. Buchstabe) ] mod 13

Dabei gibt pos() die Position des betreffenden Buchstaben im Alphabet an, also pos(A)=1 etc.

a) Geben Sie den Belegungsfaktor an.

b) Tragen Sie die folgenden Datensätze in der angegebenen Reihenfolge unter Verwendung der linearen und der quadratischen Kollisionsauflösung in die Hash-Tabelle ein:

Hammer, Feile, Nagel, Zange, Zwinge, Raspel, Schraube, Niete, Pinsel

c) Berechnen Sie die mittlere Anzahl der Vergleiche für erfolgreiche und erfolglose Suche.

Lösung

1 Hammer,

2 Feile,

3 Nagel,

4 Zange,

5 Zwinge,

6 Raspel,

7 Schraube,

8 Niete,

9 Pinsel,

8+1

6+5

14+1

26+1

26+23

18+1

19+3

14+9

16

9

11

2

1

10

6

9

10

3

Hash-Adresse

Name (linear)

Name (quadr.)

 

1

4 Zange

4 Zange

2

3 Nagel

3 Nagel

3

9 Pinsel

9 Pinsel

4

---

---

5

---

---

6

6 Raspel

6 Raspel

 

7

---

8 Niete

8

---

---

9

1 Hammer

1 Hammer

 

10

5 Zwinge

5 Zwinge

11

2 Feile

2 Feile

12

7 Schraube

---

13

8 Niete

7 Schraube

 

Quadr. Kollisionsaufl.: h+1, h+4, h+9, etc.

Aufgaben und Lösungen

2-23

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

Aufgabe 11.3.4 (P3)

Es soll eine Datenverwaltung unter Verwendung der gestreuten Speicherung aufgebaut wer- den. Die Primäradresse A soll dabei möglichst einfach unter Einhaltung der lexikografischen Ordnung aus dem Primärschlüssel der Datensätze berechnet werden. Die Kollisionsbehand- lung soll mit Hilfe einfach verketteter nach dem Primärschlüssel geordneter linearer Listen erfolgen. Es sollen die Operationen Einfügen, Suchen, Löschen und Auflisten von Datensät- zen realisiert werden.

Lösung

//************************************************* // // Hashing mit Überlaufbehandlung // durch verketteten Listen // //************************************************* #include <stdio.h> #include <string.h> #include <malloc.h>

#define DIM 20 #define ANZ 100

int eingabe(char text[], int l); int auflisten(void); int einfuegen(void); int hash(char *text); int suchen(void); int loeschen(void);

struct rec { char info[DIM]; struct rec *pnt;

};

struct rec *hfeld[ANZ];

// Datensatz // Informationsteil // Zeiger

// Hash-Feld, Zeiger

//************************************************** // Eingabefunktion mit Überlaufsicherung

//--------------------------------------------------

int eingabe(char text[], int len) { int i=0, e=0, c=1; if(len>=DIM) { len=DIM-1; e=-1; } // Maximallänge while(i<len) { c=(char)getche(); if(c<33) break; text[i++]=c;

}

text[i]=0;

return(e);

}

//************************************************** // Hash-Funktion zur Bestimmung der Primäradresse

//--------------------------------------------------

int hash(char text[]) { char c; if(text[0]>64) c=text[0]|96-33; else c=text[0]-33; return(c%ANZ);

// Kleinbuschst.

3-24

Aufgaben und Lösungen

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

}

//************************************************** // Auflisten der Daten

//--------------------------------------------------

int auflisten(void) { int i, e=-1; struct rec *pnt; printf("\nDatensätze:\n"); for(i=0; i<=ANZ; i++) { if(hfeld[i]) {

e=0;

pnt=hfeld[i]; while(pnt!=NULL) { printf("%s ",pnt->info); pnt=pnt->pnt;

}

}

}

if(e) { printf("Datei ist leer!\n"); return(-1); } printf("\n");

return(0);

}

//************************************************** // Suchen eines Datensatzes

//--------------------------------------------------

int suchen(void) { char text[DIM]; struct rec *pnt;

}

printf("\nGeben Sie den Datensatz ein: ");

eingabe(text,DIM);

pnt=hfeld[hash(text)];

while(strcmp(text,pnt->info) && pnt) pnt=pnt->pnt; if(pnt!=NULL) printf("\nGefunden: %s\n",pnt->info); else printf("\nNicht gefunden: %s\n",text);

return(0);

// Zu suchender Datensatz // Primäradresse

//************************************************** // Einfügen eines Datensatzes

//--------------------------------------------------

int einfuegen(void) {

char text[DIM]; int i; struct rec *neu, *pnt, *vor; printf("\nEingabe, beenden mit <CR>"); for(;;) { printf("\n> ");

// Prompt

eingabe(text,DIM);

// Eingabe

if(text[0]==0) return(0); i=hash(text);

pnt=vor=hfeld[i]; neu=(struct rec *)malloc(sizeof(struct rec)); strcpy(neu->info,text);

if(strcmp(text,pnt->info)<0 || hfeld[i]==NULL) {

// Eingabe beenden // Primäradresse

neu->pnt=hfeld[i];

hfeld[i]=neu;

// Neue Wurzel

}

else {

while(strcmp(text,pnt->info)>0 && pnt) {

vor=pnt;

// Einfügestelle

Aufgaben und Lösungen

2-25

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

pnt=pnt->pnt;

}

vor->pnt=neu;

// In Liste einfügen

neu->pnt=pnt;

}

}

}

3-26

Aufgaben und Lösungen

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

11.4 Direkte Sortierverfahren

Aufgabe 11.4.1 (T1)

a) Welches direkte Sortierverfahren benötigt im Mittel am wenigsten Schlüsselvergleiche?

b) Welches direkte Sortierverfahren benötigt im Mittel am wenigsten Zuweisungen?

c) Welches direkte Sortierverfahren arbeitet für bereits sortierte Daten am schnellsten?

Aufgabe 11.4.2 (P2)

Implementieren Sie die Sortierverfahren direktes Einfügen, direktes binäres Einfügen, direk- tes Auswählen, Bubble-Sort und Shaker-Sort. Erzeugen Sie Zufallsfolgen von double- Zahlen, sortieren Sie diese mit allen implementierten Sortierfunktionen und geben Sie die Sie jeweiligen die Laufzeiten aus.

Aufgabe 11.4.3 (P3)

Schreiben Sie unter Verwendung des Bubble-Sort ein Programm zum Sortieren einer linea- ren Liste am Platz, d.h. ohne wesentlichen zusätzlichen Speicherplatz. Der Bubble-Sort ist die einzige Sortier-Methode, die dies leistet.

Lösung

//***********************************************************************

// Am-Platz-Sortieren einer linearen List durch Bubble Sort

//***********************************************************************

#include <stdlib.h> #include <stdio.h>

BUBBLE_L.C

struct element { int key; struct element *next; };

// Element

//*********************************************************************** // Ausgabe der linearen Liste

//-----------------------------------------------------------------------

int list(struct element *h) { struct element *p;

// Laufzeiger

}

printf("\n"); if(h->next==NULL) { printf("Liste ist leer\n"); return(-1); } p=h->next;

while(p) { printf("%i ",p->key); p=p->next;

// durchlaufe gesamte Liste // Ausgabe eines Elements // weiter zum nächsten Element

}

return(0);

//*********************************************************************** // Anfügen eines Elementes

//-----------------------------------------------------------------------

void add(struct element *h) { char c[80]="0"; struct element *p; printf("\nEingabe beenden mit q\n"); while(c[0]!='q') { printf("Eingabe: "); scanf("%s",c);

// Element einlesen

if(c[0]!='q') { p=(struct element *) malloc(sizeof(struct element));

p->key=atoi(c);

// in Integer umwandeln und am Kopf einfügen

Aufgaben und Lösungen

2-27

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

p->next=h->next;

h->next=p;

}

}

return;

}

//*********************************************************************** // Austauschen zweier aufeinanderfolgender Elemente einer linearen Liste

//-----------------------------------------------------------------------

struct element *swap(struct element *p) { struct element *t; t=p; p=p->next; t->next=p->next; p->next=t; return(p);

}

// temporäres Element

//*********************************************************************** // Bubble-Sort für lineare Liste

//-----------------------------------------------------------------------

void sort(struct element *h) {

struct element *p,*q; char go=1; while(go) {

// Laufzeiger // Flag wird 0, wenn die Liste sortiert ist // solange die Liste noch nicht sortiert ist

go=0;

if(!(q=h->next)) return; if(q->key > q->next->key) {

// prüfe erstes Element // vergleiche mit zweitem Element

}

go=1;

h->next=swap(q);

}

q=h->next; p=q->next; while(p->next!=NULL) {

if(p->key > p->next->key) {

go=1;

q->next=swap(q->next);

}

q=q->next; p=q->next;

}

// und tausche, wenn dieses kleiner ist

// betrachte die folgenden Elemente // durchlaufe die gesamte Liste // vergleiche mit nächstem Element

// und tausche, wenn dieses kleiner ist

// gehe zum nächsten Element

return;

}

//*********************************************************************** // Hauptprogramm //*********************************************************************** void main() {

}

struct element *head; printf("\n\nBUBBLE SORT IN VERKETTETER LISTE \n");

head=(struct element *) malloc(sizeof(struct element)); head->next=NULL;

for(;;) {

// Listenkopf

// Arbeitsschleife

printf("\n\n1: anhängen 2: sortieren 3: auflisten 4: ende\n");

switch(getch()) { case '1': add(head); break; case '2': sort(head); break; case '3': list(head); break;

return;

// Kommando lesen

case '4': case 27:

default: break;

}

}

3-28

Aufgaben und Lösungen

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

11.5 Höhere Sortierverfahren

Aufgabe 11.5.1 (T1)

a) Was bedeutet die Aussage, dass der Quick-Sort entarten kann?

b) Welches Sortierverfahren benötigt die geringste Anzahl von Schlüsselvergleichen?

Aufgabe 11.5.2 (P2)

Modifizieren Sie die im Text angegebene Funktion quicksort(

sie dasselbe Interface erhält, wie die in der C-Bibliothek enthaltene Funktion qsort(

Also int quicksort(void *a, int n, size_t w, int (*cmp)()) Vergleichen Sie die Performance Ihres Programm mit der C-Funktion qsort(

) so, dass

Aufgabe 11.5.3 (P2)

Schreiben Sie unter Verwendung der C-Funktion qsort() ein Programm zum Sortieren eines Feldes von Zeigern, die auf Strings deuten. Die Strings sollen dabei in absteigender Reihenfolge lexikografisch sortiert werden, also c vor b vor a etc.

Aufgabe 11.5.4 (M2)

Ein Test habe ergeben, dass in Abhängigkeit von der Anzahl n der Daten für das Sortieren von Integer-Arrays mit dem Insertion-Sort auf einer bestimmten Maschine t i =2.8n 2 10 -5 Se- kunden benötigt werden. Mit dem Quick-Sort benötigt man t q =1.4nld(n)10 -4 Sekunden. Von welchem n ab ist der Quick-Sort schneller als der Insertion-Sort?

Lösung

2.8n 2 10 -5 =1.4nld(n)10 -4 ist nach n aufzulösen. Es folgt:

0.2n=ld(n) und 2 0.2n =n

Wertetabelle:

n

2 0.2n

10

2 2 =4

20

2 4 =16

22

2

4.4

21.1

23

2

4.6

24.25

ab n=23 ist der Quick-Sort schneller

Aufgaben und Lösungen

2-29

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

11.6 Sortieren externer Dateien

Aufgabe 11.6.1 (T1)

a) Was ist ein nicht-flüchtiger Speicher?

b) Was ist sequentieller und halbsequentieller Speicherzugriff?

c) Was versteht man unter der Zykluszeit im Zusammenhang mit Speicherzugriffen?

d) Was bewirkt eine Defragmentierung?

e) Was ist der Interleave-Faktor?

Aufgabe 11.6.2 (T1)

a) Grenzen Sie die Begriffe direktes und natürliches Mischen ab.

b) Um welchen Faktor könnte das Sortieren durch Mischen schneller werden, wenn man an- stelle von zwei Sequenzen jeweils vier Sequenzen mischt?

c) Nennen Sie den wesentlichen Vorteil des Ein-Phasen-Mischens im Vergleich zum Zwei- Phasen-Mischen.

Aufgabe 11.6.3 (L1)

Sortieren Sie die Daten a = { 27, 31, 11, 42, 89, 16, 17, 14, 12, 64, 50, 61, 72, 26, 28, 32, 66, 19, 22, 83, 87, 99 } nach dem in Abbildung 11.6.3 vorgeführten Muster per Hand unter Verwendung des natürlichen Mischens mit zwei Hilfsbändern b und c.

Aufgabe 11.6.4 (P4)

Schreiben Sie eine Funktion zum Sortieren durch Mischen eines Arrays am Platz. Es darf also kein wesentlicher zusätzlicher, von der Anzahl n der Daten abhängiger Speicherplatz verwendet werden.

3-30

Aufgaben und Lösungen

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

12

Bäume und Graphen

12.1 Binärbäume

Aufgabe 12.1.1 (T1)

a) Was ist ein Nullbaum?

b) Wodurch ist ein innerer Knoten definiert?

c) Was versteht man unter Fädelung?

d) Was versteht man unter Preorder, Inorder und Postorder?

e) Beschreiben Sie das Ordnungskriterium von binären Suchbäumen.

Lösung

Aufgabe 12.1.2 (L2)

Gegeben sei der nebenstehende Binärbaum.

a) Geben Sie die Tiefe des Baumes an.

b) Handelt es sich um einen erweiterten Binärbaum?

c) Handelt es sich um einen

vollständigen Binärbaum?

d) Geben Sie die Durchsuchungslisten an:

- Hauptreihenfolge (Preorder)

- Nebenreihenfolge (Postorder)

- symmetrischer Reihenfolge (Inorder)

- Ebenenreihenfolge (Levelorder)

2
2

1

3

4

6
6

5

7

8

11 10 12
11
10
12

9

Lösung

a) Die Tiefe t eines Baumes ist definiert als die Anzahl der Knoten des längsten Astes. Hier ist also t=4.

b) Für einen erweiterten Binärbaum gilt, dass jeder Knoten entweder keinen Nachfolger hat oder zwei hat. Der abgebildete Teilbaum ist also kein erweiterter Binärbaum, da der Kno- ten 10 nur einen Nachfolger hat.

c) Bei einem vollständigen Binärbaum sind alle Ebenen (Niveaus), evtl. mit Ausnahme der letzten, vollständig besetzt. Hier sind die Ebenen 0, 1 und 2 vollständig besetzt, die letzte Ebene 3 ist nicht vollständig besetzt. Es handelt sich also um einen vollständigen Baum.

d) Hauptreihenfolge, Preorder: Wurzel, linker Teilbaum, rechter Teilbaum

8,4,2,1,3,6,5,7,11,10,9,12

Nebenreihenfolge, Postorder: linker TB, rechter TB, Wurzel

1,3,2,5,7,6,4,9,10,12,11,8

Symmetrische Reihenfolge, Inorder: linker TB, Wurzel, rechter TB

1,2,3,4,5,6,7,8,9,10,11,12

Ebenenreihenfolge, Levelorder: Ebenenweise von links nach rechts

Aufgaben und Lösungen

2-31

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

8,4,11,2,6,10,12,1,3,5,7,9

Aufgabe 12.1.3 (P2)

In einem verkettet gespeicherten Binärbaum seien numerische Werte gespeichert. Schreiben Sie ein Programm zur Durchsuchung dieses Baumes in Hauptreihenfolge, das folgende In- formationen ausgibt: Anzahl der besuchten Knoten, kleinster Inhalt, größter Inhalt, Mittelwert aller Inhalte.

Programmieren Sie eine rekursive und eine iterative Varianten mit folgender Knoten-Struktur:

struct node { double value; struct node *l; struct node *r; };

Lösung

//****************************************************************** // Ausgabe eines Baumes in Hauptreihenfolge (Preorder) // Rekursive Variante. // Rückgabewert: Anzahl der besuchten Knoten.

//****************************************************************** int tree_preorder_rec(struct node *w) { int cnt=0;

}

if(w==NULL) return cnt; printf("%d ",h->info); cnt++;

if(w->l) cnt+=tree_preorder(w->l); // behandle linken Nachfolger if(w->r) cnt+=tree_preorder(w->r); // behandle rechten Nachfolger

return cnt;

// Der Baum ist leer // Behandle (drucke) den Knoten // Knotenzähler hochzählen

//****************************************************************** // Ausgabe eines Baumes in Hauptreihenfolge (Preorder) // Iterative Variante unter Verwendung eines Stacks. // MAXDEPTH gibt die maximale Tiefe des Baums an. // Rückgabewert: Anzahl der besuchten Knoten. //****************************************************************** int tree_preorder_iter(struct node *w) { struct node h, *stack[MAXDEPTH+1]={NULL}; int s=0,cnt=0;

}

if(w==NULL) return cnt; h=w; do { do { if(h->r) stack[++t]=h->r; printf("%d ",h->info); cnt++; } while (h=h->l); } while (h=stack[s--]); return cnt;

// Der Baum ist leer

// rechten Nachfoler in Stack // Behandle (drucke) den Knoten // Knotenzähler hochzählen // gehe zu linkem Nachfolger // hole Zeichen aus dem Stack

3-32

Aufgaben und Lösungen

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

Aufgabe 12.1.4 (L2)

Ordnen Sie die in der Reihenfolge {6, 9, 3, 7, 13, 21, 10, 8, 11, 14, 5} gegebenen Zahlen als binären Suchbaum an.

Lösung

6

6

9 6
9
6

6 6 6 9 6 6   3 6 9 7 6 9  

6 6 9 6 6 6   3 6 9 7 6 9  
6 6 9 6 6 6   3 6 9 7 6 9  
6
6
 
3
3
6 9 7 6 9
6
9
7
6
9
 

3

9

3

 

9

6
6

6   3 6 9 7 6 9   3 9 3   9 3 3 9

3

3 3 9   3

33 9   3

3 3 9   3
9
9
 

3

7

13

13

7

7 13 7   7
7 13 7   7
 

7

7 13 7   7
7 13 7   7
6

6

6
 

13

13

6

6

13

13

6

6

3

3 3 9   3 9  

33 9   3 9  

3 3 9   3 9  
9
9
 

3

3 3 9   3 9  
9
9
 
7
7
7 7 13 7 13
7 7 13 7 13
7
7
7 7 13 7 13
7 7 13 7 13

13

7
7
7 7 13 7 13
7 7 13 7 13

13

13

 

8

  8 10 21 8 10 21

10

21

8

  8 10 21 8 10 21
10

10

21

6

6

6
 

11

 

11

14

3
3
3 9  
9
9
 
 

5

7
7
  5 7 13
13

13

8

10 11
10
11

21

Binärer Suchbaum

14

Aufgabe 12.1.4 (P4)

Schreiben Sie ein Programm zum Verwalten eines binären Suchbaums. Es sollen die Funk- tionen Initialisieren des Baums, Eingabe, Suchen und Löschen eines Knotens und Ausgabe der Knoteninhalte in Haupt-, Neben- und symmetrischer Reihenfolge. Verwenden Sie dabei die Knotenstruktur struct node { int info; struct node *l; struct node *r; };

Lösung

//************************************************************************ // Programm zur Verwaltung eines binären Suchbaumes // mit Eingabe, Suchen, Löschen von Knoten und Ausgabe in // Hauptreihenfolge, symmetrischer Reihenfolge und Nebenreihenfolge. //************************************************************************

#include <stdlib.h> #include <stdio.h> #include <conio.h> #include <malloc.h> #include <stdio.h> #include <string.h>

Aufgaben und Lösungen

2-33

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

#define CR 13 #define ESC 27 #define BLNK 32 #define DIM 80 #define MAXDEPTH 10

struct node { int key; struct node *l,*r;

// Schlüssel (Informationsteil) // Zeiger auf die Nachfolger

};

// Baumstruktur

int getkey();

// Funktionsprototypen

void prelist(struct node *p); void inlist(struct node *p); void postlist(struct node *p); int prelist_iter(struct node *p); struct node *node_init(int key); int tree_search(struct node *p, int key); int tree_del(struct node *p, int key); int tree_in(struct node *root, int key); void tree_free(struct node *p);

//------------------------------------------------------------------------

// Hauptprogramm

//------------------------------------------------------------------------

int main() { printf("\nEingabe und Ausgabe von binaeren Suchbaeumen"); printf("\n============================================\n"); int c=0, i, e, key; char buffer[DIM+1]; struct node *root=NULL, *p;

while(c!=ESC) { printf("\n\nNeuer Baum

(n):");

printf("\nKnoten eingeben (e):");

printf("\nAusgabe printf("\nSuchen printf("\nLoeschen printf("\nBeenden c=toupper(getkey()); if(c==ESC) return 0;

(s):");

(a):");

(l):");

<ESC>: ");

// Arbeitsschleife // Menü

// Kommando lesen // Programm beenden

////////////////////////////////////////// Eingabe if(c=='N' || c=='E') { if(c=='E') e=1; else e=0;

i=1;

if(e) {

// Eingabe eines Knotens

if(root==NULL) { printf("\n\nDer Baum ist nicht initialisiert!");

i=0;

}

else printf("\n\nEingabe eines Schluessels (Integer-Zahl):");

}

else { tree_free(root);

root=node_init(0);

if(root==NULL) {

// Anlegen eines neuen Baums // alten Baum löschen // Baum initialisieren // nicht genug Speicher

printf("\n\nNicht genug Speicher!");

i=0;

}

else { printf("\n\nEs wird ein neuer Baum aufgebaut.\nEingabe "); printf("von Schluesseln (Integer-Zahlen), beenden mit <ESC>:");

}

}

3-34

Aufgaben und Lösungen

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

if(i) while(c!=ESC) { printf("\n> ");

i=0;

// Schleife für Eingabe // neues Element

while((c=getkey())!=CR) { if(i==DIM) break;

// Zeichen lesen // Eingabe zu lang

if(c>=BLNK) {

// druckbares Zeichen

putch(c);

//

anzeigen

buffer[i++]=c;

//

in Puffer kopieren

}

}

}

else if(c==CR || c==ESC) break;

}

if(i>0) {

buffer[i]=0;

key=atoi(buffer); i=tree_in(root,key); if(i==0) {

// Eingabe beenden

// Puffer abschließen // Puffer in Integer konvert. // Element einfügen // nicht genug Speicher

printf("\n\nNicht genug Speicher!");

break;

}

}

if(e) c=ESC;

////////////////////////////////////////// Ausgabe des Baums else if(c=='A') { if(root==NULL) printf("\n\nDer Baum ist nicht initialisiert!"); else if(root->l==NULL) printf("\n\nDer Baum ist leer!"); else { printf("\n\nAusgabe:\n");

printf("\nHauptreihenfolge: "); prelist_iter(root->l); printf("\nSym. Reihenfolge: "); inlist(root->l); printf("\nNebenreihenfolge: "); postlist(root->l);

}

}

////////////////////////////////////////// Suchen eines Schlüssels

else if(c=='S') { if(root==NULL) printf("\n\nDer Baum ist nicht initialisiert!"); else if(root->l==NULL) printf("\n\nDer Baum ist leer!"); else { printf("\n\nBitte zu suchenden Schluessel eingeben: "); scanf("%d",&key); i=tree_search(root,key); if(i==-1) printf("\nSchluessel %d nicht gefunden.",key); else printf("\nSchluessel %d nach %d Schritten gefunden.",key,i);

}

}

////////////////////////////////////////// Löschen eines Schlüssels else if(c=='L') { if(root==NULL) printf("\n\nDer Baum ist nicht initialisiert!"); else if(root->l==NULL) printf("\n\nDer Baum ist leer!"); else { printf("\n\nBitte zu loeschenden Schluessel eingeben: "); scanf("%d",&key); i=tree_del(root,key); if(i==-1) printf("\nSchluessel %d nicht gefunden.",key);

else printf("\nSchluessel %d nach %d Schritten geloescht.",key,i);

}

}

else printf("\n\nFalsche Eingabe!");

c=0;

// Falsche Eingabe

Aufgaben und Lösungen

2-35

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

}

}

tree_free(root);

return 0;

// Speicher freigeben

//------------------------------------------------------------------------

// Ein Zeichen von der Tastatur lesen.

// Hinweis: Manche Sonderzeichen bestehen aus zwei Byte, wobei das erste // Byte den Wert 0 oder 224 hat. // Rückgabe-Wert:

//

8-Bit ASCII-Code des Zeichens oder der negative Wert des Codes,

//

wenn es sich um ein Sonderzeichen aus zwei Byte handelt.

//------------------------------------------------------------------------

int getkey() { int i; i=getch(); if(kbhit()) return -getch(); return i;

}

// erstes Byte // noch ein Byte?

//------------------------------------------------------------------------

// Ausgabe eines Baumes in Hauptreihenfolge (Preorder)

//------------------------------------------------------------------------

void prelist(struct node *p) { printf("%d ",p->key); if(p->l) prelist(p->l); if(p->r) prelist(p->r);

}

// behandle Wurzel (drucke key) // behandle linken Nachfolger // behandle rechten Nachfolger

//------------------------------------------------------------------------

// Ausgabe eines Baumes in symmetrischer Reihenfolge (Inorder)

//------------------------------------------------------------------------

void inlist(struct node *p) { if(p->l) inlist(p->l); printf("%d ",p->key); if(p->r) inlist(p->r);

}

// behandle linken Nachfolger // behandle Wurzel (drucke key) // behandle rechten Nachfolger

//------------------------------------------------------------------------

// Ausgabe eines Baumes in Nebenreihenfolge (Postorder)

//------------------------------------------------------------------------

void postlist(struct node *p) { if(p->l) postlist(p->l); if(p->r) postlist(p->r); printf("%d ",p->key);

}

// behandle linken Nachfolger // behandle rechten Nachfolger // behandle Wurzel (drucke key)

//------------------------------------------------------------------------

// Ausgabe eines Baumes in Hauptreihenfolge - iterative Lösung // Rückgabewert: 0 für normale Ausführung

//

//------------------------------------------------------------------------

int prelist_iter(struct node *p) { struct node *s[MAXDEPTH]={NULL}; int t=0; if(p==NULL) return 0; while(p) { while(p) { if(p->r) {

// Stack // Stack-Pointer // der Baum ist leer // Arbeitsschleife // gehe nach links bis zu einem Blatt // gibt es einen rechten Nachfolger?

-1 bei Stack-Überlauf

if(++t==MAXDEPTH) return -1; // Stack-Überlauf

s[t]=p->r;

// rechten Nachfolger in Stack

}

printf("%d ",p->key);

// bearbeite Wurzel (drucke key)

3-36

Aufgaben und Lösungen

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

 

p=p->l;

// weiter zum linken Nachfolger

}

p=s[t--];

// Knoten aus Stack holen

}

return 0;

 

}

//------------------------------------------------------------------------

// Einfügen eines Elementes in einen Binärbaum // Rückgabewert: 0 bei normaler Ausführung

//

//------------------------------------------------------------------------

int tree_in(struct node *root, int key) { int dir=0; struct node *p, *v=root, *k=root->l;

-1 wenn nicht genug Speicher

p=node_init(key); if(p==NULL) return -1; while(k) { v=k; if(key<k->key) { k=k->l;

dir=0;

// neuen Knoten erzeugen // nicht genug Speicher

//

// Vorgänger

Einfügestelle suchen

// Verzweige nach links // Verzweigungsrichtung merken

}

else {

// Verzweige nach rechts

k=k->r;

dir=1;

// Verzweigungsrichtung merken

}

}

if(dir) v->r=p;

else v->l=p;

}

// Einfügen von p als rechten

//

oder linken Nachfolger

//------------------------------------------------------------------------

// Suchen eines Schlüssels key in einem Binärbaum // Rückgabewert: Anzahl der Schritte

//

//------------------------------------------------------------------------

int tree_search(struct node *root, int key) { int cnt=1;

-1 wenn nicht gefunden

struct node *k=root->l; while(k) { if(k->key==key) return cnt; else if(key < k->key) k=k->l; else k=k->r; cnt++;

// Start mit der Wurzel // Schlüssel key suchen // Schlüssel gefunden // gehe zum linken Nachfolger // gehe zum rechten Nachfolger // Schrittzähler inkrementieren

}

return -1;

// nicht gefunden

}

//------------------------------------------------------------------------

// Löschen eines Schlüssels key in einem Binärbaum // Rückgabewert: Anzahl der Schritte

//

//------------------------------------------------------------------------

int tree_del(struct node *root, int key) { int cnt=1, dir=0;

-1 wenn nicht gefunden

struct node *v=root, *k=root->l; struct node *s, *vs; while(k) { if(k->key==key) break; v=k; if(key < k->key) { k=k->l;

// Start mit der Wurzel // Symm. Vorgänger s und dessen Vorg. // Schlüssel key suchen // Schlüssel gefunden // Vorgänger merken // Schlüssel vergleichen // gehe zum linken Nachfolger

Aufgaben und Lösungen

2-37

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

dir=0;

// Verzweigungsrichtung merken

}

else {

k=k->r;

// gehe zum rechten Nachfolger

dir=1;

// Verzweigungsrichtung merken

}

cnt++;

// Schrittzähler inkrementieren

}

if(k==NULL) return -1;

///////////////////////////////////// Blatt löschen

if(k->l==NULL && k->r==NULL) { if(dir) v->r=NULL;

v->l=NULL;

}

///////////////////////////////////// Knoten mit einem Nachf. löschen else if(k->l==NULL && k->r!=NULL) {// k hat nur rechten Nachfolger

// nicht gefunden

// Blatt ist rechter Nachfolger // Blatt ist linker Nachfolger

else

}