• Keine Ergebnisse gefunden

¨Ubungspaket 32 Einfach verkettete, sortierte Liste

N/A
N/A
Protected

Academic year: 2021

Aktie "¨Ubungspaket 32 Einfach verkettete, sortierte Liste"

Copied!
19
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

Ubungspaket 32 ¨

Einfach verkettete, sortierte Liste

Ubungsziele: ¨

1. Aufbau einer einfach verketteten, sortierten Liste 2. Traversieren von Listen

3. Vereinfachung durch ein Dummy-Element Skript:

Kapitel: 75 und insbesondere ¨ Ubungspaket 29 und 31 Semester:

Wintersemester 2021/22 Betreuer:

Thomas, Tim und Ralf Synopsis:

In diesem ¨ Ubungspaket werden wir endlich eine einfach verkettete, sor-

tierte Liste programmieren. Dabei orientieren wir uns an der in der

Vorlesung vorgestellten Wirth’schen Variante, die das Programmieren

deutlich vereinfacht. Da das Programmieren sortierter Listen erst mal

recht komplex ist, werden wir die wesentlichen Aspekte wiederholen

bzw. gezielt darauf hin arbeiten. Also, nicht verzweifeln, sondern ein-

fach probieren. . .

(2)

Teil I: Stoffwiederholung

Aufgabe 1: Allgemeine Fragen zu Listen

Erkl¨are mit eigenen Worten, was eine einfach verkettete, sortierte Liste ist.

Eine Liste besteht aus Elementen, die hintereinander aufgereiht sind. Der Begriff

”line- ar” besagt, dass es in dieser Liste keinerlei Verzweigungen oder dergleichen gibt. Mit Ausnahme von Anfang und Ende gibt es also immer genau einen Nachfolger sowie einen Vorg¨anger. Schließlich besagt

”sortiert,“ dass die einzelnen Elemente hinsichtlich irgend- eines Kriteriums auf- oder absteigend sortiert sind.

Was versteht man unter

”Traversieren“?

Traversieren bedeutet, dass man eine komplexe Datenstruktur elementweise

”abarbeitet“.

Ein Beispiel w¨are, dass man eine lineare Liste Element f¨ur Element ausgibt. Traversieren k¨onnte aber auch sein, dass man Element f¨ur Element l¨oscht.

Was ist der Unterschied zwischen einer linearen, sortierten Liste und einem Stack?

Von der Struktur her sind beide identisch. Ausgehend von einem Startzeiger folgt Element nach Element, bis man das Ende der Liste erreicht hat. Der einzige Unterschied betrifft die Stelle, an der man neue Elemente einf¨ugt (und wieder entfernt). Bei einem Stack wird immer vorne eingef¨ugt und auch wieder entfernt, weshalb man einen Stack auch mit dem Begriff FIFO f¨ur

”first in first out“ attributiert. In einer sortierte Liste hingegen, werden die neuen Elemente immer nur dort eingef¨ugt, wo sie die Eigenschaft

”sortiert“ aufrecht erhalten; dies kann prinzipiell also an jeder beliebigen Stelle der Liste sein.

Aufgabe 2: Detailfragen zu Listen

Die folgenden Fragen beziehen sich immer auf eine einfach verkettete, sortierte Liste oder deren Elemente.

Wie viele Nachfolger hat jedes Element? Einen, mit Ausnahme des letzten Elementes Wie viele Vorg¨anger hat jedes Element? Einen, mit Ausnahme des ersten Elementes Woran erkennt man das Listenende? Der

”next“-Zeiger ist ein Null-Zeiger Wie findet man den Listenanfang? Mittels eines Startzeigers

Wie kann man eine Liste ausgeben? Am einfachsten mit einer for-Schleife Wie kann man sie invertiert ausgeben? Am einfachsten rekursiv

Wie muss man sich das vorstellen? Erst den Rest, dann das aktuelle Element

(3)

Aufgabe 3: Einf¨ ugen neuer Elemente in eine Liste

Im Skript haben wir recht ausf¨uhrlich ¨uber die algorithmische Umsetzung des sortierten Einf¨ugens geschrieben. Zur Rekapitulation haben wir eine Reihe von Fragen zusammenge- tragen.

Welche vier F¨alle muss man beim Einf¨ugen neuer Elemente beachten?

1. Die Liste kann leer sein; der Startzeiger ist ein Null-Zeiger 2. Das neue Element kommt vor das erste Element

3. Das neue Element kommt irgendwo mitten drin hinein 4. Das neue Element muss an das Ende angeh¨angt werden In wie vielen F¨allen wird der Startzeiger ver¨andert? 2 (zwei)

