ALP II Dynamische Datenmengen Datenabstraktion (Teil 2)

O1

O2

O3

O4

SS 2012 Prof. Dr. Margarita Esponda

M. Esponda-Argüero

49

Einfach verkettete Listen head

O1

O2

O3

M. Esponda-Argüero

50

Einführung Einfach verkettete Listen sind die einfachsten dynamischen Datenstrukturen, die zur Laufzeit an den tatsächlichen Speicherbedarf anpassen können. Eine Liste besteht aus einer Menge von Knoten, die untereinander verkettet sind. Jeder Knoten besteht aus einer Referenz auf das eigentliche zu speichernde Objekt und eine Referenz auf das nächste Element der Liste.

head

Das letzte Element der Liste zeigt auf die Konstante null

O1

O2

O3

M. Esponda-Argüero

51

Implementierung head

O1

O2

O3

null

Wir haben eine rekursive Klassendefinition,

class ListNode { T element; ListNode next; ... }

weil das next-Element zu der gleichen ObjektKlasse gehört, die wir gerade definieren.

M. Esponda-Argüero

52

Implementierung

head

O1

O2

class ListNode { T element; ListNode next; // Konstruktoren ListNode( T element, ListNode next ){ this.element = element; this.next = next; } ListNode() { this( null, null ); }

O3

null

Zwei Konstruktoren: Einer, der nur einen leeren Knoten erzeugt und einen, der gleichzeitig ein Objekt in dem Knoten speichert und die Referenz auf den nächsten Knoten bekommt.

} M. Esponda-Argüero

53

Stapel als verkettete Liste Mit Hilfe von verketteten Listen lässt sich sehr einfach ein Stapel implementieren. Wir müssen dabei nicht mehr überprüfen, ob der Stapel voll ist. Wir brauchen ein head-Element, das eine Referenz auf ein ListNodeObjekt ist. Mit dieser Referenz können wir bei einer push-Operation neue Elemente am Anfang der Liste verketten oder entfernen, wenn eine popOperation stattfindet.

head

O1

O2

O3

null

M. Esponda-Argüero

54

Stapel-Schnittstelle In dieser Implementierung werden wir eine EmptyStackException erzeugen bei dem Versuch, ein Element zu entfernen oder zu lesen (pop- und topOperationen), wenn die Liste leer ist. public interface Stack { public void push( T element ); public T pop() throws EmptyStackException; public T top() throws EmptyStackException; public boolean empty(); }

M. Esponda-Argüero

55

