Durch zusätzliche Anforderungen bzgl. einer Verteilung der Blätter und Höhen in Unterbäumen kann man ein Degenerieren verhindern; Aspekte: 42 - Vorteil: geringer Aufwand für Grundoperationen kann zugesichert werden. 17 52 - Nachteil: Strukturinvariante muss erhalten werden. 10 - Kosten der Strukturerhaltung? 22 45 57 Wegen der Balancierungseigenschaft mussten alle Knoten vertauscht werden. Beispiel: (Strukturerhaltung) Einfügen von 10 unter Erhaltung von FastVollständigkeit Adelson-Velskij und Landis schlugen folgende Balancierungseigenschaft vor: 45 Begriffsklärung: (AVL-Baum) 22 17 31.01.2007 57 42 Ein binärer Suchbaum heißt AVL-ausgeglichen, höhenbalanciert und ein AVL-Baum, wenn für jeden Knoten K gilt: Die Höhe des linken Unterbaums von K unterscheidet sich von der Höhe des rechten Unterbaums höchstens um 1. 52 © A. Poetzsch-Heffter, Universität Kaiserslautern 611 © A. Poetzsch-Heffter, Universität Kaiserslautern 31.01.2007 612 Vorgehen: Rotationen auf Suchbäumen: • Gestaltsanalyse von AVL-Bäumen Eine Rotation ist ein lokale Reorganisation eines Suchbaums, bei der die Suchbaumeigenschaft erhalten bleibt, die Höhen der Unterbäume aber ggf. verändert werden. • Rotationen auf Suchbäumen • Datenstruktur für AVL-Bäume • Heraussuchen Rotation nach rechts (nach links entsprechend): • Balancieren nach Einfügen • Diskussion Y X Gestaltsanalyse: X Y Frage: Hat jeder AVL-Baum logarithmische Höhe? C (Dies ist die Voraussetzung, alle Grundoperationen mit logarithmischen Aufwand realisieren zu können.) A Lemma: B A B C Es gilt: Für die Höhe eines AVL-Baums mit N Knoten gilt: - alle Schlüssel aus A sind echt kleiner als X h ≤ 2 * log (N +1) + 1 - alle Schlüssel aus B sind echt größer als X und echt kleiner als Y Beweis: - alle Schlüssel aus C sind echt größer als Y siehe Vorlesung - X<Y 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 613 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 614 Doppelrotation links-rechts (rechts-links entsprechend): Z Ein Dictionary wird repräsentiert durch ein Objekt, das eine Referenz auf einen Binärbaum enthält: X Y Y D • Der leere Binärbaum wird durch die null-Referenz repräsentiert (leeres Dictionary). Z X A A B B C D C Auch bei der Doppelrotation bleibt die SuchbaumEigenschaft erhalten. • Jeder Knoten eines nichtleeren Binärbaums wird durch ein Objekt vom Typ AVLNode repräsentiert mit Instanzvariablen für: - den Schlüssel - die Daten - die Referenz auf das linke Kind - die Referenz auf das rechte Kind - die Höhendifferenz bf • Die Baumknoten sind gekapselt und können von außen nur indirekt über die Grundoperationen manipuliert werden. Datenstruktur für AVL Bäume: Zur Realisierung von AVL-Bäumen gehen wir von der Implementierung der natürlichen Suchbäume aus: Datenstruktur-Invarianten: - Die Baumknoten bekommen ein zusätzliches Attribut bf (balance factor), in dem die Höhendifferenz von linkem und rechtem Unterbaum gespeichert wird. - Die Binärbäume sind Suchbäume. - Schlüssel kommen nicht doppelt vor. - Die Höhendifferenz ist korrekt belegt. - Die Operationen zum Einfügen und Löschen müssen angepasst werden (ggf. wird dazu auch die Datenstruktur erweitert). 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 615 class AVLTreeDictionary implements Dictionary { private AVLNode root; 616 - Ist der Wurzelschlüssel gleich dem gesuchten Schlüssel, terminiert das Verfahren. - Ist der Wurzelschlüssel größer als der gesuchte Schlüssel, suche im linken Unterbaum weiter. - Ist der Wurzelschlüssel kleiner als der gesuchte Schlüssel, suche im rechten Unterbaum weiter. private AVLNode( int k, Object d ) { key = k; data = d; } } public AVLTreeDictionary() { root = null; // leeres Dictionary } public Object get( int key ) {...} private AVLNode searchNode( AVLNode current, int key) {...} public void put(int key, Object value){...} private AVLNode insertNode( AVLNode current, int key, Object v ){...} private AVLNode rotate(AVLNode current){...} public void remove( int key ) { throw RuntimeException("not available"); } } © A. Poetzsch-Heffter, Universität Kaiserslautern © A. Poetzsch-Heffter, Universität Kaiserslautern Heraussuchen: private static class AVLNode { private int key; private Object data; private AVLNode left, right; private int bf; 31.01.2007 31.01.2007 617 public Object get( int key ) { AVLNode tn = searchNode(root,key); if( tn == null ) { return null; } else { return tn.data; } } private AVLNode searchNode( AVLNode current, int key) { if( current!=null && key != current.key ) { if( current.key > key ) { return searchNode( current.left, key ); } else { // current.key < key return searchNode( current.right, key ); } } return current; } 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 618 Einfügen: Beispiel: Entwicklung des Algorithmus in 4 Schritten: Einfügen von 33: 45 1. Einfügen ohne Aktualisieren von bf und Rotation 2. Aktualisieren von bf, aber ohne Rotation 22 57 3. Aktualisieren von bf mit Aufruf der Rotation 4. Rotation mit Aktualisieren von bf an den rotierten Knoten 17 42 33 1. Einfügen ohne Aktualisieren von bf und Rotation : 52 65 49 - Neue Knoten werden immer als Blätter eingefügt. - Die Position des Blattes wird durch den Schlüssel des neuen Knotens festgelegt. class AVLTreeDictionary implements Dictionary { ... private AVLNode root; ... public void put( int key, Object value ){ if( root == null ) { root = new AVLNode(key, value); } else { AVLNode res = insertNode(root,key,value); if( res!=null ) root = res; } } ... } - Beim Aufbau eines Baumes ergibt der erste Knoten die Wurzel. - Ein Knoten wird in den linken Unterbaum der Wurzel eingefügt, wenn sein Schlüssel kleiner ist als der Schlüssel der Wurzel; in den rechten, wenn er größer. Dieses Verfahren wird rekursiv fortgesetzt, bis die Einfügeposition bestimmt ist. 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 619 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 620 Rekursives Aufsuchen der Einfügestelle und Einfügen: 2. Aktualisieren von bf, aber ohne Rotation void insertNode( AVLNode current, int key, Object v ) { // pre: current != null // if( key < current.key ) { if ( current.left == null ) { current.left = new AVLNode(key,v); } else { insertNode( current.left, key, v); } } else if( key > current.key ) { if ( current.right == null ) { current.right = new AVLNode(key,v); } else { insertNode( current.right, key, v); } } else { // key == current.key current.data = v; } } 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 621 Algorithmisches Vorgehen: - Einfügen wie oben. - Aktualisieren von bf soweit nötig: Der Höhendifferenz bf kann sich nur bei Knoten ändern, die auf dem Pfad von der Wurzel zum eingefügten Knoten liegen. Nur an diesen Knoten kann die AVL-Eigenschaft verletzt werden. - Bestimmen des kritischen Knotens KK; das ist der nächste Elternknoten zum eingefügten Knoten mit bf=2. - Rotiere bei KK, Rotationstyp ergibt sich aus Pfad von eingefügtem Knoten zu KK. Wir werden zeigen, dass durch die Rotation der Unterbaum mit Wurzel KK die gleiche Höhe erhält, die er vor dem Einfügen hatte. Die Balancierungsfaktoren an Knoten oberhalb von KK brauchen also nicht aktualisiert zu werden. 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 622 Bemerkung: Einfügen mit Aktualisieren von bf: private boolean insertNode ( AVLNode current, int key, Object v ){ /* pre: current != null ens: result==true, wenn h(current) > old(h(current)) result==false, sonst */ if( key < current.key ) { if( current.left == null ) { current.left = new AVLNode(key,v); current.bf++; // |current.bf| < 2 return (current.bf>0); } else { if( insertNode(current.left,key,v) ){ current.bf++; return (current.bf>0); } else { return false; } } } else if( key > current.key ) { ... // symmetrisch auf rechter Seite } else { // key == current.key current.data = v; return false; } } 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 623 private AVLNode insertNode ( AVLNode current, int key, Object v ) { if( key < current.key ) { if( current.left == null ) { current.left = new AVLNode(key,v); current.bf++; // |current.bf| < 2 return (current.bf>0) ? null : current; } else { AVLNode res = insertNode(current.left,key,v); if( res == null ) { current.bf++; if( current.bf < 2 ) { return (current.bf>0)?null:current; } else { return rotate( current ); } } else { current.left = res; return current; } } } else if( key > current.key ) { ... // symmetrisch auf rechter Seite } else { // key == current.key current.data = v; return current; } } 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 625 • Die obige Fassung veranschaulicht die Vorgehensweise, auf dem „Rückweg“ von einem rekursiven Abstieg Operationen auszuführen. • Die obige Fassung wird so nicht benötigt, da bf nur bis zu kritischen Knoten zu aktualisieren ist. Ist der kritische Knoten gefunden, wird rotiert und damit die Aktualisierungen oberhalb unnötig. 3. Einfügen mit Aktualisieren von bf und Rotation: Problem: Die Rotation macht es nötig, den Elternknoten des kritischen Knotens zu modifizieren. Idee: Statt true/false liefert die Einfüge-Operation: - null, wenn sich die Höhe geändert hat. - Die Referenz auf den möglicherweise rotierten Unterbaum, wenn sich die Höhe nicht geändert hat; deshalb ist ggf. root zu modifizieren: public void put( int key, Object val ) { if( root == null ) { root = new AVLNode(key, value); } else { AVLNode res = insertNode(root,key,val); if( res!=null ) { root = res; } } } 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 624 4. Rotation mit Aktualisieren von bf: Wir betrachten die Situation, dass bf im kritischen Knoten KK auf 2 gestiegen ist, also links eingefügt wurde. Die Unterbäume von KK bezeichnen wir mit li, li.li, li.re etc. Da h(li) = h(re)+2, kann der Wurzelknoten von li nicht der neu eingefügte Knoten KN sein. Es gibt vier unterschiedliche Fälle: - KN ist in li.li eingefügt (rechts-Rotation) - KN ist in li.re eingefügt; bf ist abhängig davon, ob KN neue Wurzel von li.re (links-rechts-Rotation) KN in li.re.li eingefügt (links-rechts-Rotation) KN in li.re.re eingefügt (links-rechts-Rotation) Fall: links-links h+2 vor Einfügen: Y h+1 X 0 1 h C h h A 31.01.2007 B © A. Poetzsch-Heffter, Universität Kaiserslautern 626 nach Einfügen: Beachte: h+3 Y h+2 X 2 Die Höhe nach der Rotation ist gleich der Höhe vor dem Einfügen. Damit wird die AVLEigenschaft der Baumteile oberhalb des kritischen Knotens nicht beeinflusst. h 1 C h+1 h A B Fall: links-rechts: vor Einfügen: nach Rotation: h+2 X h+1 1 0 h+1 Y 0 0 Y h+2 1 Z h+1 0 1 Z h 0 Y A D h h h B h X C 0 A h-1 h-1 B C (der Fall rechts-rechts geht analog) 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 627 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern nach Einfügen links-rechts-links: nach Einfügen links-rechts: 2 h+3 h+2 2 Z 2 Z h -1 Y 628 D 1 h -1 Y h+1 X 1 A 0 X h 0 h-1 B C nach Rotation: nach Rotation: h+2 1 X h+1 0 Y 0 X 0 0 0 Z Y 0 h+1 Z -1 0 h h A h-1 B C h D (der Fall rechts-links geht analog) (der Fall rechts-links-rechts geht analog) 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 629 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 630 nach Einfügen links-rechts-rechts: h+3 h+2 2 Z h -1 Y private AVLNode rotate( AVLNode current ) { // pre: current != null && |current.bf| == 2 // if( current.bf == 2 ) { AVLNode cleft = current.left; if( cleft.bf == 1 ) { // Variante LL current.left = cleft.right; current.bf = 0; cleft.right = current; cleft.bf = 0; return cleft; } else { // LR-Varianten AVLNode clright = cleft.right; current.left = clright.right; cleft.right = clright.left; clright.left = cleft; clright.right = current; if( clright.bf == 1 ) { // LR(a) current.bf = -1; cleft.bf = 0; } else if( clright.bf == -1 ) { // LR(b) current.bf = 0; cleft.bf = 1; } else { // degenerierter Fall current.bf = 0; cleft.bf = 0; } clright.bf = 0; return clright; } } else { // current.bf == -2 ) ... // symmmetrisch fuer rechts } D h h+1 X -1 A h-1 h B C nach Rotation: h+2 h+1 h Y 1 h-1 A B X 0 h+1 Z h 0 h C D (der Fall rechts-links-links geht analog) 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 631 Diskussion: 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 632 Begriffsklärung: (Hashfunktion, -tabelle) Beim Einfügen eines Knotens hat der rebalancierte Unterbaum stets die gleiche Höhe wie vor dem Einfügen: Seien - S die Menge der möglichen Schlüsselwerte (Schlüsselraum) und - Der restliche Baum wird nicht beeinflusst. - A die Menge von Adressen in einer Hashtabelle (im Folgenden ist A immer die Indexmenge 0 .. m-1 eines Feldes). - Höchstens eine Rotation wird benötigt. Beim Löschen können ungünstigsten Falls so viele Rotationen erforderlich sein, wie es Knoten auf dem Pfad von der Löschposition bis zur Wurzel gibt. A ordnet jedem Schlüssel Eine Hashfunktion h: S eine Adresse in der Hashtabelle zu. Als Hashtabelle (HT) der Größe m bezeichnen wir einen Speicherbereich, auf den über die Adressen aus A mit konstantem Aufwand (unabhängig von m) zugegriffen werden kann. Da der Aufwand für eine Rotation aber konstant ist, ergeben sich maximal O(log N) Operationen. C. Hashing/Streuspeicherung Anstatt durch schrittweises Vergleichen von Schlüsseln auf einen Datensatz zuzugreifen, versucht man bei Hash- oder Streuspeicherverfahren aus dem Schlüssel die Positionsinformation des Datensatzes (z.B. den Feldindex) zu berechnen. Enthält S weniger Elemente als A, kann h injektiv sein: Für alle s,t in S: s ≠ t => h(s) ≠ h(t) d.h. die Hashfunktion ordnet jedem Schlüssel eine eineindeutige Adresse zu. Dann ist perfektes Hashing möglich. Für viele praktisch relevante Szenarien erreicht man dadurch Datenzugriff mit konstantem Aufwand. 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern Andernfalls können Kollisionen auftreten: 633 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 634 Klassifikation von Hashverfahren: Hashverfahren unterscheiden sich Begriffsklärung: (Kollision, Synonym) • durch die Hashfunktion Zwei Schlüssel s,t kollidieren bezüglich einer Hashfunktion h, wenn h(s) = h(t). • durch die Kollisionsauflösung: - extern: Überläufer werden in Datenstrukturen außerhalb der Hashtabelle gespeichert. - offen: Überläufer werden an noch offenen Positionen der Hashtabelle gespeichert. Die Schlüssel s und t nennt man dann Synonyme. Die Menge der Synonyme bezüglich einer Adresse a aus A heißt die Kollisionsklasse von a. Ist schon ein Datensatz mit Schlüssel s in der Hashtabelle gespeichert, nennt man einen Datensatz mit einem Synonym von s einen Überläufer. • durch die Wahl der Größe von der Hashtabelle: - statisch: Die Größe wird bei der Erzeugung festgelegt und bleibt unverändert. - dynamisch: Die Größe kann angepasst werden. Wir betrachten im Folgenden eine Realisierung einer statischen Hashtabelle mit externer Kollisionsauflösung durch ein Dictionary mit binärer Suche. Anforderungen an Hashfunktionen: Eine Hashfunktion soll Hashfunktion: - sich einfach und effizient berechnen lassen (konstanter Aufwand bzgl. S) Entscheidend ist, dass die Hashfunktion die Schlüssel gut streut. Verbreitetes Verfahren: - Wähle eine Primzahl als Hashtabellen-Größe. - Wähle den ganzzahligen Divisionsrest als Hashwert: - zu einer möglichst gleichmäßigen Belegung der Hashtabelle führen - möglichst wenige Kollisionen verursachen private int hash( int key ) { return Math.abs(key) % hashtable.length; } 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 635 Schlecht wäre beispielsweise eine Wahl von m = 2i als Tabellengröße bei dem Divisionsrest-Verfahren, da bei Binärdarstellung der Schlüssel dann nur die letzten i Bits relevant sind. 636 - Die Hashtabelle enthält den Datensatz zu s, wenn hashtable[ h(s) ] == key datatable[ h(s) ] != null Die Daten liefert dann datatable[ h(s) ] . Datenstruktur: Wir realisieren eine Hashtabelle als Implementierung der Schnittstelle Dictionary: class HashDictionary implements Dictionary { int[] hashtable; Object[] datatable; private Dictionary[] overflowtable; public HashDictionary( int tabsize ) { /* tabsize sollte eine Primzahl sein */ hashtable = new int[tabsize]; datatable = new Object[tabsize]; overflowtable = new Dictionary[tabsize]; } private int hash( int key ) { ... } public Object get( int key ) { ... } public void put (int key,Object v ){ ... } public void remove( int key ) { ... } } © A. Poetzsch-Heffter, Universität Kaiserslautern © A. Poetzsch-Heffter, Universität Kaiserslautern Sei s ein Schlüssel, h(s) sein Hashwert. Der Datenstruktur HashDictionary liegen folgende Invarianten zugrunde: Bemerkung: 31.01.2007 31.01.2007 637 - Alle eingetragenen Elemente der Kollisionsklasse zu h(s) befinden sich in der Hashtabelle mit Index h(s) oder in overflow[h(s)]. Wegen Löschens ist es möglich, dass die Hashtabelle zu h(s) keinen Eintrag hat, sich trotzdem aber Einträge in der Überlauftabelle zu h(s) befinden! Löschen: public void remove( int key ) { int hix = hash(key); if( hashtable[hix] == key && datatable[hix] != null ) { datatable[hix] = null; } else if( overflowtable[hix] != null ) { overflowtable[hix].remove(key); } } 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 638 Suchen: Einfügen: public Object get( int key ) { int hix = hash(key); if( hashtable[hix] == key && datatable[hix] != null ) { return datatable[hix]; } else if( overflowtable[hix] == null ) { return null; } else { Object v = overflowtable[hix].get(key); if( datatable[hix] == null ) { datatable[hix] = v; overflowtable[hix].remove(key); } return v; } } 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern public void put ( int key, Object value ) { if( value != null ) { int hix = hash(key); if( datatable[hix] == null ) { hashtable[hix] = key; datatable[hix] = value; } else if( hashtable[hix] == key ) { datatable[hix] = value; } else { if( overflowtable[hix] == null ) { overflowtable[hix] = new BintreeDictionary(); } overflowtable[hix].put(key,value); } } } 639 © A. Poetzsch-Heffter, Universität Kaiserslautern 31.01.2007 640 Die Hashfunktion wird dabei benutzt, um komplexere Schlüssel in einfachere, meist ganzzahlige Schlüssel abzubilden. Diskussion: Die Komplexität der Operationen einer Hashtabelle hängt ab von: - der Hashfunktion und dem Füllungsgrad der Tabelle - dem Verfahren zur Kollisionsauflösung Bei guter Hashfunktion und kleinem Füllungsgrad kommt man im Mittel mit konstantem Aufwand aus. Da Injektivität meist nur annäherungsweise erreicht werden kann, braucht man Kollisionsauflösung. Meist verwendet man dazu offene Kollisionsauflösung. Beispiel: (Strings als Schlüssel) Bemerkung: In Bezeichnerumgebungen (vgl. ESSy 1, Folie 114) und Deklarationstabellen von Übersetzern sind die Schlüssel in natürlicherweise Zeichenreihen. Hashverfahren sind ein Beispiel dafür, dass man sich nicht immer für Algorithmen mit asymptotisch gutem Verhalten interessiert. Durch Hashing wird jedem Bezeichner eine natürliche Zahl als Schlüssel zugeordnet. Unter diesem Zahlschlüssel wird die Deklarationsinformation verwaltet. Anwendung von Hashverfahren: Bemerkung: Hashverfahren werden auch verwendet um Schlüsselräume zu vereinfachen. Wir betrachten hier Anwendungen mit Zeichenreichen als Schlüssel. Die praktische Bedeutung von Hashverfahren zur Schlüsselvereinfachung wird auch durch die Methode Zeichenreihen als Schlüssel: - Vorteile: bzgl. Anwendung der sich direkt ergebende Schlüsseltyp; nicht längenbeschränkt. /** * * * Returns a hash code value for the object. This method is supported for the benefit of hashtables such as those provided by java.util.Hashtable ... */ public native int hashCode(); - Nachteile: Schlüsselvergleiche sehr teuer; nicht zur Indizierung geeignet. 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern in der Java Klasse Object verdeutlicht. 641 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 642 Beobachtermuster: Da über die Beobachter nichts bekannt ist, realisiert man sie sinnvollerweise durch einen Schnittstellentyp: Als Beispiel einer Klasse, die Objekte nur über deren Schnittstelle anspricht, betrachten wir eine Anwendung des Beobachtermusters. Aktie interface Beobachter { void steigen( Aktie a ); void fallen( Aktie a ); } // Fortsetzung nächste Folie Beobachter * name: String kurswert: int * void steigen(Aktie a) Die Assoziation zwischen Aktien und Beobachtern implementieren wir durch eine Liste in der Klasse Aktie, die alle Beobachter der Klasse enthält: void fallen(Aktie a) Boersianer1 public class Aktie { private String name; private int kursWert; private ArrayList beobachterListe; Boersianer2 Aktie( String n, int anfangsWert ){ name = n; kursWert = anfangsWert; beobachterListe = new ArrayList(); } Bei Realisierung der Klasse Aktie ist nur bekannt, dass die Beobachter über das Steigen und Fallen des Aktienkurses informiert werden wollen. Wie Beobachter auf Änderungen reagieren, ist nicht bekannt. Die Klassen können also getrennt entwickelt werden. public String getName(){ return name; } public int getKursWert(){ return kursWert; } // Fortsetzung nächste Folie Bemerkung: Das Beispiel illustriert eine Anwendung des Entwurfmusters „Beobachter“ (engl. „observer“). 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 643 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 644 Zur Illustration von Beobachterimplementierungen betrachten wir einen Boersianer der Beim Setzen des Kurswertes werden auch die Beobachter benachrichtigt: - von der beobachteten Aktien kauft, wenn deren Kurs unter 300 Euro fällt und er noch keine besitzt, // Fortsetzung von voriger Folie - verkauft, wenn der Kurs über 400 Euro steigt. void setKursWert( int neuerWert ){ int alterWert = kursWert; kursWert = neuerWert>0 ? neuerWert : 1 ; public class Boersianer1 implements Beobachter{ private boolean besitzt = false; ListIterator it = beobachterListe.listIterator(); void fallen( Aktie a ) { if( a.getKursWert() < 300 && !besitzt ) { System.out.println("Kauf "+a.getName()); besitzt = true; } } void steigen( Aktie a ) { if( a.getKursWert() > 400 && besitzt ) { System.out.print("Verkauf "+a.getName()); System.out.println(); besitzt = false; } } if( kursWert > alterWert ) { while( it.hasNext() ){ Beobachter b = (Beobachter)it.next(); b.steigen( this ); } } else { while( it.hasNext() ){ Beobachter b = (Beobachter)it.next(); b.fallen( this ); } } } } public void anmeldenBeobachter( Beobachter b ) { beobachterListe.add( b ); } Anwendungsfragment: ... Aktie vw = new Aktie("VW", 354); Beobachter peter = new Boersianer1(); vw.anmeldeBeobachter( peter ); ... } 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 645 31.01.2007 © A. Poetzsch-Heffter, Universität Kaiserslautern 646