Ist das programmiertechnisch gut oder schlecht? It’s: Pain in the Ass

Warum? Man ben¨otigt Zeiger auf Zeiger

In den meisten F¨allen benutzt man eine Schleife, um diejenige Stelle zu finden, an der man einf¨ugen m¨ochte. Welches Programmierproblem ergibt sich dadurch?

In der Regel bleibt man ein Element zu sp¨at stehen. Daher ben¨otigt man entweder einen zweiten Zeiger, der ein Element hinterher hinkt, oder muss bei der Schleifenbedingung ein weiteres Element vorausschauen. Beides ist nicht besonders sch¨on, l¨asst sich aber im Standardalgorithmus nicht vermeiden.

Aufgabe 4: Die Wirth’sche Variante

Im Skript haben wir auch die Variante von Wirth diskutiert. Was ist die wesentliche Idee?

Es gibt ein zus¨atzliches Element, das immer am Ende der Liste zu finden ist. Dieses Element geh¨ort zwar technisch zur Liste, ist aber nicht Bestandteil der Datenelemente.

Die Wirth’sche Idee hat einige Besonderheiten und Vorteile. Hierzu folgende Fragen:

Wird der Startzeiger ver¨andert? Nein, nie, er zeigt immer auf das selbe Element Wie viele F¨alle werden unterschieden? Es gibt nur einen Fall

Wof¨ur ist das Dummy Element? Der

”Suchzeiger“ bleibt sp¨atestens hier stehen Wo bleibt der

”Suchzeiger“ stehen? Ein Element zu sp¨at

Wie wird dann aber eingef¨ugt? Durch geschicktes Vertauschen

Welchen Algorithmus nehmen wir? 100-pro den Wirth’schen: intelligent und einfach

(4)

Teil II: Quiz

Aufgabe 1: Positionierung innerhalb von Listen

Nehmen wir an, wir haben die folgende Definition einer Liste sowie die folgende Funktion zur Positionierung eines Zeigers.

1 t y p e d e f s t r u c t u s e r { int i ; } D A T A ; 2

3 t y p e d e f s t r u c t _ e l e m e n t {

4 s t r u c t _ e l e m e n t * n e x t ;

5 D A T A d a t a ;

6 } ELEMENT , * EP ;

7

8 EP p o s i t i o n ( EP listp , int val )

9 {

10 w h i l e ( listp - > d a t a . i < val ) 11 l i s t p = listp - > n e x t ;

12 r e t u r n l i s t p ;

13 }

Nun gehen wir davon aus, dass wir bereits die folgende Liste (in unserem Hauptprogramm) aufgebaut haben:

start next:

i : 1

next:

i : 3

next:

i : 5

next:

i : 7

Auf welche Elemente zeigen die Zeiger start und p nach folgenden Funktionsaufrufen:

Funktionsaufruf start p Anmerkung

p = position( start, 3 ) Element

”1“ Element

”3“

p = position( start, -1 ) Element

”1“ Element

”1“

p = position( start->next, -1 ) Element

”1“ Element

”3“

p = position( start, 6 ) Element

”1“ Element

”7“

p = position( start, 7 ) Element

”1“ Element

”7“

p = position( start, 8 ) - - - Programmabsturz:

Dereferenzieren eines Null-Zeigers

Schlussfolgerung: Programmabsturz, falls val gr¨oßer als der gr¨oßte Listenwert ist.

(5)

Aufgabe 2: Positionierung: zweiter Versuch

Aufgrund des vorherigen Programmabsturzes bei Suchwerten, die gr¨oßer als das gr¨oßte Element der Liste waren, haben wir unser Programm wie folgt ver¨andert:

1 t y p e d e f s t r u c t u s e r { int i ; } D A T A ; 2

3 t y p e d e f s t r u c t _ e l e m e n t {

4 s t r u c t _ e l e m e n t * n e x t ;

5 D A T A d a t a ;

6 } ELEMENT , * EP ;

7

8 EP p o s i t i o n ( EP listp , int val )

9 {

10 w h i l e ( listp - > next - > d a t a . i < val ) 11 l i s t p = listp - > n e x t ;

12 r e t u r n l i s t p ;

13 }

Wir betrachten wieder die gleiche Liste sowie die gleichen Funktionsaufrufe:

start next:

i : 1

next:

i : 3

next:

i : 5

next:

i : 7

Auf welche Elemente zeigen die Zeiger start und p nach folgenden Funktionsaufrufen:

Funktionsaufruf start p Anmerkung

p = position( start, 3 ) Element

