R S IS UN http://www.mpi-sb.mpg.de/~sschmitt/info5-ss01 E R SIT S SS 2001 SA Schmitt, Schömer IV A Grundlagen zu Datenstrukturen und Algorithmen A VIE N Lösungsvorschläge für das 5. Übungsblatt Letzte Änderung am 16. Mai 2001 Aufgabe 1 a) Berechnen Sie die mittlere Anzahl von Würfen bis zum Auftreten einer Sechs. Oder allgemeiner: Ist p die Wahrscheinlichkeit mit der ein bestimmtes Ereignis auftritt und X = Anzahl der Experimente bis dieses Ereignis auftritt. Dann ist P (X = i) = p · (1 − p)i−1 . E(X) = ∞ X i · p · (1 − p)i−1 i=1 X p = · lim i · (1 − p)i n−>∞ 1−p i=1 n (1 − p)n · ((1 − p) · n + n + (1 − p)) + (1 − p) p · lim 1 − p n−>∞ p2 p 0 + (1 − p) = · 1−p p2 1 = p = Da in diesem Fall p = 16 ⇒ E(X) = 6. b) Berechnen Sie die mittlere Anzahl von Würfen bis jede Augenzahl einmal aufgetreten ist. Nun ist X = Anzahl der Würfe bis alle Augenzahlen einmal aufgetreten sind bzw X= 6 X Yi i=1 mit Yi = Anzahl der Würfe vom Auftreten der (i-1)’ten Zahl bis zum Auftreten der i’ten Zahl Es gilt: P (Yi = k) = pi (1 − pi )k−1 mit pi = 7−i 6 E(X) = E( = ⇒ E(Yi ) = 6 X i=1 6 X Yi ) E(Yi ) i=1 6 7−i = 6 X i=1 6 7−i 6 3 + +2+3+6 5 2 = 14, 7 = 1+ Aufgabe 2 Definition: Eine Instanz S des parametrisierten Datentyps b_stack<T> ist eine Sequenz beschränkter Länge von Elementen des Typs T . Sowohl das Einfügen als auch das Entfernen von Elementen findet stets an einem Ende der Sequenz statt (Last In - First Out). Die maximale Größe wird bei der Erzeugung festgelegt. Erzeugung: b_stack<T> S(int n) Erzeugt eine Instanz S des Typs b_stack<T> der Größe n, die Anfangs die leere Sequenz ist. Operationen: bool S.isEmpty() liefert den Wert true zurück, wenn S die leere Sequenz ist, ansonsten false. void S.push(T x) hängt das Element x vom Typ T an das Ende von S an, falls |S| < n ist. T S.pop() liefert das Element x vom Ende der Sequenz S zurück und entfernt es vom Ende, falls S nicht die leere Sequenz ist. Laufzeit: Wie man leicht sehen kann, geschehen alle Stack-Operationen auf einem Array und finden deshalb in konstanter Laufzeit statt. is_empty() hat trivialerweise ebenfalls konstante Laufzeit. Implemetierung: template<class T> class b_stack { int top; int size; array<T> *A; // zeigt, wieviele Elemente auf dem Stapel liegen // die totale Groesse des Stapels // Zeiger auf ein Array aus Typen T public: b_stack(int n) // Konstruktor; n gibt die Groesse an { top = 0; size = n; A = new array<T>(n); // Zeiger auf ein neues Array der Groesse n biegen } ~b_stack() //Destruktor { delete A; } void push(T x) { assert(top<size); (*A)[top++] = x; } T pop() { assert(top>0); return (*A)[--top]; } bool isEmpty() { return top == 0; } // ist noch Platz auf den Stapel? // "oberstes" Element zuweisen, Stapel "erhoehen" // sind noch Elemente auf dem Stapel? // oberstes Element zurueckgeben, Stapel "verkleinern" // Null Elemente? => Stapel leer. }; Aufgabe 3 Definition: Eine Instanz S des parametrisierten Datentyps b_queue<T> ist eine Sequenz beschränkter Länge von Elementen des Typs T . Das Einfügen von Elementen findet stets am Ende, das Entfernen am Anfang der Sequenz statt (First In - First Out). Die maximale Größe wird bei der Erzeugung festgelegt. Erzeugung: b_queue<T> S(int n) Erzeugt eine Instanz S des Typs b_queue<T> der Größe n, die Anfangs die leere Sequenz ist. Operationen: bool S.isEmpty() liefert den Wert true zurück, wenn S die leere Sequenz ist, ansonsten false. void S.enqueue(T x) hängt das Element x vom Typ T an das Ende von S an, falls |S| < n ist. T S.dequeue() liefert das Element x vom Anfang der Sequenz S zurück und entfernt es von dort, falls S nicht die leere Sequenz ist. Laufzeit: Um die Laufzeit von n aufeinanderfolgenden Operationen in Θ(n) zu halten, was bedeutet, daß die Operationslaufzeiten im Mittel konstant sind, benutzen wir zwei getrennte b_stacks, die ebenso groß sind wie die Schlange. Einen benutzen wir stets für die Einfügeoperationen, d.h. was auch immer an die Schlange angefügt werden soll, legen wir oben auf den, sagen wir “linken” Stack. Erfolgt nun eine dequeue-Operation, so nehmen wir immer das oberste Element vom “rechten” Stack. Sollte dieser leer sein (z.B. vor der ersten enqueue-Operation) “kippen” wir einfach alle Elemente von links nach rechts - tatsächlich “kippen”, da die Elemente des enqueue-Stacks danach in umgekehrter Reihenfolge auf dem dequeue-Stack liegen. Wir brauchen also bei der ersten dequeue-Operation die Laufzeit Θ(k), wenn davor k enqueueOperationen direkt aufeinander gefolgt sind. Für alle k − 1 nachfolgenden dequeue-Operationen brauchen wir aber wieder jeweils nur konstante Zeit, egal ob zwischendurch wieder enqueue-t wird. Bei n aufeinanderfolgenden, beliebig gemischten de- und enqueue-Operationen wird jedes Element je höchstens einmal AUF den linken Stapel, VOM linken AUF den rechten gelegt, und wieder VOM Rechten genommen. Also liegt die Gesamtlaufzeit der Operationen in Θ(n), also amortisiert kostet eine Operation Θ(1) Zeit. Implemetierung: template<class T> class b_queue { int top; int size; b_stack<T> *enq_stack; b_stack<T> *deq_stack; // // // // wieder die aktuelle "Laenge" der Schlange absolute Groesse der Stack fuer die enqueue-Operationen und der fuer die dequeue-Operationen public: b_queue(int n) { top = 0; size = n; enq_stack = // die beiden Stacks mit der Groesse n initialisieren new b_stack<T>(n); deq_stack = new b_stack<T>(n); } ~b_queue() { delete enq_stack; delete deq_stack; } void enqueue(T x) { assert(top<size); top++; enq_stack->push(x); } T dequeue() { assert(top>0); top--; //Destruktor // ist noch Platz in der Schlange? // Schlange "verlaengern" // und bitte auf dem enqueue-Stapel Platz nehmen // Elemente in der Schlange? if (deq_stack->isEmpty()) // Ist der deque-Stapel leer,dann while(!enq_stack->isEmpty()) // kippen wir den enqueue-Stapel deq_stack->push(enq_stack->pop()); // umgekehrt auf den dequeue-Stapel return deq_stack->pop(); // "erstes" Element liegt oben auf dequeue Stapel } bool isEmpty() { return top == 0; } // Null Elemente => Schlange leer. }; Anmerkung: objekt → memberf unction() entspricht (∗objekt).memberf unction() Aufgabe 4 a) integer encode(int n,int B) { int m = n; int i = 0; integer d; // initialisiere Darstellung while(m != 0) { d.put(i,m % B); // schreibe Rest an aktuelle Stelle (mod) m = m / B; // berechne noch zu betrachtende Zahl (div) i++; // betrachte naechste Potenz } return(d); // gebe Darstellung zurueck } Siehe auch Skript, Seiten 9ff. b) a = 95110 = 25267 b = 41610 = 11337 Alle Rechnungen sind bzgl. Basis 7 zu verstehen. 2526 * 1133 ----------2526 2526 11214 11214 ----------3235254 Ergebnis: 25267 · 11337 = 32352547 . c) Eine B-adische Darstellung wie in der Aufgabenstellung angegeben ist mit B = 1 nicht sinnvoll möglich! Das ist leicht einzusehen, da man in diesem Fall für alle B i den gleichen Wert bekommt, nämlich ∀ i : B i = 1. Sicherlich lassen sich Zahlen auch mit einem Element als Reihen darstellen, allerdings ist der Sinn der B-adischen Darstellung ja eine Vereinfachung des Zählens (der math. Operationen) und das ist bei dieser Darstellung sicherlich nicht der Fall!