Verkettete Datenstrukturen: Listen

Verkettete Datenstrukturen: Listen

Verkettete Datenstrukturen: Listen 2 Listen Formal: Liste = endliche Folge von Elementen [a1 , a2 , . . . , an ]. Spezialfall: leere Liste [ ]. L¨ ...

91KB Sizes 0 Downloads 6 Views

Verkettete Datenstrukturen: Listen

2

Listen Formal: Liste = endliche Folge von Elementen [a1 , a2 , . . . , an ]. Spezialfall: leere Liste [ ]. L¨ ange einer Liste = Anzahl der Elemente (bei leerer Liste: 0). Anders als bei Mengen: Listen sind geordnet (es kommt auf die Reihenfolge an), Wiederholungen von Elementen sind erlaubt. (Mengen k¨ onnen durch Listen implementiert werden). 3

Listen: wie repr¨ asentieren? Bisher: Array (oder in Java: Vector) Zugriff auf Elemente erfolgt u ¨ber Index/Position. Nachbarschaft zwischen Elementen ist implizit durch ihre Position definiert. Zugriff auf Element u ¨ber Position: Zeitbedarf O(1). Zugriff auf n¨ achstes“ Element: Zeitbedarf O(1). ” Anh¨ angen eines neuen Elements am Schluß: Zeitbedarf O(1) (solange Platz vorhanden). Einfu ¨gen eines neuen Elements am Anfang oder zwischen zwei Elementen: Zeitbedarf O(n) (nachfolgende Elemente mu ¨ssen umkopiert werden). 4

Listen: wie repr¨ asentieren? Andere M¨ oglichkeit der Repr¨ asentation: verkettete Liste (siehe Ende der letzten Vorlesungsstunde) Zugriff auf Elemente erfolgt sequentiell. Jedes Element besitzt eine Referenz auf seinen Nachfolger ( weiß“, was sein Nachfolger ist) ” ; einfach verkettete Liste oder sogar: Jedes Element besitzt Referenzen auf seinen Nachfolger und seinen Vorg¨ anger ( weiß“, was sein Nachfolger und sein Vorg¨ anger ist) ” ; doppelt verkettete Liste) 5

Listen: wie repr¨ asentieren? Verkettete Liste Zugriff auf n¨ achstes“ Element: Zeitbedarf O(1). ” Zugriff auf Element u ¨ber Position: Zeitbedarf O(n). (drittes Element = Nachfolger des Nachfolgers des ersten Elements) Einfu ¨gen neuer Elemente: Zeitbedarf O(1).

6