”1“ Element

”1“

p = position( start, -1 ) Element

”1“ Element

”1“

p = position( start->next, -1 ) Element

”1“ Element

”3“

p = position( start, 6 ) Element

”1“ Element

”5“

p = position( start, 7 ) Element

”1“ Element

”5“

p = position( start, 8 ) - - - Programmabsturz:

Dereferenzieren eines Null-Zeigers Schlussfolgerung: Wieder Programmabsturz, fallsvalgr¨oßer als der gr¨oßte Listenwert ist.

Der Versuch ging nach hinten los, meint unser Team von Star-Programmierern ;-))

(6)

Aufgabe 3: Positionierung: dritter Versuch

So richtig sch¨on waren die beiden vorherigen Versuche nicht. Versuchen wir es also noch- mals, diesmal mit einem etwas modifizierten Algorithmus sowie einer modifizierten Liste.

1 t y p e d e f s t r u c t u s e r { int i ; } D A T A ; 2

3 t y p e d e f s t r u c t _ e l e m e n t {

4 s t r u c t _ e l e m e n t * n e x t ;

5 D A T A d a t a ;

6 } ELEMENT , * EP ;

7

8 EP p o s i t i o n ( EP listp , int val )

9 {

10 w h i l e ( listp - > d a t a . i < val && listp - > n e x t != 0 ) 11 l i s t p = listp - > n e x t ;

12 r e t u r n l i s t p ;

13 }

start next:

i : 1

next:

i : 5

next:

i : 7

next:

i : BIG

Auf welche Elemente zeigen die Zeiger start und p nach folgenden Funktionsaufrufen:

Funktionsaufruf start p Anmerkung

p = position( start, 3 ) Element

”1“ Element

”5“

p = position( start, -1 ) Element

”1“ Element

”1“

p = position( start, 6 ) Element

”1“ Element

”7“

p = position( start, 7 ) Element

”1“ Element

”7“

p = position( start, 8 ) Element

”1“ Element

”BIG“ Sp¨atestens bei diesem Element bleibt p stehen.

Schlussfolgerung: Kein Programmabsturz :-)

Position von p: Beim gesuchten oder n¨achstgr¨oßeren Element

(7)

Aufgabe 4: Speicherallokation

Gegeben sei folgendes Programmst¨uck:

1 t y p e d e f s t r u c t u s e r { int i ; } D A T A ; 2

3 t y p e d e f s t r u c t _ e l e m e n t {

4 s t r u c t _ e l e m e n t * n e x t ;

5 D A T A d a t a ;

6 } ELEMENT , * EP ;

7

8 EP n e w E l e m e n t ( int val , EP n e x t )

9 {

10 EP new = m a l l o c ( s i z e o f ( E L E M E N T ) ) ;

11 if ( new )

12 {

13 new - > d a t a . i = val ; new - > n e x t = n e x t ;

14 }

15 r e t u r n new ;

16 }

Vervollst¨andige das Speicherbild f¨ur die folgenden beiden Aufrufe vonnewElement(). Wie bei fast allen ¨Ubungen gehen wir davon aus, dass sowohlint-Werte als auch Zeiger immer vier Bytes im Arbeitsspeicher belegen. Ferner nehmen wir an, dass der Aufruf der Funktion newElement() die Adresse 0xFA00 liefert.

1. EP p = newElement( 4711, 0 );

Segment: Stack

Adresse Variable Wert 0xFE7C EP p: 0xFA00

Segment: Heap Adresse Wert 0xFA04 4711 0xFA00 0x0000 2. ELEMENT el; EP p = newElement( 815, & el );

Segment: Stack

Adresse Variable Wert

0xFE7C el.data.i: --- 0xFE78 el.next : ---

0xFE74 EP p : 0xFA00

Segment: Heap Adresse Wert 0xFA04 815 0xFA00 0xFE78

Vielen hilft es beim Verstehen, wenn sie zus¨atzlich noch die Zeiger in die Speicherbildchen einzeichnen.

(8)

Aufgabe 5: Elemente einf¨ ugen

F¨ur die beiden letzten Quizaufgaben haben wir den Positionierungsalgorithmus aus Auf- gabe 3 um ein paar Zeilen erweitert. Der erste Parameter dieser neuen Funktion ist die bisher im Arbeitsspeicher aufgebaute Liste. Der zweite Parameter ist ein Zeiger auf ein neues Element, wie wir es gerade eben in der vorherigen Aufgabe gesehen haben:

1 v o i d i n s e r t E l e m e n t ( EP list , EP new )

