Startseite → Matherätsel → Das Ziegenproblem → Lösung
Lösung: Das Ziegenproblem
Lösung
Zuerst entscheidet sich der Kandidat für eine Tür. Die Wahrscheinlichkeit, dass das Auto dahinter steht liegt bei 1/3. Die Wahrscheinlichkeit, dass es hinter einer der beiden anderen Türen steht liegt also bei 2/3. Daran ändert auch die Tatsache, dass eine der beiden anderen Türen geöffnet wurde, nichts. Folglich wäre es für den Kandidaten besser, die Tür zu wechseln, denn damit erhöht er seine Gewinnchance von 1/3 auf 2/3.
Simulationsprogramm
Wer das so nicht glauben kann, der möge sich mal das folgende Simulationsprogramm ansehen, kompilieren (dazu werden die QT-Bibliotheken benötigt) und ausführen. Das Programm basiert auf Threads, kann also die Rechenleistung von Multicore-Prozessoren voll ausnutzen. Es besteht im Wesentlich aus zwei Klassen: einer, in der die eigentliche Simulation implementiert ist und einer, die die Simulation steuert.
Zuerst ein paar Worte zur Simulations-Klasse: In dieser Klasse wurde in der run()-Methode die eigentliche Simulation implementiert. Diese benötigt an verschiedenen Stellen Zufallszahlen. Diese werden durch einen linearen Kongruenzgenerator "errechnet", da der in der cstdlib-Bibliothek implementierte Zufallszahlengenerator nicht so effizient ist. Im Prinzip sind hier sogar drei Generatoren implementiert: einer für das Auto, einer für den Kandidaten und einer für den Moderator. Würde alles über einen Generator laufen, so wären die Zufallszahlen innerhalb einer Spielrunde voneinander abhängig, was das Ergebnis etwas verfälschen würde.
- #include <iostream>
- #include <cstdlib>
- #include <QThread>
- class Simulation : public QThread {
- private:
- // Konstanten fuer den Zufallszahlengenerator
- static const unsigned long MASK = (1L << 48) - 1;
- static const unsigned long ADD = 0xBL;
- static const unsigned long MULTIPLIER = 0x5DEECE66DL;
- // Hier werden die letzten Zufallszahln gespeichert
- unsigned long m_random[3];
- // Durchlaufzaehler
- int m_counter;
- // mit oder ohne Wechseln
- bool m_shift;
- // Zeiger auf den Gewinnzaehler
- int *m_wins;
- public:
- Simulation(int counter, bool shift, int *wins) {
- m_counter = counter;
- m_shift = shift;
- m_wins = wins;
- // Drei unabhaengige Zufallszahlengeneratoren initialisieren
- for (int i=0;i<3;i++) {
- m_random[i] = rand();
- }
- }
- protected:
- void run() {
- // So oft hat der Kandidat in diesem Thread schon gewonnen
- int wins = 0;
- while (m_counter > 0) {
- // Das Auto wird versteckt
- int car = getRandom(3,0);
- // Der Kandidat entscheidet sich fuer eine Tuer
- int kandidat = getRandom(3,1);
- // Diese Tueren darf der Moderator nicht oeffnen
- bool blocked[3];
- for (int i=0;i<3;i++) {
- blocked[i] = false;
- }
- // Naemlich die mit dem Auto
- blocked[car] = true;
- // und die mit dem Kandidaten
- blocked[kandidat] = true;
- // Jetzt sucht er sich zufaellig Tueren raus, bis
- // eine nicht blockiert ist, und oeffnet diese
- int opened;
- do {
- opened = getRandom(3,2);
- } while (blocked[opened]);
- // Je nachdem, was simuliert wird: Wechseln oder nicht.
- if (m_shift) {
- // Das ist genau die andere verschlossene Tuer
- kandidat = 3 - (kandidat + opened);
- }
- // Gewinnt der Kandidat das Auto?
- if (car == kandidat) {
- wins++;
- }
- // Durchlauf vorbei: Zaehler eins runterzaehlen
- m_counter--;
- }
- // Zum Gesamtergebnis dazuaddieren
- (*m_wins) += wins;
- }
- private:
- int getRandom(long n, int i) {
- // linearer Kongruenzgenerator
- m_random[i] = (MULTIPLIER*m_random[i]+ADD)%MASK;
- return static_cast<int>(m_random[i]%n);
- }
- };
Die andere Klasse steuert den Ablauf der Simulation. Im Wesentlichen besteht der Sinn dieser Klasse darin, mehrere Threads zu starten, zu warten, bis diese mit der Simulation fertig sind und zum Schluss das Ergebnis auszugeben. Etwas verwirrend ist vielleicht, dass theThreads.at(i)->start() die run()-Methode der Simulationsklasse aufruft, aber das ist bei der Threadprogrammierung halt so.
- #include <vector>
- #include <iostream>
- #include "Simulation.hpp"
- class LetsMakeADeal {
- private:
- // So viele Threads sollen gleichzeitig laufen
- int m_threads;
- public:
- LetsMakeADeal(int threads) {
- m_threads = threads;
- // Titel ausgeben
- std::cout << std::endl;
- std::cout << "Simulation der TV-Show Let's make a deal: "
- << "Das Ziegenproblem" << std::endl;
- std::cout << std::endl;
- }
- void tvShow(int runs, bool shift) {
- // Gewinne zaehlen
- int wins = 0;
- // Hier werden die verschiedenen Threads gespeichert
- std::vector<Simulation*> theThreads;
- // Der erste Thread faengt den Fehler der Integerdivision ab
- int counter = runs / m_threads + runs % m_threads;
- theThreads.push_back(new Simulation(counter,shift,&wins));
- // Der Rest wird auf die anderen Threads aufgeteilt
- counter = runs / m_threads;
- for (int i=1;i<m_threads;i++) {
- theThreads.push_back(new Simulation(counter,shift,&wins));
- }
- // Threads starten
- for (int i=0;i<m_threads;i++) {
- theThreads.at(i)->start();
- }
- // Bis alle Threads fertig sind warten
- for (int i=0;i<m_threads;i++) {
- theThreads.at(i)->wait();
- }
- // Das Ergebnis der Simulation ausgeben
- std::cout << "Simulation " << (shift ? "mit" : "ohne")
- << " Wechsel der Tuer." << std::endl;
- std::cout << wins << " Gewinne in " << runs
- << " Runden." << std::endl;
- std::cout << "Anteil der Gewinne: "
- << ((double)wins)/((double)runs) << std::endl;
- std::cout << std::endl;
- }
- };
Zur main()-Methode gibt es nicht viel zu sagen. Hier werden die für die Simulation relevanten Konstanten festgelegt und die Simulation einmal mit einem "Wechsel-Kandidaten" und einmal mit einem "nicht-Wechsel-Kandidaten" gestartet.
- #include "LetsMakeADeal.hpp"
- int main() {
- // So oft soll simuliert werden
- const int RUNS = 2147483647;
- // So viele Threads sollen benutzt werden
- const int THREADS = 2;
- // Zufallszahlengenerator initialisieren
- srand((unsigned)time(NULL));
- // Simuliere mit Threads
- LetsMakeADeal* theShow = new LetsMakeADeal(THREADS);
- // Zuerst mit Wechseln
- theShow->tvShow(RUNS,true);
- // dann ohne Wechseln
- theShow->tvShow(RUNS,false);
- return 0;
- }
Ich habe das Programm mit diesen Konstanten auf meinem Rechner (CPU: AMD Turion™ 64 X2 TL-50 @ 1,6 GHz (Dual-Core)) durchlaufen lassen und habe folgendes Ergebnis bekommen:
Simulation der TV-Show Let's make a deal: Das Ziegenproblem Simulation mit Wechsel der Tuer. 1431362079 Gewinne in 2147483647 Runden. Anteil der Gewinne: 0.66653 Simulation ohne Wechsel der Tuer. 715745454 Gewinne in 2147483647 Runden. Anteil der Gewinne: 0.333295
Das ganze hat 2:38 Minuten bei voller Prozessorauslastung gedauert. Danach habe ich das Programm nochmal mit nur einem Thread, also auch nur auf einem Prozessor, gestartet. Nach 5:12 Minuten hatte ich ein ähnliches Ergebnis. Die Programmierung mit Threads hat also in diesem Fall die Laufzeit auf die Hälfte verkürzt.
Zum Abschluss habe ich das Programm dann noch mit acht Threads auf einem Server (CPU: Intel® Xeon® E5420 @ 2,5 GHz (Dual-Quad-Core)) laufen lassen. Dieser war schon nach 30 Sekunden mit der Simulation fertig.