Einfach verkettete Listen Grundlegender Baustein, aus dem eine verkettete Liste aufgebaut ist: public class Element { Element nach; // Referenz auf Nachfolger int wert; // oder z.B.: Object wert } Elementsicht: nach ist eine Referenz auf das n¨ achste Element der Liste. Listensicht: nach ist eine Referenz auf den Rest der Liste. 7

Einfach verkettete Listen Frage: was passiert am Listenende? ¨ Ubliche Lo ¨sung: Beim letzten Element der Liste hat nach den Wert null (= kein Objekt“). ” Das heißt: null repr¨ asentiert die leere Liste.

8

Einfach verkettete Listen Problem: Listenoperationen z.B.: vorne (oder hinten) an eine Liste ein Element anh¨ angen In einer objektorientierten Sprache sollte der Aufruf so aussehen: liste.haengeAn(34) Aber: null ist kein Objekt. (Folglich kann man null keine Nachrichten schicken.)

9

Einfach verkettete Listen Ausweg: ein weiterer Baustein: Listenkopf // "Liste" und "Element" sind im gleichen Package // (da wir in "Liste" auf die Variablen von "Element" // zugreifen werden). public class Liste { Element erstes; // entweder null (falls Liste leer) // oder Referenz auf erstes Element (sonst) // Methoden ... } 10

Einfach verkettete Listen: Methoden public boolean istLeer() { return erstes == null; }

11

Einfach verkettete Listen: Methoden public int ersteZahl() { if (istLeer()) { // hier sollte eigentlich eine Exception // ausgel¨ ost werden (aber Exceptions wurden // noch nicht besprochen) return 0; } else { return erstes.wert; } }

12

Einfach verkettete Listen: Methoden public int laenge() { int i = 0; Element e = erstes; while (e != null) { e = e.nach; i++; } return i; }

13

Einfach verkettete Listen: Methoden public int anzahlVorkommen(int j) { int i = 0; Element e = erstes; while (e != null) { if (e.wert == j) { i++; } e = e.nach; } return i; } 14

Einfach verkettete Listen: Methoden public String toString() { Element e = erstes; String s = "[ "; while (e != null) { s += e.wert + " "; e = e.nach; } s += "]"; return s; } erm¨ oglicht folgenden Aufruf: Liste liste = ...; System.out.println("liste hat den Wert " + liste); 15

Einfach verkettete Listen: Methoden public void haengeVorneAn(int j) { Element neu = new Element(); neu.nach = erstes; neu.wert = j; erstes = neu; }

16

Einfach verkettete Listen: Methoden public void haengeHintenAn(int j) { Element neu = new Element(); neu.wert = j; neu.nach = null; // eigentlich unn¨ otig if (erstes == null) { erstes = neu; } else { Element e = erstes; while (e.nach != null) { // d.h.: solange e nicht auf das letzte Element zeigt e = e.nach; } e.nach = neu; } } 17

Einfach verkettete Listen: Methoden Die Schleife in haengeHintenAn() sieht anders aus als in laenge(). Warum? Wir mu ¨ssen hier die nach-Variable des letzten Listenelements ver¨ andern. Die Schleife while (e != null) {...} aus der Methode laenge() wird erst verlassen, wenn e das letzte Listenelement u ¨berschritten hat und nun den Wert null hat. In diesem Moment haben wir aber keinen Zugriff mehr auf das letzte Listenelement. Stattdessen mu ¨ssen wir in der Schleife untersuchen, ob e gerade auf das letzte Listenelement zeigt. 18

Einfach verkettete Listen: Methoden public void loescheVorn() { if (erstes == null) { // hier sollte eigentlich eine // Exception ausgel¨ ost werden } else { erstes = erstes.nach; } }

19

Einfach verkettete Listen: Methoden public void loescheHinten() { if (erstes == null) { // hier sollte eigentlich eine Exception ausgel¨ ost werden } else if (erstes.nach == null) { erstes = null; } else { Element e = erstes; while (e.nach.nach != null) { // d.h.: solange e nicht auf das VORletzte Element zeigt e = e.nach; } e.nach = null; } } 20

Einfach verkettete Listen: Methoden Diese Schleife ist noch komplizierter als in haengeHintenAn(), da wir hier die nach-Variable des vorletzten Listenelementes ver¨ andern mu ¨ssen. Aus diesem Grund mu ¨ssen wir auch den Fall einer einelementigen Liste separat behandeln; in dieser gibt es n¨ amlich kein vorletztes Element.

21

Einfach verkettete Listen: Methoden public void loescheEinmal(int j) { if (erstes == null) { // nichts zu tun } else if (erstes.wert == j) { erstes = erstes.nach; } else { Element e = erstes; while (e.nach != null && e.nach.wert != j) { e = e.nach; } if (e.nach != null) { e.nach = e.nach.nach; } } } 22

Einfach verkettete Listen: Methoden public Liste teillisteAb(int j) { Liste l = new Liste(); Element e = erstes; while (e != null && e.wert != j) { e = e.nach; } l.erstes = e; return l; }

23

Einfach verkettete Listen: Methoden Vorsicht: die Originalliste und die zuru ¨ckgegebene Teilliste verwenden dieselben Elemente ( sharing“). ” Wenn aus einer der Listen etwas gel¨ oscht wird, fu ¨hrt das zu sehr merkwu ¨rdigen Effekten. (Insbesondere macht es einen Unterschied, ob das erste gesharte“ ” Element gel¨ oscht wird, oder irgend ein anderes Element. Warum?) Ein mo ¨glicher Ausweg: explizites Kopieren der Teilliste.

24

Einfach verkettete Listen: Alternativen Geht das alles auch einfacher? Kann man sich zum Beispiel die Sonderf¨ alle in haengeHintenAn(), loescheHinten() und loescheEinmal() sparen?

25

Einfach verkettete Listen: Alternativen Idee: Wenn man die Variable in der Klasse Liste nicht erstes nennt, sondern nach, dann kann man die Klasse Element als Erweiterung der Klasse Liste auffassen: public class Liste { Element nach; ... } public class Element extends Liste { int wert; // oder z.B.: Object wert } 26

Einfach verkettete Listen: Alternativen Die Laufvariable e in haengeHintenAn() kann dann als Liste anstatt als Element deklariert werden. Vorteil: e kann nun u ¨ber den Listenkopf und u ¨ber die Elemente laufen: public void haengeHintenAn(int j) { Element neu = new Element(); neu.wert = j; neu.nach = null; // eigentlich unn¨ otig Liste e = this; while (e.nach != null) { // d.h.: solange e nicht auf das letzte Element zeigt e = e.nach; } e.nach = neu; } 27

Einfach verkettete Listen: Alternativen K¨ onnte man die Klassenhierarchie auch anders w¨ ahlen? Ko ahlen? ¨nnte man die Klassenhierarchie auch sinnvoller w¨

28

Doppelt verkettete Listen Geht das alles noch einfacher? Teilweise ja, wenn man doppeltverkettete Listen verwendet: public class Element { Element vor; // Referenz auf Vorg¨ anger Element nach; // Referenz auf Nachfolger int wert; }

29

Doppelt verkettete Listen Zus¨ atzlicher Trick: Die vor-Variable des ersten Elements und die nach-Variable des letzten Elements haben nicht den Wert null, sondern verweisen auf ein gemeinsames Dummy-Element. Die Liste wird sozusagen zu einem Kreis geschlossen. Der Listenkopf enth¨ alt eine Referenz auf das Dummy-Element. Die Abfrage nach == null muß dann natu ¨rlich ersetzt werden durch die Abfrage, ob nach gleich dem Dummy-Element ist.

30

Doppelt verkettete Listen Nachteile: Elemente brauchen (etwas) mehr Speicherplatz. Teillisten k¨ onnen nicht mehr mit Sharing erzeugt werden. (Die Elemente m¨ ussen also jetzt kopiert werden.) Vorteile: Man kann jetzt auch das Element lo ¨schen, auf das man gerade zeigt. Man kann in konstanter Zeit auf das letzte Element einer Liste zugreifen (statt in O(n), wie bei einfach verketteten Listen). 31