2 {

3 D A T A tmp ;

4 w h i l e ( list - > d a t a . i < new - > d a t a . i && list - > n e x t != 0 ) 5 l i s t = list - > n e x t ;

6 tmp = new - > d a t a ;

7 * new = * l i s t ;

8 list - > n e x t = new ; 9 list - > d a t a = tmp ;

10 }

In den beiden folgenden Aufgaben wird die Funktion insertElement() immer mit der selben Liste aufgerufen. Sie hat nur einDatenelement.Beim ersten Mal wird das Datenele- ment5, beim zweiten Mal 13 eingef¨ugt. Der in den Abbildungen verwendete start-Zeiger ist der im Hauptprogramm verwaltete Startzeiger der Liste. Er dient nur zur Illustrierung und wird nicht weiter verwendet. Bearbeite nun die beiden F¨alle:

1. Einf¨ugen des

”Datenelementes“ 5: Zeile 3:

next:

i : 9

next:

i : BIG next:

i : 5

new start

Ende Zeile 9:

next:

i : 5

next:

i : BIG next:

i : 9

new start list

(9)

2. Einf¨ugen des

"Datenelementes\ 13: Zeile 3:

next:

i : 9

next:

i : BIG next:

i : 13 new

start

Ende Zeile 9:

next:

i : 9

next:

i : 13 next:

i : BIG new

start list

Damit w¨aren wir jetzt f¨ur den Anwendungsfall ger¨ustet.

(10)

Teil III: Fehlersuche

Aufgabe 1: Ausgabe von Listen

Die meisten Fehler- und Problemf¨alle haben wir bereits besprochen. Doch Dr. L. Ist- Wirth hat das Ausgeben Wirth’scher Listen noch nicht im Griff. Was lief hier schief. . . ?

1 v o i d p r t L i s t ( EP p )

2 {

3 p r i n t f ( " L i s t e : " ) ;

4 do {

5 p = p - > n e x t ;

6 p r i n t f ( " % i " , p - > d a t a . i ) ;

7 } w h i l e ( p != 0 ) ;

8 p r i n t f ( " \ n " ) ;

9 }

Leider erscheinen hier einige Anweisungen in genau der falschen Reihenfolge.

Zeile Fehler Erl¨auterung Korrektur

5 Hier wird bereits zum n¨achsten Element weiter gegan- gen. Das w¨are eigentlich nicht so schlimm, doch ist bis hier beim ersten Schleifendurchlauf noch nichts ausge- geben worden. Insofern w¨urde das erste Datenelement verborgen bleiben.

auf sp¨ater verschieben

. . . .

7 Die Schleife l¨auft so lange, wie der Zeiger pkein Null- Zeiger ist. Dadurch wird auch das letzte Element der Liste ausgeben. In einer Wirth’schen Liste ist dies aber das Dummy-Element und geh¨ort nicht zum Datenbe- stand und sollte auch nicht ausgegeben werden.

p->next != 0

Programm mit Korrekturen:

1 # i n c l u d e < s t d i o . h >

2

3 v o i d p r t L i s t ( EP p )

4 {

5 p r i n t f ( " L i s t e : " ) ;

6 for ( ; p - > n e x t ; p = p - > n e x t ) 7 p r i n t f ( " % i " , p - > d a t a . i ) ; 8 p r i n t f ( " \ n " ) ;

9 }

(11)

Teil IV: Anwendungen

Aufgabe 1: Definition einer geeigneten Datenstruktur

1. Aufgabenstellung

Definiere eine Datenstruktur f¨ur lineare Listen, in der jedes Element ein einzelnes Zeichen aufnehmen kann.

2. Kodierung

In Anlehnung an ¨Ubungspaket 31definieren wir folgende Datenstruktur:

1 t y p e d e f s t r u c t u s e r {

2 c h a r c ;

3 } DATA , * DP ; // the u s e r d a t a

4

5 t y p e d e f s t r u c t _ l i s t _ e l e m e n t {

6 s t r u c t _ l i s t _ e l e m e n t * n e x t ; // the a d m i n p a r t

7 D A T A d a t a ; // the u s e r d a t a p a r t

8 } ELEMENT , * EP ;

Aufgabe 2: Allokation eines neuen Elementes

1. Aufgabenstellung

Entwickle eine Funktion, die dynamisch ein neues Listenelement generiert.

2. Entwurf

Deklaration: neues Element alloziieren: EP newElement( char c, EP next );

3. Kodierung

1 EP n e w E l e m e n t ( c h a r c , EP n e x t )

