Datenstrukturen Daten: (lesbar / bearbeitbar) + Bedeutungskontext
Transcrição
Datenstrukturen Daten: (lesbar / bearbeitbar) + Bedeutungskontext
Datenstrukturen Daten: (lesbar / bearbeitbar) + Bedeutungskontext / Kodierung in Zeichenketten gemäß Syntax / Attribute Beziehung / Wertebereich / Speicherplatz. Datentyp: endliche Menge (Wertebereich des Types/Konstanten) + gewisse Anzahl Operationen Untypisierte Sprachen: Variable kann beliebigen Typ annehmen (LISP, Smalltalk, Scriptsprachen) -> Laufzeitfehler Typisierte Sprachen: Variablen müssen definiert werden mit Hilfe eines Datentyps (C,C++,Java) der Compiler kann Integrität prüfen weniger Laufzeitfehler / ADA gar keine LZF Atomare Typen: Vordefinierte Typen einer Sprache, Grundlage für Ableitung von weiteren Typen (Java int, float, boolen, char) Strukturierte Datentypen (Klassen) Zusammenfassung beliebiger Datentypen + Spezifikation von Methoden & Operationen kartesisches Produkt: DT = DT1 x DT2 x DT3. (DTx atomare oder strukturierte Datentypen) Abstrakter Datentyp: Definition von Datentyp ohne Implementation! + Operationspezifikation. Verwendung Geheimnisprinzip + Wiederverwendbarkeit. Definiert einen Datentyp nur mit Hilfe des Wertebereichs und der Menge der Operationen auf diesen Bereich. Jeder Operation ist definiert durch ihre Spezifikation, Input und Outputparameter und Vor und Nachbedingung.Spezifiziert eine Liste von Dienstleistungen (Operationen, Methoden, Funktionen), die der Datentyp dem Anwender zur Verfügung stellt. Die Spezifikation eines DT muss vollständig, präzise und eindeutig sein Achtung vor einer Überspezifikation mittels der Implementationsvorgaben. Geheimnisprinzip: Spezifikation sichtbar / Implementation verborgen Vorteile: Anwender kann verwenden ohne die Komplexität der Impl. Zu kennen / Implementator kann die Implementation verändert, solange er den Spez-Vertrag erfüllt. / Klare Verantwortlungsregelung zwischen den Aktoren – hilft bei Debugging. Wiederverwendbarkeit: Modul soll in verschiedenen Apps wieder verwendtbar sein, um ähnliche Probleme zu lösen. Reduktion der Entwicklungszeit / Kosten Datenstruktur: ist eine Instanz eines (abstrakten) Datentyps Beinhaltet die Repräsentation der Daten und die Implementation von Prozeduren für die Operationen. Logische / physikalische Form von Datenelement: Abstrakter Datentyp ist logische Form / Implementation ist die physikalische Form Algorihmus: beschreibt das Vorgehen oder Methode für lösen einer Aufgabe/Problems. Besteht aus einer Folge von einfachen Rechenschritten/Anweisungen welche zur Lösung der Aufgabenstellung führen. Die 7 Muss-Kriterien: 1) Reihe konkret ausführbarer Schritte bestehen 2) er muss in einem endlichen Text beschreibbar sein 3) nur endliche viele Schritt benötigen (Termination) 4) zur jeder Zeit nur endlich viel Speicherplatz benötigen 5) muss mit gleicher Eingabe das selbe Ergebnis liefern 6) die nach Ausführung eines Schrittes ist eindeutig definiert welcher der nächste Schritt ist /) der ausgabewert des Algorithmus muss richtig sein. Ausnahme: z.B. Lottozahlenalgorithmus… Algorithmen-Schema Iteration: Greedy: Rekursion: Divide and Conquer: verstehen wir ein Verfahrens-Muster oder allgemeine Methode um ein Problem lösen zu können. Ein Problem wird gelöst mittels … gelöst, falls der zugehörige Algorithmus eine Loop (while oder for Schleife) benutzt. Hilfreich bei Arrays (gierige Methode) für Optimierungsprobleme / Bewertunsgsfunktion bei mehreren Lösungen (Spiele Strategien) z.B. Suche nach em besten Weg) direkt oder indirekt selber aufruft. 1) Darf nicht unendlich sein, es müssen Abbruchkriterien existieren. Ein instruktionszweig muss existieren der keinen Aufruf der Prozedur beinhaltet. 2) muss sichergestellt werden, dass die Anzahl rekursiver Aufrufe vernünftig bleibt und kein Speicher-Notstand dadurch entsteht. Achtung auf Asymptotische Komplexität. 3) die Lösung sollte durch die Verwendung der Rekrusion klarer und kürzer werden! DAC zerlegt das Problem in kleiner Teilprobleme bis die Lösung der Teilprobleme einfach ist. Anschliessend werden die Teillösungen wieder zur Gesamtlösung vereint (Merge) 1) Hard Split Easy Join: Quicksort Dabei wird die gesamte Arbeit beim Teilen des Problems verrichtet und die Kombination ist trivial, das heisst, F wird so in Teilfolgen F1 und F2 partitioniert, dass zum Schluss die sortierten Teilfolgen einfach aneinandergereiht werden konnen: S = S1S2. 2) Easy Split Hard Join: Mergesort Dabei ist die Aufteilung in Teilfolgen F = F1F2 trivial und die Hauptarbeit liegt beim Zusammensetzen der sortierten Teilfolgen S1 und S2 zu S. Dieses Prinzip führt zum Mergesort-Algorithmus Rekursionselimination: Vermeidung der Rekursion durch Umstellung des Algorithmus. Einfach wenn die letzte Instruktion (Rekursiv aufgerufen wird. 1) Umdrehung der Berechnung 2) Zwischenspeicherung von Ergebnissen public long procRek(int n) { if(n<=3) return 2; else return 2*procRek(n-1) + procRek(n-2)/2 - procRek(n-3); } public long procRek( int n ) { long tmp[] = new long[n+1]; tmp[0] = tmp[1] = tmp[2] = tmp[3] = 2; for (int i=4; i<=n; i++) { tmp[i] = 2*tmp[i-1] + tmp[i-2]/2 - tmp[i-3]; } return tmp[n]; } Komplexitätsanalyse 3 Gruppen: Alle Funktionen -> Berechenbare Funktionen -> Durchführbare Algorithmen Ermittlung der erforderlichen Betriebsmittel (Zeit & Speicherbedarf) / Gibt Hinweis zur Einsatzfähigkeit eines Algorithmus / Wichtig für die Programmoptimierung (ein Problem -> mehrere Lösungswege -> bester Lösungsweg) Ermittlung der Effizenz der Algor. Komplexität in der Regel abhängig von der Grösse der Eingabedaten (Für Grössenordnung der Komplex. des Algor.-> zählen der relevanten Operationen (Anzahl Rechenschritte; Vergleiche Vertauschungen). Best-Case(1), Average-Case(2), Worst-Case Analyse(3) : 1) gibt an, wie lange der Algorithmus mindestens braucht( idealen Eingabe), trifft nur für wenige Fälle zu und wird selten angegeben / die best-case-Laufzeit in der für die schlechteren Fälle enthalten ist 2) gibt die erwartete Laufzeit bei einer gegebenen Verteilung der Eingaben an. Da allerdings die Verteilung der Eingaben bei Programmen nicht immer bekannt ist, ist die Berechnung nur unter einschränkenden Annahmen möglich. 3) gibt an, wie lange der Algorithmus maximal braucht. Speziell wichtig für Echtzeitsystemen / sonst nicht zwingend Asymptotische Komplexität: z.B. eines Polynoms entspricht dessem größten Term (Konstanten ignorieren). 1.) Log(n) kleinere Ordnung als O(n) 2) Ordnung eines Polynoms -> O(n^k) (nur höchste Potenz) 3) zwei Funktionen O(f + g) = max{O(f), O(f)} 4) O(f*g) = O(f)*(O(g) int proc4( int n ) { int res = 0, m = n*n; for( i = m; i > 1; i=i/2 ) res = do_something(res, i); return res; } m+m/2+m/4+···+ 4+2 = 2m−2 = O(n^2) int procRec( int n ) { int res = 0; if(n <= 1) return res; for( int i = 0; i < n; i++ ) res = do_something(res, i); return procRec(n/2); } n = 1: 0 n = 2: 2 + 0 n = 4: 4 + 2 + 0 n = 2^k: 2^k+2^(k−1)+···+4+2+0 = n = 2^(k+1)−2 = 2n−2 void procedure2( int n ) { for(int i=0; i<n; i++) for(int j=0; j<2*i; j++) do something(i,j,n); } void procedure4( int n ) { int j=n; while( j > 0 ) { j = j/2; do something(i,j,n); } } JAVA Syntax Helper long tmp[][] = new long[x+1][y+1]; for( int i=0; i<=x; i++ ) tmp[i][0] = 1; for( int j=1; j <= y; j++ ) for( int i=1; i<=x; i++ ) tmp[i][j] = tmp[i-1][j] + tmp[i][j-1]; if (initialCapacity < 0) throw new IllegalArgumentException( ... ); this.elementData = (E[]) new Object[initialCapacity]; if (index > size || index < 0) throw new IndexOutOfBoundsException( ... ); System.arraycopy(elementData, index, elementData, index + 1, size - index ); elementData[index] = element; Object[] myList = new Object[] {new Integer(1), new Integer(2), …} ObjectIterator iterator = new ObjectIterator(myList); while(iterator.hasNext()) { System.out.println(iterator.next()); } Datentypen: Listen, Stacks und Queues (arraybasiert oder zeigerbasierend implementiert) Array Listen (Java: ArrayList,Vector (sync)) In einer Array Liste werden die einzelnen Elemente (bzw. die Referenzen auf die Elemente) in einen Array (vom generischen Typ E) abgelegt Es entspricht direkt dem Speicherabbild Ein Element somit extrem schnell adressiert werden Array sind werden immer auf eine statische Grösse erstellt. Ein nachträgliches Vergrössern ist nicht möglich. Zufüge bzw. Lösch-Aktionen: In diesem Falle muss ein neuer Array erstellt werden und die bestehenden Werte kopiert/teilweise kopiert werden. Vergrösserung benötigt erheblich Systemressourcen (Kopieren) Doppelt verkettete Listen (Java: LinkedList, Jedes Listenelement referenziert einem Datenelement (Entry ) und zwei Zeigern (next und previous ). Liste entsteht durch Zusammenfugen der Entry Elemente. Listenanfang = Header Element. Die Elemente werden nur am Kopf der Liste zugefügt. Die Elemente in der zweiten Hälfte einer sortierten Liste sind schneller aufzufinden. Schnelles Löschen und Einfügen von Elementen. Das macht zum Beispiel das effiziente Sortieren der Liste möglich. Über die Liste kann von hinten nach vorne iteriert werden. Höherer Speicherbedarf für die zusätzlichen Zeiger. Stack-Stapelspeicher (Last-In-First-Out-Prinzip (LIFO)) (Java: Stack, Elemente können nur oben auf den Stapel gelegt bzw. von dort wieder gelesen werden. Push: legt das Objekt oben auf den Stapel. Pop: liefert das oberste Objekt und entfernt es vom Stapel. Peek: liefert das oberste Objekt, ohne es zu entfernen. Empty: wenn Liste leer ist Anwendung: Sehr häufig eingesetzte Datenstruktur / meisten von Mikroprozessoren in der Hardware direkt unterstützt. Queue /Warteschlange (First In First Out Prinzip) (Java: PriorityQueue, Eine Queue kann eine beliebige Menge von Objekten aufnehmen und gibt diese in der Reihenfolge ihres Einfügens wieder zurück. Die neuen Elemente immer am Ende angehängt. Anwendung: zur Interprozesskommunikation(Pipe) / Programmabarbeitung entkoppelt (langsame Drucker) / Datenübergabe zwischen asynchronen Prozessen / Message Queue Oft auch als Ringpuffer mit je einem Zeiger auf Anfang (In-Pointer) und Ende (Out-Pointer) implementier z.B. Flugschreiber. Iterator Ein Pattern als Hilfsmittel zum Durchlaufen von Listen. Enormer Performance Gewinn bei verketteten Listen O(n) vs. O(n^2) hasNext: prüft ob das Ende der Liste erreicht ist next: verschiebt den Iterator auf das nächste Element und gibt ihn zurück Die Implementierung der zu Grunde Je nach Variante der Implementierung können sich Nachteile durch erhöhte liegenden Datenstruktur bleibt verborgen Laufzeit- und Speicherkosten ergeben. z.B. Java: Iterator sein Aggregat kennt und/oder das Aggregat über seine Iteratoren Buch führt Weitere Listen: HashSet, LinkedHashSet, TreeSet,-> Mengenlehre Element sind immer einmalig keine Dupletten public class LinkedList<E> {…} LinkedList(){ ... }; boolean isEmpty(){ ... }; E get(int index){ ... }; void add(int index, E element){ ... }; E remove(int index){ ... }; int indexOf(Object o){ ... }; } public void add(int index, E element) { addBefore(element, (index == size ? header : entry(index))); } private Entry<E> addBefore(E o, Entry<E> e) { Entry<E> newEntry = new Entry<E>(o, e, e.previous); newEntry.previous.next = newEntry; newEntry.next.previous = newEntry; size++; return newEntry; } private Entry<E> entry(int index) { if (index < 0 || index >= size) throw new IndexOutOfBoundsExce..(...); Entry<E> e = header; if (index < (size >> 1)) { for (int i = 0; i <= index; i++) e = e.next; } else { for (int i = size; i > index; i--) e = e.previous; } return e; } private Entry<E> header; STACK private int size = 0; public E push(E item) { header = new Entry<E>(item, header); size++; return item; } public E pop() { if (size==0) throw new EmptyStackException(); Entry<E> e = header; E result = e.element; header = e.next; e.element = null; e.next = null; size--; return result; } E remove(Entry<E> e) { if (e == header) throw new NoSuchElementExce…(); E result = e.element; e.previous.next = e.next; e.next.previous = e.previous; e.next = e.previous = null; e.element = null; size--; return result; } public E peek() { if (size==0) throw new EmptyStackException(); return header.element; } public boolean empty() { return size == 0; } private static class Entry<E> { E element; Entry<E> next; Entry(E element, Entry<E> next) { this.element = element; this.next = next; } } Datenbäume Graph: definiert als ein Paar B = (E,K) bestehend aus je einer endlichen Menge E von Ecken (Knoten, Punkten) und einer Menge von Kanten. Eine Kante wird dargestellt als Zweiermenge von Ecken {x,y}, den Endpunkten der Kante. Baum: ist ein Graph mit der zusätzliche Einschränkung, dass es zwischen zwei Ecken nur eine (direkte oder indirekte) Verbindung gibt. Ein Baum ist ein zusammenhängender Graph ohne Zyklen. Binärer Baum besteht aus einer Wurzel (Root) und endlich vielen Knoten und verbindenden Kanten dazwischen. Jeder Knoten hat entweder 0, 1 oder 2 Nachfolgerknoten. Weg: in einem Baum ist eine Liste von disjunkten, direkt verbunden Kanten. Vollständiger binärer Baum (von der Höhe n) falls alle inneren Knoten zwei Nachfolger haben und die Blätter maximal Weglänge n bis zur Wurzel haben. Maximale Nodes in einem Baum = 2^(Höhe+1)-1 K Ecken (optimal plaziert): log2(n) Präorder Baumdurchlaufe Praorder: 1) Betrachte zuerst den Knoten 2 ) durchsuche L-Teilbaum 3) durchsuche R-Teilbaum Inorder: 1 ) durchsuche L-Teilbaum 2) Betrachte zuerst den Knoten 3) durchsuche R-Teilbaum Postorder: 1 ) durchsuche L-Teilbaum 2) durchsuche R-Teilbaum 3) Betrachte zuerst den Knoten Anmerkung: Inorder perfekt für für mathematische Ausdrucke Klammern wären überflüssig Inorder Tiefensuche mit Hilfe eines Stacks zur Vermeidung rekursiven Aufrufe. Inorder und Postorder können so nicht erfolgen, das Prinzip funktioniert nur für Präorder! Breitensuche mit Hilfe einer Queue Starte bei der Wurzel. Bis die Höhe des Baumes erreicht ist, setze den Level um eines höher und gehe von links nach rechts durch alle Knoten dieser Ebene. Um mittels Breitensuche durch Postorder einen Baum zu wandern, werden alle Baumknoten einer Ebene in einer Queue vermerken für den späteren Zugriff Binäre Suchbäume Alle Werte des linken Nachfolger-Baumes eines Knotens K sind kleiner, alle Werte des rechten Nachfolger-Baumes von K sind grösser als der Wert von K selber. Vorteil (Einfügen/Suchen) es muss immer nur ein Nachfolger untersucht werden. Kleiner dann links sonst rechts B-Bäume Ein B-Baum ist ein stets vollständig balancierter und sortierter Baum. In einem B-Baum darf die Anzahl Kindknoten variieren. Durch die flexiblere Anzahl Kindknoten ist das Rebalancing weniger häufig nötig. 2-3-4 B-Baum: jeder innere Knoten minimal 2 und maximal 4 Nachfolger haben darf (der Wurzelknoten hat 0-4 Nachfolger, Blätter haben keine Nachfolger). Knoten im Baum speichert: 1) eine variable Anzahl s von aufsteigend sortierten Daten-Elementen k1;:::;ks 2) isLeaf gibt an ob es ein Blatt ist. 3) s+1 Referenzen auf Kindknoten, falls der Knoten kein Blatt ist. Schranke m, so dass m <= s <= 2m gilt (Kindknoten hat mindestens m, aber höchstens 2m Daten-Elemente). Elementsuche: 1) Wurzel ->Suchknoten k 2) Suche in k von links her die Position p des ersten Daten-Elementes x, welches grösser oder gleich e ist. 3) Falls alle Daten-Elemente von k kleiner sind als e, führe die Suche im Kindknoten ganz rechts weiter. 4) Falls x gleich e ist, ist die Suche zu Ende. 5) Anderfalls wird die Suche beim p-ten Kindelement von k weitergeführt. 6) Falls k ein Blatt ist, kann die Suche abgebrochen werden (fail). Einfügen: (Achtung: nicht mehr als 2m Daten-Elemente in einem Knoten) 1) Blatt sucht (siehe Elementsuche), wo neues Element eingefügt werden muss. Bis zur Blatt-Tiefe weitersuchen (sogar, wenn wir den Wert unterwegs gefunden haben)! Falls Platz vorhanden wird der Wert dort eingefügt. Anderenfalls Knoten aufteilen und nach Mitte nach oben verschieben. Verbleibende Elemente in zwei Gruppen zerlegen. Löschen: (Achtung: jeder Knoten muss mindestens m Datenelemente enthalten) Falls Element in Blatt liegt mit mehr als m Elementen einfach löschen anderenfalls Elemente verschieben oder Blätter verschmelzen. Verschieben: wird aus dem rechten Nachbarn das kleinste Element nach oben, und das Splitter-Element des Vorgängers in das linke Blatt verschoben oder konnte (falls vorhanden) aus einem linken Nachbarn das grösste Element verschoben werden. Verschmelzen: (Achtung: links und rechts haben nur noch m Elemente im Knoten) Das linke Blatt erhält vom mittleren Blatt das Element, sowie von der Wurzel das Element drüber. Priority Queues Ziel: Elemente in einer bestimmten Reihenfolge (Priorität) abarbeiten. Elemente werden gemäss ihrer Priorität in einer Priority Queue gesammelt, so dass immer das Element mit höchster Priorität direkt verfügbar ist. Einsatzgebiet: Filekomprimierungs- oder bei Graph Algorithmen. Heap: ist ein (fast) vollständiger Baum, in welchem nur in der untersten Ebene ganz rechts Blätter fehlen dürfen. Jeder Knoten im Baum besitzt eine Priorität und eventuell noch weitere Daten. Heapbedingung: Die Knoten-priorität ist immer grösser als (oder gleich wie) die Priorität der Nachkommen. Falls als Array abgelegt siehe Bild (Vorbedingung festzulegen falls Element fehlt! (z.B. Ferner gilt: heap[i] ≥ heap[ 2*i+1]). parent(i)=[(i-1)/2]; left(i) = 2i+1; right(i) = 2i+2 Insert: RemoveMax: Array um 1 verländern, Element am Ende zufügen. Kontrolle der Heapbeding. Loop(Element nach oben vertauschen wenn grösser als Vorgänger) Max Vertauschungen log2(n) Wurzel-Element wird gelöscht und an deren Stelle das Element von unten rechts kopiert. Kontrolle Heapbeding Loop(Element wird mit dem grösseren seiner beiden Nachfolger vertauscht bis Bedingung erfüllt ist). Max Vertauschungen log2(n)+1 Suche: Einflussfaktoren auf Effizienz der Suche: Grösse der Datenmenge; benutze Datenstruktur direkt Zugriff auf das Element (Array) notfalls Pointer-Arrays bei Datenobjekten. Lineare Suche Vorne beginnen und jedes Element der Liste nacheinander vergleichen bis Element gefunden oder Ende der Liste erreicht. Komplexität (1 |n/2 | n) Asymptotische Komplextität O(n) Binäre Suche Vorbedingung: Sortierte Liste in dem man das Element an der richtigen Stelle direkt einfügt. JAVA: Sortierung erfordert das interface Comparable Methode compareTo(). Vorgehensweise: 1)Folge teilen in 2 Hälften. 2) Wert gefunden? 3)Falls der gesuchte Wert kleiner ist als die Hälfte dann Suche in der linken Teilliste wiederholen anderenfalls rechte verwenden. Wiederholen bis gefunden oder Teilliste leer ist. Asymp Komplex.: Tbest(n) in O(1) bis Tworst(n) in O(log2(n)) Hashing (.B. ELFHash mit guter Streuung) Anforderung an Hashfunktion: 1) Hashwert eines Objekts muss während der Applikation-Session zwingend gleich bleiben 2) bei Objekten die gemäss der equals() Methode gleich sind, muss der Hashwert auch gleich sein 3)verschiedene Objekte dürfen gleichen Hashwert haben. Double Hashing Lösungsansätze für Kriterium 3 Problem Verwendet zwei verschiedene Hashfunktionen Hash1 und Hash2. Hash1 dient dazu, die Hashadresse eines Schlüssels in der Tabelle zu suchen. Hash2 dient dazu, bei einer Kollision in der Tabelle den nächsten freien Platz zu suchen. Als zweite Hashfunktion kann z.B. Hash2(k) = k mod 8 + 1. Achtung Sicherstellung dass diese Funktionswerte teilerfremd zur Lange der Hashtabelle ist, Als Länge der Tabelle eine Primzahl wählen Einfügen: 1) Hashadresse Hash1(k) mod N berechnen. 2) Falls Platz bereits verwendet so wird nacheinander bei den Adressen Hash1(k) + Hash2(k)) mod N bzw, Hash1(k) + 2* Hash2(k)) mod N usw. gesucht bis ein Platz frei ist. Übersteigt der Füllstand die 60% muss die Hashtabelle vergrössert und ein Rehashing vorgenommen werden (alle Elemente neu zuteilen). Suche: Hashadresse 'Hash1(k) mod N' wird berechnet. Falls das gesuchte Element mit Schlüssel k nicht dort ist, wird der Wert Hash2(k) berechnet und das Element gemäss der Einfüge Regel gesucht. Abbruchkriterium:1) Element gefunden 2) Leeren Tabellenplatz gefunden 3) Erkennen eines Suche in einen Zyklus Löschen: Elemente werden nur gelöscht markiert! können aber wiederverwendet werden! Sortierung Suchalgorithmen Einfach: zu implementierender, langsamer Suchalgorithmus / Algorithmen mit einer Komplexitat von O(n2) ausreichend / Bubble Sort / Bei kleinen Datenmengen (weniger als 500 Elemente) Komplexere: aber schnellere Suchalgorithmus / Bei grossen Datenmengen / Algorithmen mit einer Komplexität von O(nlog2(n)) Datenstrukturen (wichtiger als der Algorithmus) Array-Strukt: ein BasisTyp-Array wie float[n] oder eine ArrayList / Vorteil: direkter Zugriff auf eine konkretes Element / Nachteil: Aufwändiges Verschieben und Löschen von Elementen Listenstruktur: z.B. LinkedList / Vorteil: Schnelles Umketten von Elementen (Einfügen und Löschen von Elementen) / Nachteil: Langsamer Zugriff auf ein konkretes Element Stablität von Sortier-Algorithmen Stabil heist, falls Elemente, welche gemäss der Vergleichsfunktion gleich sind, ihre Originalreihenfolge bei erneuten Sortieren behalten. Viele Sortier-Algorithmen auf Array-Strukturen zwar sehr schnell aber nicht stabil sind. Selection Sort Algorithmus: Nicht Stabil Komplexität: O(n^2) Einsatzgebiet: Für Arrays geeignet. Ablauf: Teilung der Menge in sortiert Menge S und nichtsortiert Menge U. Aus U wird das kleinste Element gesucht und mit dem letzten Element von S vertauscht. Swap gut für Array-Strukturen, deshalb aber auch nicht stabil. Insertion Sort Algorithmus: Stabil Komplexität: O(n^2) Einsatzgebiet: Für Listen geeignet. Ablauf: Teilung der Menge in sortiert Menge S und nichtsortiert Menge U Das jeweils erste Element aus der unsortierten Folge U wird an der richtigen Stelle in die sortierte Folge S eingefügt. Wegen dem Einfügen an einer Position nicht für Array-Strukturen geeignet, Falls das Element beim Ein-Sortieren jeweils am Ende aller (gemäß der Ordnung) gleichen Elemente eingefügt wird, ist dieser Algorithmus stabil. Quicksort Algorithmus: Hard split / Easy join - nicht stabil Komplexität: minimum: O(nlog2(n)) max: O(n^2) Einsatzgebiet: Nur für Arrays geeignet! Schwierigkeit: Das Pivot Element zu bestimmen ist die eigentliche Schwierigkeit. z.B. links Element oder mittleres Element Ablauf: Loop(1) Pivot Element q auswählen 2) der zu sortierende Bereich F so in zwei Teilbereiche Fklein und Fgross partitioniert, dass alle Element kleine als q in der Partition klein liegen der Rest in der Partition gross. ) Am Ende sortierten Folgen zusammenhängen. Ab eine Mengengrösse mit 4 oder 8 Elementen wird Selection Sort angewandt Merge Sort Algorithmus: Easy split / Hard join- stabil Komplexität: minimum: O(nlog2(n)) max: O(nlog2(n)Einsatzgebiet: Nur für Listen geeignet! Vorteil: Kein Zusätzlicher Speicher wird benötigt Schwierigkeit: Das Pivot Element zu bestimmen ist die eigentliche Schwierigkeit. Ablauf: die Folge F mit Hilfe einer Methode divideList() in zwei (möglichst) gleich grosse Hälften geteilt. Die (im rekursiven Aufruf) sortierten Folgen werden in einem linearen Durchgang zur sortierten Endfolge zusammen gefügt (gemischt). Das Mischen geschieht dadurch, dass jeweils das kleinere Kopfelement der beiden Listen ausgewählt, herausgelöst und als nächstes Element an die neue Liste angefügt wird. Bucket Sort Einsatz nur sinnvoll, wenn der Wertebereich der zu sortierenden Zahlen eng begrenzt ist. Jede der n Karteikarten wird genau einmal in einen Eimer geworfen und genau einmal wieder entnommen. Die Komplexität von Bucket Sort liegt also in O(n), benützt aber O(n) zusätzlichen Speicher. Implementation: Die Eimer werden als Array list von verketteten Listen dargestellt. Das Legen einer Karteikarte in den Eimer k wird implementiert, indem eine Referenz auf die Karteikarte in die Liste list[k] eingefügt wird. 42 3 24 33 13 5 7 25 28 14 46 16 49 15 3 24 33 13 5 7 25 28 14 15 16 49 46 42 16 46 42 16 46 49 46 49 42 16 3 15 14 13 5 7 3 Bucket Hashing (Eimer Hashing) / Kollisionsvermeidung Aufteilen des Hash-Arrays in verschiedene Buckets. Eine Hashtabelle der Länge H wird dabei aufgeteilt in H/B Teile (buckets), von der Grosse B. Nur 1 Hashfunktion wird benutzt, Elemente mit gleichem Hashwert (modulo Hashsize) werden im selben Bucket abgelegt Einfügen: Zuordnung von Element mittels Hashfunktion dem Bucket. Suche nach dem ersten freien Platz / gelöscht markierten Element anderenfalls ab in die Overflow Liste. Alle Buckets teilen sich denselben Überlauf-Speicher. Wichtig: damit Buckets nicht überlaufen muss ab einem gewissen Füllgrad ein Rehashing erfolgen, so dass die Zugriffe schnell bleiben. Suche: 1) Ermittlung mittels Hashfunktion vom richtigen Bucket 2) Abbruch der Suche falls das Element nicht gefunden wurde und im Bucket noch freie Plätze sind 3) Anderenfalls den ganzen Overflow durchsucht ob Element dort vorhanden ist. Variante des Bucket Hashing (leerer Platz = jungfräulich aber nicht gleich gelöscht markiert!!!) Keine explizite Aufteilung in Buckets / Verwendung von virtuellen Bucket rund um den Hashwert. Vorteil, jeder Tabellenplatz kann als Ausgangspositionen für das Einfugen benutzt werden Reduktion der Anzahl Kollisionen bei gleicher Tabellen-Grösse. Einfügen: 1) Ermittlung von Platz P mittels Hashfunktion. 2) Falls Platz bereits besetzt ist, wird ein freier Platz gesucht (rund um P in der Reihenfolge P + 1, P + 2, …, P + B − 2, P + B – 1) 3) Falls kein freier Platz gefunden kommt Element in den Überlauf-Speicher. Suchen: 1) gleiche Vorgehensweise wie beim Einfügen 2) Abbruch wenn gefunden oder leeren Platz gefunden 2) Anderenfalls den ganzen Overflow durchsucht ob Element dort vorhanden ist. Löschen: Elemente werden nur gelöscht markiert! können aber wiederverwendet werden! Separate Chaining Ist die flexibelste Art, um Kollisionen zu beheben. Jedes Element der Hashtabelle einen next-Zeiger auf eine verkettete Liste. Vorteil des Bucket System jedoch ohne Overflow!!! Einfügen: Zuerst wird die Hashadresse Hash(k) berechnet. Dann wird der neue Schlüssel am Anfang der verketteten Liste eingefügt. Suchen: Berechnen der Hashadresse Hash(k). Dann wird k mit den Schlüsseln in der entsprechenden Liste verglichen, bis k entweder gefunden wird oder das Ende der Liste erreicht ist. Löschen: Das Element wird einfach aus der Liste in Hash(k) entfernt. 3 Radix Sort Wie bucket Sort jedoch mit der Begrenzung der Anzahl Eimer. Prinzip zur Sortierung einzelner Zahlenstellen gemäss dem Zahlensystem, welches durch die Anzahl Eimer definiert wird. zB. 10 Eimer also Dezimalzahlen, 8 Eimer Oktalzahlen etc. Sortierkriterium = (Zahl / Zahlensystem ^ (Durchlaufebene-1) ) mod Zahlensystem 3 5 3 5 5 13 7 13 7 13 14 7 13 14 14 15 7 25 16 25 16 16 24 16 15 16 24 28 33 24 42 24 33 25 28 25 28 33 42 25 28 33 42 28 42 42 Pattern Matching Technik, mit welcher ein String aus Text oder Binardaten nach einer Zeichenfolge durchsucht wird. Die gesuchten Zeichenfolgen werden dabei in Form eines Suchmusters (Pattern) angegeben. Beschreiben von Pattern, Regulare Ausdrücke Benötigen Sprache für die Beschreibung von Pattern. Alphabet E: definiert die endliche Menge Zeichen Wort über E ist eine endliche Folge von Zeichen aus E; leeres Wort λ(Lamda) besteht aus keinem Zeichen; Menge aller Wörter des Alphabets bezeichnet man als E* ||| Für E = {A;B} ist E∗ = {λ;A;B;AA;AB;BA;BB;:::}: Reguläre Ausdrücke Ist ein Wort was mit Hilfe der Operation Konkatenation, der Auswahl oder der Iteration zusammengebaut wurden. Konkatenation: setzt zwei Wörter zusammen z.B ‚AB‘ ‚CD‘ -> ‚ABCD‘ (mittlere Bindungsstärke) Auswahl: erlaubt zwischen Wörtern auszuwählen (OR) z.B. AB|B -> ‚AB‘ oder ‚B‘ (tiefste Bindungsstärke) Iteration: repetiert das angegebene Wort beliebig oft (0…x) z.B. A(AB)* -> ‚A‘, ‚AAB‘ ‚AABAB‘ (höchste Bindungsstärke) Endliche Automaten (z.B. zur Prüfung der regulären Ausdrücke) Ein endlicher (deterministischer) Automat besteht aus einer endlichen Menge von Zuständen, einem Anfangs(oder Start-) Zustand und einem oder mehreren Endzustanden (oder akzeptierenden Zustanden). Deterministisch: wenn jede Eingabe des Eingabealphabetes in jedem Zustand erlaubt ist und zu einem eindeutigen Nachfolgezustand führt. Eingabe: Es gibt also eine endliche Menge E von Eingabezeichen, die der Automat lesen kann und die eine gewisse Aktion auslösen. Die Menge E heisst Eingabealphabet. E = {EC-Karte, Pincode (korrekt/falsch), Auswahl (Geld, Kontost.), Geldbetrag, Ok } Zustand: Ein deterministischer Automat befindet sich immer in einem bestimmten Zustand. Die endliche Menge Z der möglichen Zustände heisst die Zustandsmenge. Z = {1 (Startzustand), 2 (warten auf Pincode) (Anfangszustand Robus / Endzustand Doppelter Kreis) Zustandsübergang: Die Verarbeitung eines einzelnen Eingabezeichens kann durch eine Nachfolgefunktion, ein Zustandsdiagramm oder durch eine Zustandstafel beschrieben werden. Durch die Einwirkung der Eingabe kann er einen Zustandswechsel machen. Ausgabe: Im Laufe seiner Arbeit kann der Automat eine Ausgabe produzieren. Die endliche Menge A der produzierten Ausgabezeichen heisst Ausgabealphabet. A = {EC-Karte, Kontostand, Geld} Nichtdeterministischer: endlicher Automat kann für jeden Zustand und jede Eingabe null, einen oder mehrere Nachfolgezustände haben. leere Übergänge ε: Übergänge ohne gelesenes Zeichen (epsilon- Übergänge) ε WICHTIG: jedes Zeichen muss gelesen werden können!! Von einer leeren Menge kommt man NIE zurück!!! Automaten zu regulären Ausdrücken Suche einen zu einem regulären Ausdruck äquivalenten Automaten. (AB|C)*(A|B) (AB|BC)∗(C|BC) Prüfung von Regulären Ausdrücken BBCC –B->{1} –A->{1} –C->{4} –C-> {3} Vergleich mit dem Endzustand wenn gleich dann ok! Top Down Parser Kontextfreie Grammatik Grammatik EBNF: Extended Backus-Naur Form Mit Hilfe einer Grammatik wird Aufbau oder Strktur (Regeln) einer Satz S ist Satz ::= S ’ist’ A Sprache beschrieben. KFG dient als Notation einer Sprachsyntax. Sie S Artikel Nomen S ::= Artikel Nomen beschreibt eine Sprache (Menge von Wörtern oder Strings). KFG hat 4 Muss Kriterien: Artikel der Artikel ::= ’der’ 1) Eine Menge von Terminalen wie ’ist’, ’der’, ’ein’ … 2) Eine Menge Artikel ::= ’ein’ ein von Nichtterminalen, d.h. Namen bzw. Symbole, die weiter ersetzt werden. 3) Einer Menge von Produktionen (l ::= r), Jede Produktion A Adjektiv A ::= Adjektiv besteht aus einem Nichtterminal auf der linken Seite und einer A ::= Adjektiv ’und’ A und rechten Seite, welche eine Reihe von Terminalen und/oder A ::= Adjektiv ’oder’ A oder Nichtterminalen darstellt. 4) Ein ausgezeichnetes Nichtterminal dient Adjektiv::= ’schön’ | ’gross’ | ... als Startsymbol (ist immer ein Nichtterminal und immer links von einer Produktion) Herleiten eines Wortes: Beginn mit Startsymbol, 2) ersetzten im hergeleiteten Wort ein Nichtterminal durch die rechte Seite der Produktion. 3) Wiederholung bis alle Nichtterminale ersetzt worden sind. Alle Wörter zusammen bilden die Grammatik. Top-Down Parser public class ArithmeticExpressionParser { private String parseString; private int position; private int length; public void parse(String aExpr) throws ParseException { . . . } // one method per grammar line ... } public void parse(String aExpr) throws ParseException { position = 0; parseString = aExpr; length = parseString.length(); expression(); if (position < length) throw new ParseException(position, "parse"); } expression ::= term {′+′term |′−′term } term ::= factor {‘*′factor } private void expression() throws ParseException { term(); while(position < length ) { if( parseString.charAt(position) == ’+’ || parseString.charAt(position) == ’-’) { position++; term(); } else return; } } private void term() throws ParseException { factor(); while (position < length) if( parseString.charAt(position) == ’*’) { position++; factor(); } Else return; } A A number ::= digit { digit } private void number() throws ParseException { int n = position; while (position < length && isDigit(parseString.charAt(position))) position++; if (n == position) throw new ParseException(position, "number: digit expected"); factor ::=′(′expression′)′| number private void factor() throws ParseException { try { if (parseString.charAt(position) == ’(’) { position++; expression(); if (parseString.charAt(position) == ’)’) position++; else throw new ParseExcon(position, "factor"); } else number(); } catch (StringIndexOutOfBoundsException e) { throw new ParseException(position, "factor"); } } Kryptologie Kryprographie=Lehre des Verschlüsselns, Kryptoanalyse=Lehre des CodeKnackens, Kryptologie= Kryprographie + Kryptoanalyse Geheimhaltung, Authentifizierung(Nachweis eigener Identität), Integrität (Fälschungssicherheut) Einfache Verschlüsselung Caesar Chiffre: Vigenère Chiffre: einfachste und älteste Verschlüsselungsmethode. jeder Buchstabe wird um k verschoben (Stromchiffre) (27 Variationen) etwas verbesserter Caesar mit Schlüsselwort als Additionstabelle (Blockchiffre). -> Schlüsselwort wird endlos wiederholt und entsprechende Stellen im Klartext mit Buchstabennummer des Schlüssels addiert jedem Buchstaben wird ein anderer Zugeordnet -> Permutationsfunktion (27! Variationen) Zweierblöcken von Buchstaben wird ein anderer Block zugewiesen (272! Variationen) Permuttationschiffre: Permutation von Blöcken: Knackbar? Es gibt keine einfache und trotzdem sichere Verschlüsselungsverfahren, jeder Code ist knackbar (mit genügend Zeit und verschl. Text) Alle Methoden einfach knackbar mit Häufigkeitsanalysen (Häufigkeit von Buchst.: E, SPACE, N, R, I, S, T... oder Silben: en, ...) Vernamchiffre, OneTimePad: absolut sicher wenn: Vigenère Chiffre verwendet wird mit Schlüsselwort gleich lang wie der Text bestehend aus zufälliger Buchstabenfolge und jeder Schlüssel nur einmal verwendet. -> Sehr umständlich da pro Zeichen ein Zeichen über sicheren Weg transp. werden muss. Lösung: Als Schlüssel wird eine Zufalls-Zahlenreihe benutzt -> Zufallsfunktion muss bekannt und geheim sein. Beispiel für eine gute Zufallsfunktion: f(n) = 100*(sin(n)+1). Verschlüsselungs/Entschlüsselungsmaschinen für grosse Datenmengen (z.B. Telefone) Symmetrische Verfahren Es wird für die Verschlüsselung und Entschlüsselung derselbe Schlüssel verwendet. Die meisten sym. verfahren sind Blockchiffren. -> Auf Blöcke von 8 od. 16 Zeichen resp. 64 od. 128 Bit wird eine kombination von einfachen Verschlüsselungsverfahren angewendet (zB. modulare Addition, Substitution, Linear-Transformation, etc.) Beispiele: DES (Data Encryption Standard), DEA (Data Encryption Algorithm), Tripple-DES, CAST, RC4 (Rivest Cipher), IDEA Vorteil: ist relativ schnell und sicher! Asymmetrische Verfahren Verschlüsselung und Entschlüsselung verwenden nicht denselben Schlüssel. Ermöglicht PKK (Public Key Kryprosystem) das ohne Austausch von geheimen Schlüsseln auskommt. Jeder Teilnehmer braucht ein Schlüsselpaar:. Bedingungen für eine PKK: 1) Genügend viele Paare von Verschlüsselungsfunktionen (V,E) und Schlüsseln (v,e), 2) für jede Meldung m gilt: E(V(m))=m, 3) V ist eine Einwegfuntion (nur mit Kenntnis des 2. Schlüssels oder gr. Aufwand invertierbar), 4) V und E leicht berechenbar mit Kenntnis der Schlüssel v und e. Beispiele für PKK: Diffie-Hellmann Algorithmus (1976) und RSA Algorithmus (1978) -> Verfahren werden RSA-Kryptosysteme genannt. RSA meist genutztes PKK (SSL, SET, S/Mime, SSH, S-HTTP, ...). Ausgangslage: Berechnung von m^v mod n ist einfach, m^(1/v) mod n aber nicht Erzeugen von Schlüsseln: 1) zwei verschiedene grosse Primzahlen wählen (p,q) und Produkt m=p*q berechnen. -> Setze n=(p-1)*(q-1), 2) Wähle beliebigen wert e kleiner m und teilerfremd zu n. Berechne zu e das v zu dem gilt: e*v=1+l*n Authentifikation mit RSA (geht nur mit RSA) Die RSA-Funktionen sind umkehrbar (Verschlüsseln mit priv. Schlüssel und entschlüsseln mit öff. Schlüssel). Wird die Meldung vom Absender zusätzlich mit dem eigenen privaten Schlüssel verschlüsselt kann der Empfänger durch Entschlüsseln mit dem öffentlichen Absender-Schlüssel verifizieren ob die Meldung wirklich von diesem stammt. Integritätsprüfung mit RSA Versicherung das eine Webseite zu einem bestimmten Anbieter gehört oder dass eine Meldung unterwegs nicht verändert wurde. Vorgehensweise: Bilden eines Hash-Wertes aus den Webseiten-/Meldungsdaten, verschlüsseln mit dem privaten Schlüssel des Anbieters und senden an den Empfänger. Letzterer kann den Hashwert ebenfalls berechnen und mit dem erhaltenen Hashwert (entschlüsselt mit dem öff. Schlüssels des Anbieters) vergleichen. Wichtig öffentliche Schlüssel muss korrekt ist, uns also kein falscher Schlüssel vorgetäuscht!! Dies garantieren spezielle Firmen und Institutionen, sogenannte Trustcenter wie TC TrustCenter, VeriSign oder Thawte Hybride Verfahren Bei hybriden Verfahren werden weniger sichere symmetrische Verschlüsselungen verwendet (z.B. DES) und mittels sicheren asym. Verfahren ergänzt. Konkret wird er Schlüssel für das sym. Verfahren mit dem öff. Schlüssel des Empfängers verschlüsselt und an die sym. Verschlüsselte Meldung angehängt. Verbindet Schnelligkeit von sym. Verfahren und Sicherheit von asym. Verfahren. Diverses Datentyp Einfügen am Anfang Überschreiben der Einfügen am idx Ende Einfügen sortiert Suchen "Gefundenes" Element löschen Array, unsortiert Θ(n) Θ(1) Θ(1) - Θ(n) Θ(n) Array, sortiert - - - Θ(n) Θ(log2(n)) Θ(n) Einfach / doppelt verkette Listen Θ(1) Θ(n) Θ(1) Θ(n) Θ(n) Θ(1) Stack Θ(1) - - - - Θ(1) Queue - - Θ(1) - - Θ(1) Binärer Suchbaum - - - Θ(log2(n)) Θ(log2(n)) Θ(log2(n)) Heap(priority Queue) - - - Θ(log2(n)) Θ(n) * Θ(log2(n)) Hash Tabelle - - - Θ(1) Θ(1) Θ(1) Public class BinaryTreeNode<T>{ protected T data; protected BinaryTreeNode<T>leftChild; protected BinaryTreeNode<T>rightChild; public BinaryTreeNode(T item){data = item;} public BinaryTreeNode<T> inOrderFind(final T item){. . . } public BinaryTreeNode<T> postOrderFind(final T item){. . . } public BinaryTreeNode<T> preOrderFind(final T item){. . . } } public BinaryTreeNode<T>preOrderFind(final T item){ if (data.equals(item)) return this; // Präordersuche Punkt 1 if (left.Child != null) // Präordersuche Punkt 2 {BinaryTreeNode<T> result = leftChildren.preOrderFind(item); if (result != null) return result;} if (right.Child != null) // Präordersuche Punkt 3 {BinaryTreeNode<T> result = rightChildren.preOrderFind(item); if (result != null) return result;} return null; }