Einfache Implementierung der Stapel-Schnittstelle Ein Konstruktor wird definiert, der head mit der Konstante null initialisiert. public class ListStapel implements Stack { /* Instanzvariablen */ private ListNode head; /* Konstruktor */ public ListStapel() { head = null; } /* Methoden */ ... M. Esponda-Argüero

56

Implementierung der empty-Operation head null Der Stapel ist leer, wenn das head-Element auf die Konstante null zeigt

public boolean empty () { return head == null; }

M. Esponda-Argüero

57

Implementierung der push-Operation head

O1

O2

O3

Das T-Objekt e soll am Anfang der Liste eingefügt werden public void push ( T element ) { ListNode temp = new ListNode (); temp.element = element; temp.next = head; head = temp; } M. Esponda-Argüero

58

Implementierung der push-Operation head

O1

O2

O3

temp public void push ( T element ) { ListNode temp = new ListNode (); temp.element = element; temp.next = head; head = temp; } M. Esponda-Argüero

59

Implementierung der push-Operation head

e

O1

O2

O3

temp public void push ( T element ) { ListNode temp = new ListNode (); temp.element = element; temp.next = head; head = temp; } M. Esponda-Argüero

60

Implementierung der push-Operation head

e

O1

O2

O3

temp public void push ( T element ) { ListNode temp = new ListNode (); temp.element = element; temp.next = head; head = temp; } M. Esponda-Argüero

61

Implementierung der push-Operation head

e

O1

O2

O3

temp public void push ( T element ) { ListNode temp = new ListNode (); temp.element = element; temp.next = head; head = temp; } M. Esponda-Argüero

62

Implementierung der push-Operation head

e

O1

O2

O3

Mit Hilfe eines ListNode-Konstruktors, der als erster Parameter das zu speichernde Objekt element bekommt, und als zweiter Parameter eine Referenz auf ein ListNode-Objekt bekommt, können wir sehr einfach unsere push-Operation wie folgt implementieren. public void push ( T element ) { head = new ListNode ( element, head ); } M. Esponda-Argüero

63

Implementierung der pop-Operation head null

Wenn innerhalb einer pop-Operation festgestellt wird, dass der Stapel leer ist, wird ein EmptyStackExceptionObjekt geworfen und die pop-Operation unterbrochen.

public T pop() throws EmptyStackException { if ( empty() ) throw new EmptyStackException(); T element = head.element; head = head.next; return element; } M. Esponda-Argüero

64

Implementierung der pop-Operation

head

element

Wenn der Stapel nicht leer ist

O1

e

O2

O3

public T pop() throws EmptyStackException { if ( empty() ) throw new EmptyStackException(); T element = head.element; head = head.next; return element; } M. Esponda-Argüero

65

Implementierung der pop-Operation

head

element

Wenn der Stapel nicht leer ist

O1

e

e

O2

O3

public T pop() throws EmptyStackException { if ( empty() )

Die Objekt-Referenz, die sich in dem ersten Knoten befindet, wird in die lokale Variable element gespeichert.

throw new EmptyStackException(); T element = head.element; head = head.next; return element; } M. Esponda-Argüero

66

Implementierung der pop-Operation

head

element

e

O1

e

O2

O3

public T pop() throws EmptyStackException { if ( empty() ) throw new EmptyStackException(); T element = head.element; head = head.next; return element; } M. Esponda-Argüero

67

Implementierung der pop-Operation

head e

ga

element

e

return

g ba

O1

r

O2

O3

public T pop() throws EmptyStackException { if ( empty() ) throw new EmptyStackException(); T element = head.element; head = head.next; return element; } M. Esponda-Argüero

68

Implementierung der pop-Operation

head e

ga

r

g ba

O1

O2

O3

Das entfernte ListNode-Objekt bleibt ohne eine einzige Referenz, das auf es zeigt, und verwandelt sich in Datenspeichermüll, der später von dem Java-“garbage collector“ beseitigt wird.

M. Esponda-Argüero

69

Einfache Implementierung einer Warteschlange dequeue

FIFO - Datenstruktur "First In - First Out"

head

O1

Wenn wir eine Warteschlange mit Hilfe einer verketteten Liste implementieren, brauchen wir nicht mehr zu überprüfen, ob die Warteschlange voll ist.

O2

Operationen der Warteschlange enqueue tail

dequeue

O3

empty enqueue

head M. Esponda-Argüero

70

Warteschlange-Schnittstelle In dieser Implementierung werden wir eine EmptyQueueException erzeugen bei dem Versuch, ein Element zu entfernen oder zu lesen (dequeue-Operation und head-Operationen), wenn die Liste leer ist. public interface Queue { public void enqueue( T newElement ) ; public T dequeue() throws EmptyQueueException; public T head() throws EmptyQueueException; public boolean empty(); }

M. Esponda-Argüero

71

Warteschlange-Implementierung public class ListQueue implements Queue { ListNode head; ListNode tail; public ListQueue() { this.head = null; this.tail = null; ... } M. Esponda-Argüero

72

Implementierung der empty-Operation tail

head

null Die Warteschlange ist leer, wenn das head-Element auf die Konstante null zeigt public boolean empty () { return head == null; }

M. Esponda-Argüero

73

enqueue-Operation

head

tail null

Wenn die Liste leer ist

public void enqueue ( T newElement ) { if ( empty() ) head = tail = new ListNode ( newElement ); else tail = tail.next = new ListNode( newElement ); }

M. Esponda-Argüero

74

enqueue-Operation

head

Das zu speichernde Element muss zuerst

tail

in ein ListNode-Objekt verpackt werden, und head und tail bekommen eine Referenz aus den neuen Knoten

O1

zugewiesen. public void enqueue ( T newElement ) { if ( empty() ) head = tail = new ListNode ( newElement ); else tail = tail.next = new ListNode( newElement ); }

M. Esponda-Argüero

75

enqueue-Operation

head

tail

O0

O1

O2

O3

public void enqueue ( T newElement ) { if ( empty() ) Wenn die Liste nicht leer ist

head = tail = new ListNode ( newElement ); else tail = tail.next = new ListNode( newElement ); }

M. Esponda-Argüero

76

enqueue-Operation

head

tail

O0

Zuerst wird das zu speichernde neue Objekt in einen neu erzeugten Listenknoten (ListNode) verpackt.

O1

O2

O3

newE

public void enqueue ( T newElement ) { if ( empty() ) head = tail = new ListNode ( newElement ); else tail = tail.next = new ListNode( newElement ); }

M. Esponda-Argüero

77

enqueue-Operation

head

tail

O0

Die Referenz, die von dem ListNodeKonstruktor erzeugt wird, bekommt tail.next zugewiesen.

O1

O2

O3

newE

public void enqueue ( T newElement ) { if ( empty() ) head = tail = new ListNode ( newElement ); else tail = tail.next = new ListNode( newElement ); }

M. Esponda-Argüero

78

enqueue-Operation head

tail

O0

O1

O2

O3

newE

public void enqueue ( T newElement ) { if ( empty() )

Zum Schluss bekommt tail auch die gleiche Referenz wie tail.next.

head = tail = new ListNode ( newElement ); else tail = tail.next = new ListNode( newElement ); }

M. Esponda-Argüero

79

dequeue-Operationen

head

e Wenn die Liste nicht leer ist

tail

O1

O2

O3

public T dequeue() throws EmptyQueueException { if (empty () ) throw new EmptyQueueException(); T element = head.element; head = head.next; return element; } M. Esponda-Argüero

80

dequeue-Operationen

head

tail

element

e Die Referenz des Elements, das am Anfang der Warteschlange gespeichert ist, wird der Variablen element zugewiesen.

e

O1

O2

O3

public T dequeue() throws EmptyQueueException { if (empty () ) throw new EmptyQueueException(); T element = head.element; head = head.next; return element; } M. Esponda-Argüero

81

dequeue-Operationen

head

tail

element

e

e

O1

O2

O3

public T dequeue() throws EmptyQueueException { if (empty () ) throw new EmptyQueueException(); T element = head.element; head = head.next; return element; } M. Esponda-Argüero

82

dequeue-Operationen

head

tail

element

e

return

e

O1

O2

O3

public T dequeue() throws EmptyQueueException { if (empty () ) throw new EmptyQueueException(); Die Referenz des entfernten Objekts wird als Ergebnis zurückgegeben.

T element = head.element; head = head.next; return element; } M. Esponda-Argüero

83

dequeue-Operationen

head

e

tail

O1

O2

O3

Weil keine Referenz auf das entfernte ListNode-Objekt zeigt, bleibt es Speicherdatenmüll, bis es vom Java-“garbage collector“ gelöscht wird.

M. Esponda-Argüero

84

Allgemeine dynamische Datenmenge Wenn wir allgemeine dynamische Datenmengen mit Hilfe von Listen implementieren möchten, müssen wir an einer beliebigen Stelle der Liste Elemente einfügen und löschen können. Dafür brauchen wir ein weiteres Referenz-Objekt (current), das sich durch die Liste bewegt. Wir werden eine Einfüge-Operation definieren, die ein Element nach dem current Zeiger einfügt und eine Lösch-Operation, die ein Element nach dem current-Zeiger löscht.

insert head

current

delete

O1

O2

O4

O3

M. Esponda-Argüero

85

Einfügen an einer beliebigen Stelle der Liste

current ....

O1

O4

....

O3

temp ... ListNode temp = new ListNode(); temp.element = newE; temp.next = current.next; current.next = temp; ... M. Esponda-Argüero

86

Einfügen an eine beliebige Stelle der Liste

current ....

O1

O4

temp

....

O3

newE

... ListNode temp = new ListNode(); temp.element = newE; temp.next = current.next; current.next = temp; ... M. Esponda-Argüero

87

Einfügen an eine beliebige Stelle der Liste

current ....

O1

O4

temp

....

O3

newE

... ListNode temp = new ListNode(); temp.element = newE; temp.next = current.next; current.next = temp; ... M. Esponda-Argüero

88

Einfügen an eine beliebige Stelle der Liste

current ....

O1

O4

temp

....

O3

newE

... ListNode temp = new ListNode(); temp.element = newE; temp.next = current.next; current.next = temp; ... M. Esponda-Argüero

89

Einfügen an eine beliebige Stelle der Liste

current ....

O1

O4

temp

O3

....

newE

… current.next = new ListNode( newE, current.next ); … M. Esponda-Argüero

90

Löschen an einer beliebigen Stelle der Liste

current

....

next O1

next O4

next O3

....

… current.next = current.next.next; …

M. Esponda-Argüero

91

Löschen an einer beliebigen Stelle der Liste

current

....

garbage

next O1

next O4

next O3

....

… current.next = current.next.next; … Das entfernte ListNode-Objekt bleibt ohne eine einzige Referenz, die auf es zeigt, und verwandelt sich in Datenspeichermüll, der später von dem Java-“garbage collector“ beseitigt wird. M. Esponda-Argüero

92

"Header"-Knoten ("Dummy"-Knoten) Die Einfüge- und Löschoperationen, wie wir bis jetzt diskutiert haben, gehen davon aus, dass immer ein Vorgänger-Element vorhanden ist. Das macht unsere Implementierung einfacher, weil die speziellen Fälle - Löschen des ersten Elements der Liste- Einfügen, wenn die Liste leer istnicht berücksichtigt werden müssen. Um unsere Implementierung übersichtlich und einfach zu halten, benutzen wir einen Dummy-Knoten (sentinel), der selbst keine Elemente speichert und nur benutzt wird, um diese speziellen Fälle zu vermeiden, weil auf diese Weise jeder Knoten der Liste immer einen Vorgänger haben wird. M. Esponda-Argüero

93

"Dummy"-Knoten

current head Dummy

O2

Unsere Liste ist jetzt leer, wenn sich nur der "Dummy"-Knoten in ihr befindet.

public boolean empty () {

O4

O3

head Dummy

return head.next == null; } M. Esponda-Argüero

94

Einschränkungen bei einfach verketteten Listen Probleme: • Der current-Zeiger kann nur nach vorne bewegt werden. • Um den Vorgänger desjenigen Knotens zu finden, auf den current zeigt, müssen wir die ganze Liste von head an durchlaufen. • Das ist im allgemeinen sehr ineffizient.

current

current

current

head Dummy

O2

O4

O3

M. Esponda-Argüero

95

Doppelt verkettete Listen head

Dummy

tail

O1

class ListNode { T element; ListNode next; ListNode prev; ...

O2

Dummy

Bei doppelt verketteten Listen kann sich current problemlos in beide Richtungen bewegen. Die Einfüge- und LöschOperation ändert sich, weil wir jetzt immer eine doppelte Verkettung erstellen müssen.

} M. Esponda-Argüero

96

Doppelt verkettete leere Liste

head

tail

Dummy

Dummy

Bei doppelt verketteten Listen ist es sinnvoll, einen Zeiger auf das letzte Element zu haben.

public boolean empty { return head.next == tail; }

M. Esponda-Argüero

97

Einfügen

current

prev

O0 next

prev

O1 next

prev

O3 next

... ListNode temp = new ListNode( x ); temp.prev = current; temp.next = current.next; temp.prev.next = temp; temp.next.prev = temp; current = temp; ...

M. Esponda-Argüero

98

Einfügen

current

prev

O0 next

prev

prev

O1 next

prev

temp

x

O3 next

next

... ListNode temp = new ListNode( x ); temp.prev = current; temp.next = current.next; temp.prev.next = temp; temp.next.prev = temp; current = temp; ...

M. Esponda-Argüero

99

Einfügen

current

prev

O0 next

prev

prev

O1 next

prev

temp

x

O3 next

next

... ListNode temp = new ListNode( x ); temp.prev = current; temp.next = current.next; temp.prev.next = temp; temp.next.prev = temp; current = temp; ...

M. Esponda-Argüero

100

Einfügen

current

prev

O0 next

prev

prev

O1 next

prev

temp

x

O3 next

next

... ListNode temp = new ListNode( x ); temp.prev = current; temp.next = current.next; temp.prev.next = temp; temp.next.prev = temp; current = temp; ...

M. Esponda-Argüero

101

Einfügen

current

prev

O0 next

prev

prev

O1 next

prev

temp

x

O3 next

next

... ListNode temp = new ListNode( x ); temp.prev = current; temp.next = current.next; temp.prev.next = temp; temp.next.prev = temp; current = temp; ...

M. Esponda-Argüero

102

Einfügen

current

prev

O0 next

prev

prev

O1 next

prev

temp

x

O3 next

next

... ListNode temp = new ListNode( x ); temp.prev = current; temp.next = current.next; temp.prev.next = temp; temp.next.prev = temp; current = temp; ...

M. Esponda-Argüero

103

Einfügen

current

prev

O0 next

prev

prev

O1 next

prev

temp

x

O3 next

next

... ListNode temp = new ListNode( x ); temp.prev = current; temp.next = current.next; temp.prev.next = temp; temp.next.prev = temp; current = temp; ...

M. Esponda-Argüero

104

Löschen

current

prev

O1 next

prev

x

next

prev

O3 next

Mit doppelt verketteten Listen können wir genau das Objekt löschen, auf das current zeigt.

. . .

current.prev.next = current.next; current.next.prev = current.prev; current = head; . . .

M. Esponda-Argüero

105

Löschen

current

prev

O1 next

prev

x

next

prev

O3 next

. . .

current.prev.next = current.next; current.next.prev = current.prev; current = head; . . .

M. Esponda-Argüero

106

Löschen

current

prev

O1 next

prev

x

next

prev

O3 next

. . .

current.prev.next = current.next; current.next.prev = current.prev; current = head; . . .

M. Esponda-Argüero

107

Löschen

prev

O1 next

prev

x

next

prev

O3 next

. . .

current.prev.next = current.next; current.next.prev = current.prev; current = head; . . .

M. Esponda-Argüero

108

Löschen garbage

prev

O1 next

prev

x

next

prev

O3 next

. . .

current.prev.next = current.next; current.next.prev = current.prev; current = head; . . .

M. Esponda-Argüero

109

Löschen garbage

prev

O1 next

prev

x

next

prev

O3 next

. . .

current.prev.next = current.next; current.next.prev = current.prev; current = head; . . .

M. Esponda-Argüero

110