2 {

3 EP p = m a l l o c ( s i z e o f ( E L E M E N T ) ) ;

4 if ( p ) // do we h a v e a new e l e m e n t ?

5 {

6 p - > d a t a . c = c ; p - > n e x t = n e x t ; // i n i t i a l i z e

7 }

8 r e t u r n p ;

9 }

(12)

Aufgabe 3: Sortiertes Einf¨ ugen eines neuen Elementes

1. Aufgabenstellung

Nun wird’s ernst. Entwickle eine Funktion, die ein neues Element an der richtigen Stelle in die Liste einf¨ugt. Wir gehen wieder davon aus, dass die Elemente der Liste aufsteigend sortiert werden sollen.

2. Entwurf

Vervollst¨andige zun¨achst die Funktionsdeklaration:

Neues Element einf¨ugen: void insertElement( EP oldList, EP newElement );

3. Kodierung

1 v o i d i n s e r t E l e m e n t ( EP oldList , EP n e w E l e m e n t )

2 {

3 D A T A tmp = n e w E l e m e n t - > d a t a ; 4 w h i l e ( oldList - > n e x t

5 && n e w E l e m e n t - > d a t a . c > oldList - > d a t a . c ) 6 o l d L i s t = oldList - > n e x t ;

7 * n e w E l e m e n t = * o l d L i s t ; 8 oldList - > n e x t = n e w E l e m e n t ; 9 oldList - > d a t a = tmp ;

10 }

Um sp¨ater die Daten leichter einf¨ugen zu k¨onnen, schreiben wir uns gleich noch eine kleine Hilfsfunktion, die das Ausgeben m¨oglicher Fehlermeldungen kapselt. Damit k¨onnen wir uns jederzeit ein paar sinnvolle Testausgaben generieren, um beispiels- weise den aktuellen Zustand der Liste zu

”erfragen“.

1 int i n s e r t D a t a ( EP list , c h a r c )

2 {

3 EP p = n e w E l e m e n t ( c , 0 ) ; // a new e l e m e n t

4 if ( p == 0 ) // ran out of m e m o r y ?

5 {

6 f p r i n t f ( stderr , " can ’ t i n s e r t % c " , c ) ; 7 f p r i n t f ( stderr , " ; no s p a c e a v a i l a b l e \ n " ) ;

8 }

9 e l s e i n s e r t E l e m e n t ( list , p ) ; // ok , all c l e a r

10 r e t u r n p != 0;

11 }

Durch den Ausdruck derreturn-Anweisung in Zeile10erf¨ahrt die aufrufende Stelle, ob die Daten eingef¨ugt werden konnten.

(13)

Aufgabe 4: Ausgabe der Liste

1. Aufgabenstellung

Entwickle eine Funktion, die nacheinander alle Elemente einer ¨ubergebenen Liste ausgibt.

2. Entwurf

Vervollst¨andige zun¨achst die Funktionsdeklaration:

Liste ausgeben: void prtList( EP list );

3. Kodierung

Diese Funktion ist sehr einfach. Wir m¨ussen nur alle Elemente außer dem letzten in einer Schleife ausgeben. Das letzte Element erkennen wir daran, dass seinnext-Zeiger ein Null-Zeiger ist.

1 v o i d p r t L i s t ( EP p )

2 {

3 p r i n t f ( " L i s t e : " ) ;

4 for ( ; p - > n e x t ; p = p - > n e x t ) 5 p r i n t f ( " % c " , p - > d a t a . c ) ; 6 p r i n t f ( " \ n " ) ;

7 }

Aufgabe 5: Initialisierung der Liste

1. Aufgabenstellung

Jetzt fehlt noch die richtige Initialisierung einer neuen Liste. Im Skript haben wir gesehen, dass der konkrete Datenwert des Dummys unerheblich ist. Wichtig ist hin- gegen, dass der Next-Zeiger ein Null-Zeiger ist. Dies l¨asst sich einfach realisieren.

2. Kodierung

1 EP f i r s t = n e w E l e m e n t ( ’ x ’ , 0 ) ; // d u m m y e l e m e n t Zur Erinnerung: Bei der Wirth’schen Alternative ist es egal, was f¨ur ein Wert im letzten Element steht. Wir haben hier v¨ollig willk¨urlich ein ’x’ gew¨ahlt; es h¨atte auch jeder beliebige andere Wert sein k¨onnen. Der entscheidende Punkt ist, wie bereits mehrfach gesagt, dass der Next-Zeiger ein Null-Zeiger ist.

(14)

Aufgabe 6: Ein Hauptprogramm zum Testen

