Willkommen auf www.sudokurechner.de.vu

Linkweg: Home / Matherätsel / Das Ziegenproblem / 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.

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.

C++/QT-Code
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
#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.

C++/QT-Code
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
#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.

C++/QT-Code
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
#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.