Lernziele
Die Klasse dynamic_random_variate |
Eine sinnvoller Anwendung der Methode resize() der Klasse array |
Der gezinkte Würfel aus dem vorigen Abschnitt mag uns vielleicht Glück im Spiel gebracht haben, Glück in der Liebe kann er uns niemals bescheren. Falls wir in dieser misslichen Lage sind, sollten wir keinesfalls versuchen, unseren Kummer durch alkoholhaltige Getränke herunterzuspülen, denn sonst kann unser Heimweg aussehen wie in Abbildung 2.20.
Was wir hier sehen, ist der mehr oder weniger zufällige Spaziergang (engl. random walk) eines Betrunkenen. Bei jedem Schritt vorwärts geht der Betrunkene zufällig einen Schritt nach links oder rechts und torkelt so seiner rettenden Ruhestätte entgegen. Nun wollen wir annehmen, dass sein Gedächtnis und seine Entscheidungsfähigkeit nicht völlig außer Gefecht gesetzt sind, sondern dass er noch zu Gedanken der folgenden Form fähig ist: „Ich glaube, ich bin gerade nach links gegangen, ich sollte jetzt mal wieder nach rechts gehen, oder doch nicht? Ach, ist doch alles egal...“ Mit anderen Worten: Wenn er gerade nach links gegangen ist, sollte die Wahrscheinlichkeit, dass er im nächsten Schritt wieder nach links geht, kleiner werden; bei einem Schritt nach rechts entsprechend umgekehrt.
Wenn wir einen solchen zufälligen Spaziergang simulieren wollen,
benötigen wir offenbar eine Zufallsvariable für die Torkelrichtungen
„Links“ und „Rechts“, deren
Wahrscheinlichkeiten sich dynamisch anpassen
lassen. Genau dies leistet die LEDA-Klasse
dynamic_random_variate.
In der Simulation stellen wir uns vor, dass wir auf dem Zahlenstrahl stehen. Wir starten bei 0. Im ersten Schritt laufen wir mit gleicher Wahrscheinlichkeit nach links oder nach rechts. Jedesmal, wenn wir in eine Richtung gelaufen sind, verdoppeln wir das Gewicht der jeweils anderen Richtung. (Das bedeutet nicht, dass die entsprechende Wahrscheinlichkeit verdoppelt wird!) Insgesamt machen wir eine Million Schritte. (Ein tatsächlicher Nach-Hause-Weg ist hoffentlich nicht so lang.) Wir zählen in einem Array mit, wie oft wir bei welcher Zahl waren und geben am Schluss die Zählerstände aus.
#include <LEDA/random_variate.h>
#include <LEDA/array.h>
#include <iostream>
using leda::dynamic_random_variate;
using leda::array;
enum direction {LEFT, RIGHT};
array<int> weight(LEFT, RIGHT);
inline void double_weight(dynamic_random_variate&, direction);
int main()
{
weight[LEFT] = weight[RIGHT] = 1;
dynamic_random_variate DRV(weight);
array<int> counter(-1,1);
counter[0] = 1; // Start at position 0
counter[-1] = counter[1] = 0;
const int num_walks = 1000000;
int cur_pos = 0;
// Do random walk
for(int i = 0; i < num_walks; i++) {
int dir = DRV.generate();
if(dir == LEFT) { // Walk to the left
cur_pos--;
if(cur_pos < counter.low())
counter.resize(counter.low() * 2, counter.high());
counter[cur_pos]++;
double_weight(DRV, RIGHT);
}
else { // Walk to the right
cur_pos++;
if(cur_pos > counter.high())
counter.resize(counter.low(), counter.high() * 2);
counter[cur_pos]++;
double_weight(DRV, LEFT);
}
}
// Find boundaries of walk
int lbound = counter.low();
while(counter[lbound] == 0) lbound++;
int ubound = counter.high();
while(counter[ubound] == 0) ubound--;
// Print statistics
for(int i = lbound; i <= ubound; i++) {
std::cout.width(2);
std::cout << i << ": " << counter[i] << "\n";
}
}
inline void double_weight(dynamic_random_variate& DRV, direction dir) {
int otherdir = 1 - dir;
if(weight[otherdir] > 1) {
weight[otherdir] /= 2;
DRV.set_weight(otherdir, weight[otherdir]);
}
else {
weight[dir] *= 2;
DRV.set_weight(dir, weight[dir]);
}
}
Ein Beispiellauf erzeugt folgende Ausgabe:
-6: 2 -5: 147 -4: 2499 -3: 21527 -2: 95324 -1: 228021 0: 304723 1: 228780 2: 94966 3: 21386 4: 2482 5: 139 6: 5
Das zeigt, dass wir immer schön um die 0 herum schwanken. Je weiter wir von der Mitte abgewichen sind, desto unwahrscheinlicher wird es, dass wir noch weiter abweichen.
Wie die die Klasse random_variate
benötigt auch die Klasse
dynamic_random_variate bei der
Definition ein Array, in dem die Gewichte der zu erzeugenden
Zufallswerte stehen:
dynamic_random_variate DRV(weight);
Im Unterschied zu ersterer können aber die einzelnen Gewichte zur Laufzeit durch einen Aufruf der Methode
DRV.set_weight(i, w);
abgeändert
werden. Hierbei bezieht sich i auf den
entsprechenden Index des Gewichts-Arrays und
w ist das neue Gewicht des i-ten
Zufallswertes.
Obiges Programm ist gleichzeitig ein Beispiel für eine
sinnvolle Verwendung der Methode resize()
von LEDA-Arrays und dafür, dass LEDA-Arrays auch negative
Indizes haben dürfen. Die ursprünglichen Array-Grenzen sind -1
bzw. 1. Wenn wir über diese Grenzen hinauslaufen, verdoppeln wir
die Array-Grenze in die entsprechende Richtung. Diese
Verdoppelungsstrategie gibt uns genug „Luft“ bis
zur nächsten Verdoppelung, d. h. amortisiert gesehen bedeutet sie
einen konstanten Zeitaufwand.
Bei der Verdoppelung der Gewichte in der Funktion
double_weight() bedienen wir uns eines
Tricks: Wir können nicht einfach jedesmal das entsprechende
Gewicht verdoppeln, weil wir sonst sehr bald (je nach
Maschinenarchitektur nach 31 oder 63 Verdoppelungen) über den
Definitionsbereich des Typs int
hinauskämen. Statt dessen machen wir die Beobachtung, dass bei
nur zwei möglichen Zufallswerten das relative Gewicht eines
Wertes auch dadurch verdoppelt werden kann, dass das Gewicht
des anderen halbiert wird. Ist also das jeweils andere Gewicht
größer als 1, so halbieren wir dieses, ansonsten verdoppeln
wir. Hierbei ist nochmals anzumerken, dass die Gewichte der
Klassen random_variate und
dynamic_random_variate immer
ganzzahlig sein müssen!
Mehr zur Klasse
dynamic_random_variate findet sich
auf der entsprechenden Manualseite.