Den bereits erlernten argc/argv Mechanismus (siehe auch ¨Ubungspaket 25) k¨onnen wir hier sehr gut zum intelligenten Testen unseres Listen-Programms verwenden. Wenn wir auf diesen Mechanismus zur¨uckgreifen, brauchen wir nicht alles fest im Hauptprogramm zu kodieren und k¨onnen leicht und umfangreich testen.

1. Aufgabenstellung

Entwickle ein Hauptprogramm, mit dessen Hilfe wir unsere Listen-Funktionen in geeigneter Art und Weise testen k¨onnen. Mit Hilfe desargc/argv-Mechanismus soll das Hauptprogramm die folgenden Funktionalit¨aten anbieten:

1. Egal, wie wir unser Programm aufrufen, soll es die Liste direkt nach der Initia- lisierung sowie vor dem Programmende ausgeben.

2. Das erste Argument (argv[1]) soll alle Zeichen enthalten, die wir in die Liste aufnehmen. Beispiel: ./myListe vacation soll am Ende zu folgender Ausgabe f¨uhren: a a c i n o t v

3. Sollte noch ein zweites Argument (argv[2]) angegeben werden, dann soll die Liste nach jedem Einf¨ugen ausgegeben werden.

Mittels der bisherigen Vor¨ubungen, insbesondere ¨Ubungspaket 25, sollte die Umset- zung keine gr¨oßeren Schwierigkeiten bereiten.

2. Kodierung

1 int m a i n ( int argc , c h a r ** a r g v )

2 {

3 c h a r * p ;

4 EP f i r s t = n e w E l e m e n t ( ’ x ’ , 0 ) ; // d u m m y e l e m e n t 5 p r t L i s t ( f i r s t ) ; // c h e c k a f t e r i n i t i a l i z a t i o n

6 if ( a r g c > 1 )

7 {

8 for ( p = a r g v [ 1 ]; * p ; p ++ )

9 {

10 i n s e r t D a t a ( first , * p ) ;

11 if ( a r g c > 2 ) // d e b u g m o d e

12 p r t L i s t ( f i r s t ) ;

13 }

14 p r t L i s t ( f i r s t ) ;

15 }

16 e l s e p r i n t f ( " sorry , k e i n e D a t e n v o r h a n d e n \ n " ) ;

17 }

Die n¨achsten beiden Seiten geben das Programm nochmals vollst¨andig wieder.

(15)

Definition der Datenstrukturen:

1 # i n c l u d e < s t d i o . h > // for I / O

2 # i n c l u d e < s t d l i b . h > // for m a l l o c () 3

4 t y p e d e f s t r u c t u s e r {

5 c h a r c ;

6 } DATA , * DP ; // the u s e r d a t a

7

8 t y p e d e f s t r u c t _ l i s t _ e l e m e n t {

9 s t r u c t _ l i s t _ e l e m e n t * n e x t ; // the a d m i n p a r t

10 D A T A d a t a ; // the u s e r d a t a p a r t

11 } ELEMENT , * EP ;

Allokation eines neuen Elementes:

13 EP n e w E l e m e n t ( c h a r c , EP n e x t )

14 {

15 EP p = m a l l o c ( s i z e o f ( E L E M E N T ) ) ;

16 if ( p ) // do we h a v e a new e l e m e n t ?

17 {

18 p - > d a t a . c = c ; p - > n e x t = n e x t ; // i n i t i a l i z e

19 }

20 r e t u r n p ;

21 }

Einf¨ugen eines neuen Elementes:

23 v o i d i n s e r t E l e m e n t ( EP oldList , EP n e w E l e m e n t )

24 {

25 D A T A tmp = n e w E l e m e n t - > d a t a ; 26 w h i l e ( oldList - > n e x t

27 && n e w E l e m e n t - > d a t a . c > oldList - > d a t a . c ) 28 o l d L i s t = oldList - > n e x t ;

29 * n e w E l e m e n t = * o l d L i s t ; 30 oldList - > n e x t = n e w E l e m e n t ; 31 oldList - > d a t a = tmp ;

32 }

(16)

Einf¨ugen neuer Daten:

34 int i n s e r t D a t a ( EP list , c h a r c )

35 {

36 EP p = n e w E l e m e n t ( c , 0 ) ; // a new e l e m e n t

37 if ( p == 0 ) // ran out of m e m o r y ?

38 {

39 f p r i n t f ( stderr , " can ’ t i n s e r t % c " , c ) ; 40 f p r i n t f ( stderr , " ; no s p a c e a v a i l a b l e \ n " ) ;

41 }

42 e l s e i n s e r t E l e m e n t ( list , p ) ; // ok , all c l e a r

43 r e t u r n p != 0;

44 }

