Datenstrukturen und Algorithmen Übung 5 Alexander Pilz, Daniel Hupp, Lukas Humbel FS 2017 1 Programm von heute 1 Feedback letzte Übung 2 Wiederholung Theorie 3 Programmieraufgabe 2 Amortisierte Analyse: push back Strategie: verdoppeln wenn Array voll ist. 3 Amortisierte Analyse: push back Strategie: verdoppeln wenn Array voll ist. 1 wenn Array noch nicht voll ti = 2n + n + 1 = 3n + 1 sonst 3 Amortisierte Analyse: push back Strategie: verdoppeln wenn Array voll ist. 1 wenn Array noch nicht voll ti = 2n + n + 1 = 3n + 1 sonst Finde Potential function so dass amortisierte Kosten konstant sind: ai = ti + Φi − Φi−1 3 Amortisierte Analyse: push back Strategie: verdoppeln wenn Array voll ist. 1 wenn Array noch nicht voll ti = 2n + n + 1 = 3n + 1 sonst Finde Potential function so dass amortisierte Kosten konstant sind: ai = ti + Φi − Φi−1 n Φi = 6·Anzahl Elemente in der oberen Hälfte des Arrays ( +1, . . . , n) 2 3 Amortisierte Analyse: push back Strategie: verdoppeln wenn Array voll ist. 1 wenn Array noch nicht voll ti = 2n + n + 1 = 3n + 1 sonst Finde Potential function so dass amortisierte Kosten konstant sind: ai = ti + Φi − Φi−1 n Φi = 6·Anzahl Elemente in der oberen Hälfte des Arrays ( +1, . . . , n) 2 ⇒ 7 ≥ ai ( in beiden Fällen) 3 Amortisierte Analyse: pop back Strategie: halbieren wenn Array viertel leer ist. 4 Amortisierte Analyse: pop back Strategie: halbieren wenn Array viertel leer ist. 1 ti = n 2 + n 4 = 3 4n wenn Array mehr als viertel voll ist sonst 4 Amortisierte Analyse: pop back Strategie: halbieren wenn Array viertel leer ist. 1 ti = n 2 + n 4 = 3 4n wenn Array mehr als viertel voll ist sonst Finde Potential function so dass amortisierte Kosten konstant sind: ai = ti + Φi − Φi−1 4 Amortisierte Analyse: pop back Strategie: halbieren wenn Array viertel leer ist. 1 ti = n 2 + n 4 = 3 4n wenn Array mehr als viertel voll ist sonst Finde Potential function so dass amortisierte Kosten konstant sind: ai = ti + Φi − Φi−1 n φi = 3 · Anzahl Elemente in der unteren Hälfte des Arrays (1, . . . , ) 2 4 Amortisierte Analyse: pop back Strategie: halbieren wenn Array viertel leer ist. 1 ti = n 2 + n 4 = 3 4n wenn Array mehr als viertel voll ist sonst Finde Potential function so dass amortisierte Kosten konstant sind: ai = ti + Φi − Φi−1 n φi = 3 · Anzahl Elemente in der unteren Hälfte des Arrays (1, . . . , ) 2 ⇒ 4 ≥ ai ( in beiden Fällen) 4 Randomisierte Skipliste Idee: Füge jeweils einen Knoten mit zufälliger Höhe H ein, wobei 1 P(H = i) = 2i+1 . 3 2 1 0 x1 x2 x3 x4 x5 x6 x7 x8 ∞ 5 Skiplisten Interface class SkipList { public: SkipList(); ~SkipList(); void insert( const T& newValue ); void erase( const T& eraseValue ); }; 6 I Definiere einen Knoten x 7 I Definiere einen Knoten struct Node { T value; // pointers to successor nodes std::vector<Node*> forward; x Node( const T& v, int level ): value(v), forward(level,nullptr) {} }; 7 Implementiere insert insert new value finde erst kleineren node wähle zufällige Anzahl von Leveln aus erstelle neuen node setze pointers von den vorhärigen nodes und neuem node 8 Implementiere insert insert new value finde erst kleineren node wähle zufällige Anzahl von Leveln aus erstelle neuen node setze pointers von den vorhärigen nodes und neuem node void insert( const T& value ) { std::vector<Node*> preds = predecessors( value ); const int n = randomLevel(); Node* node = new Node( value, n); for( int i = 0; i<n; ++i ) { node->forward[i] = preds[i]->forward[i]; preds[i]->forward[i] = node; } } 8 Implementiere erase erase( value ) finde ersten kleineren node überprüfe ob nächster node den Wert value hat Pointers entsprechend setzen gegebnenfals node löschen 9 Implementiere erase erase( value ) void erase( const T& val ) { finde ersten std::vector<Node*> preds = kleineren node predecessors( val ); überprüfe ob nächster node den Node* n = preds[0]->forward[0]; while( n!=nullptr && n->value==val ) { Wert value hat for( int i = 0; i<nodeLevel(n); ++i) Pointers preds[i]->forward[i] = n->forward[i]; entsprechend delete n; setzen n = preds[0]->forward[0]; gegebnenfals } node löschen } 9 2. Wiederholung Theorie 10 Beispiele guter Hashfunktionen h(k) = k mod m, m Primzahl √ h(k) = bm(k · r − bk · rc)c, r irrational, besonders gut: r = 5−1 2 . 11 Offene Hashverfahren Speichere die Überläufer direkt in der Hashtabelle mit einer Sondierungsfunktion s(j, k) (0 ≤ j < m, k ∈ K) Tabellenposition des Schlüssels entlang der Sondierungsfolge S(k) := (h(k) − s(0, k) mod m, . . . , (h(k) − (m − 1, k)) mod m 12 Algorithmen zum Open Addressing search(k) Durchlaufe Tabelleneinträge gemäss S(k). Wird k gefunden, gib true zurück. Ist die Sondierungsfolge zu Ende oder eine leere Position erreicht, gib false zurück. insert(k) Suche k in der Tabelle gemäss S(k). Ist k nicht vorhanden, füge k an die erste freie Position in der Sondierungsfolge ein. 1 delete(k) Suche k in der Tabelle gemäss S(k). Wenn k gefunden, markiere Position von k mit einem deleted-flag. 1 Als frei gilt auch eine nicht leere Position mit deleted flag. 13 Lineares Sondieren s(j, k) = j ⇒ S(k) = (h(k) mod m, (h(k) − 1) mod m, . . . , (h(k) + 1) mod m) 14 Lineares Sondieren s(j, k) = j ⇒ S(k) = (h(k) mod m, (h(k) − 1) mod m, . . . , (h(k) + 1) mod m) Beispiel m = 7, K = {0, . . . , 500}, h(k) = k mod m. Schlüssel 12 0 1 2 3 4 5 6 Lineares Sondieren s(j, k) = j ⇒ S(k) = (h(k) mod m, (h(k) − 1) mod m, . . . , (h(k) + 1) mod m) Beispiel m = 7, K = {0, . . . , 500}, h(k) = k mod m. Schlüssel 12 , 53 0 1 2 3 4 5 12 6 Lineares Sondieren s(j, k) = j ⇒ S(k) = (h(k) mod m, (h(k) − 1) mod m, . . . , (h(k) + 1) mod m) Beispiel m = 7, K = {0, . . . , 500}, h(k) = k mod m. Schlüssel 12 , 53 , 5 0 1 2 3 4 5 53 12 6 Lineares Sondieren s(j, k) = j ⇒ S(k) = (h(k) mod m, (h(k) − 1) mod m, . . . , (h(k) + 1) mod m) Beispiel m = 7, K = {0, . . . , 500}, h(k) = k mod m. Schlüssel 12 , 53 , 5 , 15 0 1 2 3 4 5 5 53 12 6 Lineares Sondieren s(j, k) = j ⇒ S(k) = (h(k) mod m, (h(k) − 1) mod m, . . . , (h(k) + 1) mod m) Beispiel m = 7, K = {0, . . . , 500}, h(k) = k mod m. Schlüssel 12 , 53 , 5 , 15 , 2 0 1 15 2 3 4 5 5 53 12 6 Lineares Sondieren s(j, k) = j ⇒ S(k) = (h(k) mod m, (h(k) − 1) mod m, . . . , (h(k) + 1) mod m) Beispiel m = 7, K = {0, . . . , 500}, h(k) = k mod m. Schlüssel 12 , 53 , 5 , 15 , 2 , 19 0 1 2 3 4 5 15 2 5 53 12 6 Lineares Sondieren s(j, k) = j ⇒ S(k) = (h(k) mod m, (h(k) − 1) mod m, . . . , (h(k) + 1) mod m) Beispiel m = 7, K = {0, . . . , 500}, h(k) = k mod m. Schlüssel 12 , 53 , 5 , 15 , 2 , 19 0 1 2 3 4 5 19 15 2 5 53 12 6 14 Quadratisches Sondieren s(j, k) = dj/2e2 (−1)j S(k) = (h(k) + 1, h(k) − 1, h(k) + 4, h(k) − 4, . . . ) mod m 15 Quadratisches Sondieren s(j, k) = dj/2e2 (−1)j S(k) = (h(k) + 1, h(k) − 1, h(k) + 4, h(k) − 4, . . . ) mod m Beispiel m = 7, K = {0, . . . , 500}, h(k) = k mod m. Schlüssel 12 0 1 2 3 4 5 6 Quadratisches Sondieren s(j, k) = dj/2e2 (−1)j S(k) = (h(k) + 1, h(k) − 1, h(k) + 4, h(k) − 4, . . . ) mod m Beispiel m = 7, K = {0, . . . , 500}, h(k) = k mod m. Schlüssel 12 , 53 0 1 2 3 4 5 12 6 Quadratisches Sondieren s(j, k) = dj/2e2 (−1)j S(k) = (h(k) + 1, h(k) − 1, h(k) + 4, h(k) − 4, . . . ) mod m Beispiel m = 7, K = {0, . . . , 500}, h(k) = k mod m. Schlüssel 12 , 53 , 5 0 1 2 3 4 5 53 12 6 Quadratisches Sondieren s(j, k) = dj/2e2 (−1)j S(k) = (h(k) + 1, h(k) − 1, h(k) + 4, h(k) − 4, . . . ) mod m Beispiel m = 7, K = {0, . . . , 500}, h(k) = k mod m. Schlüssel 12 , 53 , 5 , 15 0 1 2 3 4 5 6 53 12 5 Quadratisches Sondieren s(j, k) = dj/2e2 (−1)j S(k) = (h(k) + 1, h(k) − 1, h(k) + 4, h(k) − 4, . . . ) mod m Beispiel m = 7, K = {0, . . . , 500}, h(k) = k mod m. Schlüssel 12 , 53 , 5 , 15 , 2 0 1 15 2 3 4 5 6 53 12 5 Quadratisches Sondieren s(j, k) = dj/2e2 (−1)j S(k) = (h(k) + 1, h(k) − 1, h(k) + 4, h(k) − 4, . . . ) mod m Beispiel m = 7, K = {0, . . . , 500}, h(k) = k mod m. Schlüssel 12 , 53 , 5 , 15 , 2 , 19 0 1 2 15 2 3 4 5 6 53 12 5 Quadratisches Sondieren s(j, k) = dj/2e2 (−1)j S(k) = (h(k) + 1, h(k) − 1, h(k) + 4, h(k) − 4, . . . ) mod m Beispiel m = 7, K = {0, . . . , 500}, h(k) = k mod m. Schlüssel 12 , 53 , 5 , 15 , 2 , 19 0 1 2 19 15 2 3 4 5 6 53 12 5 15 Double Hashing Zwei Hashfunktionen h(k) und h0 (k). s(j, k) = j · h0 (k). S(k) = (h(k) − h0 (k), h(k) − 2h0 (k), . . . , h(k) − (m − 1)h0 (k)) mod m 16 Double Hashing Zwei Hashfunktionen h(k) und h0 (k). s(j, k) = j · h0 (k). S(k) = (h(k) − h0 (k), h(k) − 2h0 (k), . . . , h(k) − (m − 1)h0 (k)) mod m Beispiel: m = 7, K = {0, . . . , 500}, h(k) = k mod 7, h0 (k) = 1 + k mod 5. Schlüssel 12 0 1 2 3 4 5 6 Double Hashing Zwei Hashfunktionen h(k) und h0 (k). s(j, k) = j · h0 (k). S(k) = (h(k) − h0 (k), h(k) − 2h0 (k), . . . , h(k) − (m − 1)h0 (k)) mod m Beispiel: m = 7, K = {0, . . . , 500}, h(k) = k mod 7, h0 (k) = 1 + k mod 5. Schlüssel 12 , 53 0 1 2 3 4 5 12 6 Double Hashing Zwei Hashfunktionen h(k) und h0 (k). s(j, k) = j · h0 (k). S(k) = (h(k) − h0 (k), h(k) − 2h0 (k), . . . , h(k) − (m − 1)h0 (k)) mod m Beispiel: m = 7, K = {0, . . . , 500}, h(k) = k mod 7, h0 (k) = 1 + k mod 5. Schlüssel 12 , 53 , 5 0 1 2 3 4 5 53 12 6 Double Hashing Zwei Hashfunktionen h(k) und h0 (k). s(j, k) = j · h0 (k). S(k) = (h(k) − h0 (k), h(k) − 2h0 (k), . . . , h(k) − (m − 1)h0 (k)) mod m Beispiel: m = 7, K = {0, . . . , 500}, h(k) = k mod 7, h0 (k) = 1 + k mod 5. Schlüssel 12 , 53 , 5 , 15 0 1 2 3 4 5 5 53 12 6 Double Hashing Zwei Hashfunktionen h(k) und h0 (k). s(j, k) = j · h0 (k). S(k) = (h(k) − h0 (k), h(k) − 2h0 (k), . . . , h(k) − (m − 1)h0 (k)) mod m Beispiel: m = 7, K = {0, . . . , 500}, h(k) = k mod 7, h0 (k) = 1 + k mod 5. Schlüssel 12 , 53 , 5 , 15 , 2 0 1 15 2 3 4 5 5 53 12 6 Double Hashing Zwei Hashfunktionen h(k) und h0 (k). s(j, k) = j · h0 (k). S(k) = (h(k) − h0 (k), h(k) − 2h0 (k), . . . , h(k) − (m − 1)h0 (k)) mod m Beispiel: m = 7, K = {0, . . . , 500}, h(k) = k mod 7, h0 (k) = 1 + k mod 5. Schlüssel 12 , 53 , 5 , 15 , 2 , 19 0 1 2 3 4 5 15 2 5 53 12 6 Double Hashing Zwei Hashfunktionen h(k) und h0 (k). s(j, k) = j · h0 (k). S(k) = (h(k) − h0 (k), h(k) − 2h0 (k), . . . , h(k) − (m − 1)h0 (k)) mod m Beispiel: m = 7, K = {0, . . . , 500}, h(k) = k mod 7, h0 (k) = 1 + k mod 5. Schlüssel 12 , 53 , 5 , 15 , 2 , 19 0 1 2 3 4 5 19 15 2 5 53 12 6 16 3. Programmieraufgabe 17 Finden eines Sub-Arrays Gegeben: Zwei Felder A = (a0 , . . . , an−1 ) and B = (b0 , . . . , bk−1 ) mit natürlichen Zahlen Aufgabe: Finde Position von B in A. 18 Finden eines Sub-Arrays Gegeben: Zwei Felder A = (a0 , . . . , an−1 ) and B = (b0 , . . . , bk−1 ) mit natürlichen Zahlen Aufgabe: Finde Position von B in A. Naiv: Gehe durch A, vergleiche die k folgenden Einträge mit B 18 Finden eines Sub-Arrays Gegeben: Zwei Felder A = (a0 , . . . , an−1 ) and B = (b0 , . . . , bk−1 ) mit natürlichen Zahlen Aufgabe: Finde Position von B in A. Naiv: Gehe durch A, vergleiche die k folgenden Einträge mit B O(nk) Vergleiche 18 Finden eines Sub-Arrays Gegeben: Zwei Felder A = (a0 , . . . , an−1 ) and B = (b0 , . . . , bk−1 ) mit natürlichen Zahlen Aufgabe: Finde Position von B in A. Naiv: Gehe durch A, vergleiche die k folgenden Einträge mit B O(nk) Vergleiche Lösung mit Hashing: Berechne Hash h(B) und vergleiche mit h((ai , ai+1 , . . . , ai+k−1 )). Vermeide die Neuberechnung von h((ai , ai+1 , . . . , ai+k−1 )) für jedes i =⇒ O(n) erwartet 18 Sliding Window Hash Mögliche Hash-Funktion: Summe aller Elemente: Kann einfach aktualisiert werden: ai abziehen und ai+k hinzufügen. Aber: schlechte Hash-Funktion 19 Sliding Window Hash Mögliche Hash-Funktion: Summe aller Elemente: Kann einfach aktualisiert werden: ai abziehen und ai+k hinzufügen. Aber: schlechte Hash-Funktion Besser: P k−j−1 Hc,m ((ai , · · · , ai+k−1 )) = k−1 mod m j=0 ai+j c c = 1021 Primzahl m = 215 int, keine Überläufe bei Berechnungen 19 Sliding Window Hash Mögliche Hash-Funktion: Summe aller Elemente: Kann einfach aktualisiert werden: ai abziehen und ai+k hinzufügen. Aber: schlechte Hash-Funktion Besser: P k−j−1 Hc,m ((ai , · · · , ai+k−1 )) = k−1 mod m j=0 ai+j c c = 1021 Primzahl m = 215 int, keine Überläufe bei Berechnungen Hc,m ((ai+1 , · · · , ai+k )) = kj=1 ai+j ck−j mod m = c · Hc,m ((ai , · · · , ai+k−1 )) + ai+k − ck ai mod m P 19 Sliding Window Hash template<typename It1, typename It2> It1 findOccurrence(const It1 from, const It1 to, const It2 begin, const It2 end) { const unsigned k = end - begin; const unsigned M = 32768; const unsigned C = 1021; // your code here // ... 20 Sliding Window Hash // elements can be compared using std::equal: if(std::equal(window_left, window_right, begin, end)) return current; // if no element is found return end of array return to; } 21 Sliding Window Hash Gehen Sie sicher, dass der Algorithmus ck nur einmal berechnet, alle Werte modulo m berechnet werden, um Überläufe zu vermeiden (verwenden Sie die Rechenregeln für Kongruenzen), und alle Werte positiv sind (z.B. durch Addition von Vielfachen von m). 22 Fragen? 23 Fragen? Let’s get to work. 23