Ausgabe der Liste:

46 v o i d p r t L i s t ( EP p )

47 {

48 p r i n t f ( " L i s t e : " ) ;

49 for ( ; p - > n e x t ; p = p - > n e x t ) 50 p r i n t f ( " % c " , p - > d a t a . c ) ; 51 p r i n t f ( " \ n " ) ;

52 }

Das Hauptprogramm:

54 int m a i n ( int argc , c h a r ** a r g v )

55 {

56 c h a r * p ;

57 EP f i r s t = n e w E l e m e n t ( ’ x ’ , 0 ) ; // d u m m y e l e m e n t 58 p r t L i s t ( f i r s t ) ; // c h e c k a f t e r i n i t i a l i z a t i o n

59 if ( a r g c > 1 )

60 {

61 for ( p = a r g v [ 1 ]; * p ; p ++ )

62 {

63 i n s e r t D a t a ( first , * p ) ;

64 if ( a r g c > 2 ) // d e b u g m o d e

65 p r t L i s t ( f i r s t ) ;

66 }

67 p r t L i s t ( f i r s t ) ;

68 }

69 e l s e p r i n t f ( " sorry , k e i n e D a t e n v o r h a n d e n \ n " ) ;

70 }

Endlich fertig :-)

(17)

Nachlese

Die meisten werden froh sein, die Listenverarbeitung halbwegs hinbekommen zu haben.

Wenn man sich aber die L¨osung mit etwas zeitlichem Abstand nochmals anschaut, wird man die eine oder andere Stelle finden, die nicht ganz so sch¨on geworden ist. Dies betrifft insbesondere die FunktionennewElement()undinsertElement(). Warum? Ganz einfach.

Ein Teil dieser Funktionen ist recht generisch, d.h. auch bei anderen Aufgaben einsetzbar.

Andererseits sind einige Komponenten genau auf die gestellte Aufgabe zugeschnitten. Al- so nix da mit Kopieren, wenn man die Algorithmen f¨ur die n¨achste Aufgabe verwenden m¨ochte.

Im Umkehrschluss heißt das: F¨ur die Wiederverwendbarkeit w¨are es sch¨on, wenn wir einen aufgabenspezifischen und einen generellen (auch generisch genannten) Teil h¨atten. Und das ist gar nicht so schwer. Wenn man erst einmal die richtige Position hat (Zeigerposwie im Quiz), kann man das Erzeugen und Einf¨ugen eines neuen Elementes wie folgt realisieren:

1 EP n e w E l e m e n t ( D A T A data , EP n e x t ) // g e n e r i c

2 {

3 EP p = m a l l o c ( s i z e o f ( E L E M E N T ) ) ;

4 if ( p ) // do we h a v e a new e l e m e n t ?

5 {

6 p - > d a t a = d a t a ; p - > n e x t = n e x t ; // i n i t i a l i z e

7 }

8 r e t u r n p ;

9 }

10

11 int i n s e r t E l e m e n t ( EP pos , D A T A d a t a ) // g e n e r i c

12 {

13 EP new = n e w E l e m e n t ( pos - > data , pos - > n e x t ) ;

14 if ( new )

15 {

16 pos - > n e x t = new ;

17 pos - > d a t a = d a t a ;

18 }

19 r e t u r n new != 0;

20 }

F¨ur unsere Zeichen-Liste ist das Finden der richtigen Position sehr einfach:

1 EP f i n d P o s i t i o n ( EP liste , c h a r c ) // s p e c i f i c : b a s e d on c

2 {

3 w h i l e ( liste - > d a t a . c < c && liste - > n e x t ) 4 l i s t e = liste - > n e x t ;

5 r e t u r n l i s t e ;

6 }

(18)

Um ein neues Zeichen in die Liste einzuf¨ugen, m¨ussen wir nun alle drei Funktionen in der richtigen Art und Weise verkn¨upfen. Dabei nutzen wir aus, dass wir das neue Element nicht mit irgendwelchen Daten vorinitialisieren sonder das gefundene Element einfach du- plizieren:

1 int i n s e r t D a t a ( EP list , c h a r c ) // s p e c i f i c

2 {

3 D A T A d a t a = { c }; // w r a p p i n g i n t o s t r u c t

4 if ( ! i n s e r t E l e m e n t ( f i n d P o s i t i o n ( list , c ) , d a t a ) )

5 {

6 f p r i n t f ( stderr , " can ’ t i n s e r t % c " , c ) ; 7 f p r i n t f ( stderr , " ; no s p a c e a v a i l a b l e \ n " ) ;

8 r e t u r n 0;

9 }

10 r e t u r n 1;

11 }

Und der Rest bleibt unver¨andert:

1 v o i d p r t L i s t ( EP p )

2 {

3 p r i n t f ( " L i s t e : " ) ;

4 for ( ; p - > n e x t ; p = p - > n e x t ) 5 p r i n t f ( " % c " , p - > d a t a . c ) ; 6 p r i n t f ( " \ n " ) ;

7 }

8

9 int m a i n ( int argc , c h a r ** a r g v )

10 {

11 c h a r * p ;

12 D A T A d a t a = { ’ x ’ };

13 EP f i r s t = n e w E l e m e n t ( data , 0 ) ; // d u m m y e l e m e n t 14 p r t L i s t ( f i r s t ) ; // c h e c k a f t e r i n i t i a l i z a t i o n

15 if ( a r g c > 1 )

16 {

17 for ( p = a r g v [ 1 ]; * p ; p ++ )

18 {

19 i n s e r t D a t a ( first , * p ) ;

20 if ( a r g c > 2 ) // d e b u g m o d e

21 p r t L i s t ( f i r s t ) ;

22 }

23 p r t L i s t ( f i r s t ) ;

24 }

25 e l s e p r i n t f ( " sorry , k e i n e D a t e n v o r h a n d e n \ n " ) ;

26 }

(19)

Aufgabe 7: Doppelt verkettete Listen

In dieser letzten ¨Ubungsaufgabe geht es um doppelt verkettete Listen. Die Bearbeitung ist vor allem theoretischer Natur, weshalb das Implementieren und Testen am Rechner freiwillig sind.

Wohin zeigen die Zeiger bei doppelt verketteten Listen?

Bei einer doppelt verketteten Liste ben¨otigt man immer zwei Zeiger, einer zeigt zum Nachfolger, der andere zum Vorg¨anger.

Definiere eine Datenstruktur f¨ur ein Element einer doppelt verketteten Liste, die ein int und zwei double Variablen aufnehmen kann.

1 t y p e d e f s t r u c t u s e r

2 {

3 int i ;

4 d o u b l e d1 , d2 ;

5 } DATA , * DP ;

6

7 t y p e d e f s t r u c t _ e l e m e n t

8 {

9 s t r u c t _ e l e m e n t * p r e v i o u s , * n e x t ;

10 D A T A d a t a ;

11 } D_LIST , * DLP ;

Skizziere an einem Beispiel, welche Zeiger beim Einf¨ugen eines neuen Elementes in eine bereits vorhandene Liste in welcher Form umgeh¨angt werden m¨ussen.

Die Grafik zeigt zwei Elemente einer doppelt verketteten Liste. Der Zeiger q zeigt auf das neue Element, das hinter das Element eingef¨ugt werden soll, auf das p zeigt. Die Nummern beziehen sich auf folgendem Programmausschnitt.

(4)

(2) (1)

(3) p

q

1 p - > next - > p r e v i o u s = q ; 2 q - > p r e v i o u s = p ;

3 q - > n e x t = p - > n e x t ;

4 p - > n e x t = q ;

Referenzen

ÄHNLICHE DOKUMENTE

Implementieren Sie eine Zahlenmenge mit Hilfe einer linearen Liste (Hinweise: Ein Menge enthält niemals mehrere Exemplare der

Implementieren Sie eine doppelt verkettete Liste für Objekte in der Klasse LinkedList.. Die Schnittstelle ist durch die abstrakte Klasse List gegeben (für Methodenbeschreibungen

1) Einfügen eines neuen Knotens: Liste war leer.. Grafische Darstellung einer verketteten Liste:3. 2) Einfügen eines neuen Knotens am Anfang.. Grafische Darstellung einer

• Im Gegensatz zu einfach verketteten Listen haben doppelt verkettete Listen in den Knoten eine zusätzliche Instanzvariable für die Referenz auf den Vorgängerknoten. class Node

Skizziere an einem Beispiel, welche Zeiger beim Einf¨ ugen eines neuen Elementes in eine bereits vorhandene Liste in welcher Form umgeh¨ angt werden

[r]

struct dolili *elem_ptr = NULL; // Erzeugen eines Zeigers auf ein Element der Liste Damit Sie dynamisch neue Elemente einer Liste hinzufügen können, müssen Sie einen Teil des

Und dieses Prinzip soll auch noch funktionieren, wenn wir ein Programm haben, das ein paar Gigabyte Platz ben¨otigt und es tausende von freien Bl¨ocken gibt, die aber sehr klein