Mischen als stochastischer Prozess

3 Minuten zum Lesen

Jede Kartenspielerin weiß, dass das Kartenglück launisch ist. An manchen Abenden scheint es wie verhext: Es will sich einfach kein gutes Blatt einstellen. Nach der dritten Runde mit nur wenigen Trümpfen auf der Hand fragt man sich dann vielleicht, ob es daran liegt, dass der Geber sich keine Mühe beim Mischen gegeben hat? Die Theorie zu stochastischen Prozessen liefert hierzu eine erstaunliche Aussage: Nach einer bestimmten Anzahl von Mischschritten kann man davon ausgehen, dass der Kartenstapel mit Karten sehr gut gemischt ist.1 Wird die Zahl von Mischschritten aber unterschritten, bleibt der Kartenstapel sehr schlecht gemischt. ist dabei von der Ordnung .

Um das zu erläutern, betrachte man das Mischverfahren Riffle Shuffle: Der Kartenstapel wird in der Mitte geteilt und dann wieder zufällig zusammengemischt. In unserem Modell wir die jeweils nächste Karte auf dem Zielstapel zufällig entweder aus dem ersten oder aus dem zweiten Teilstapel gezogen. Das idealisiert den Vorgang, wie er beim Mischen von Hand stattfindet, denn dort ist nicht wirklich gegeben, dass die Auswahl der Quelle einer Karte jeweils unabhängig ist von der vorhergehenden Auswahl. Dennoch dürfte das Ergebnis sehr ähnlich ausfallen.

Die Funktion splitandmix! verwendet zwei Iteratoren, von denen einer die erste Hälfte des ursprünglichen Stapels durchläuft und der andere die zweite Hälfte. Es werden solange Karten vom ersten Iterator übernommen, bis eine Zufallszahl zum ersten Mal größer als ist. Dann wechseln die Iteratoren. Falls schließlich einer der Iteratoren erschöpft ist, werden die restlichen Karten vom anderen Iterator übernommen.

	function splitandmix!(dest::T, source::T) where T<:Vector{Int}
		p = lastindex(source)
		hp = div(p, 2)
		itcurrent = Iterators.Stateful(source[1:hp])
		itother = Iterators.Stateful(source[hp+1:p])
		i = firstindex(dest)
		while !isempty(itcurrent)
			rand() < 0.5 || begin itcurrent, itother = itother, itcurrent end
			dest[i] = popfirst!(itcurrent)
			i += 1
		end
		itcurrent = itother
		while i <= lastindex(dest)
			dest[i] = popfirst!(itcurrent)
			i += 1
		end
		dest
	end

Ein Objekt der Struktur Pile enthält einen Vektor mit den Positionen der Karten. Durch die Definition von iterate wird aus einem Pile ein iterierbares Objekt.

	mutable struct Pile
        pilesize::Int # number of cards
        shufflesteps::Int # steps in a round of shuffling
        vpos::Vector{Int} # positions of the cards (state)
	end
	
	function Pile(pilesize::Int, shufflesteps::Int)
		vpos = collect(1:pilesize)
		Pile(pilesize, shufflesteps, vpos)
	end

	let vposlast = Int[]
		global function iterate(p::Pile)
			p.vpos = collect(1:p.pilesize)
			vposlast = p.vpos
			return(p, 1)
		end

		global function iterate(p::Pile, step)
			splitandmix!(vposlast, p.vpos)
			p.vpos, vposlast = vposlast, p.vpos
			if step >= p.shufflesteps
				return nothing
			else
				return(p, step + 1)
			end
		end
	end

Die Funktion sample legt ein dreidimensionales Array an und zählt, wie häufig eine Karte an den verschiedenen Positionen nach jedem Schritt des Mischverfahrens angetroffen wird.

	function sample(pile::Pile, ncycles::Int, nmsg = 12)
		pilesize = pile.pilesize
		field = zeros(Int, pilesize, pile.shufflesteps, pilesize)
		nmsgstep = div(ncycles, nmsg)
		steptomsg = nmsgstep

		@inbounds for cycle in 1:ncycles
			for (s, p) in enumerate(pile)
				for (pos, card) in zip(sortperm(p.vpos), 1:pilesize)
					field[pos, s, card] += 1
				end
			end
			if steptomsg == 0
				println("cycle: ", cycle)
				steptomsg = nmsgstep
			end
			steptomsg -= 1
		end
		field
	end

Die folgenden Abbildungen zeigen, wie sich die Positionen von Karten aus einem Stapel von 32 Karten verändern. Es ist die Wahrscheinlichkeit abgebildet, eine Karte an einer bestimmten Position zu finden. Im ersten Schritt, bevor das Mischen begonnen hat, trifft man die Karte immer an der Position, die ihrer Nummer entspricht, d.h. Karte 1 an Position 1, Karte 15 an Position 15, und so weiter. Schon nach zweimaligem Mischen, vor Schritt drei sind die Karten beträchtlich gewandert, vor allem, wenn ihre Ausgangsposition am Ende (Karte 32) oder in der Mitte (Karte 15) war. Bei Schritt neun sind die Karten an jeder der Positionen gleich häufig anzutreffen:

Position der Karte 1

Position der Karte 15

Position der Karte 32

Der Abstand von der Gleichverteilung über alle Karten dient als Maß für den Erfolg des Mischvorgangs. Das Diagramm zeigt, wie sich dieser Abstand im Laufe des Mischvorgangs verändert:

Fortschritt beim Mischen

Man erkennt deutlich, dass das Mischen schon nach wenigen Schritten zum gewünschten Erfolg führt. Der Stapel mit 32 Karten ist schon nach fünf Schritten sehr gut gemischt, danach lässt sich keine Verbesserung mehr erzielen. Vergrößert man den Stapel, erhöht sich die Zahl der benötigten Schritte zum Mischen. Die Zahl der Karten wurde von Stapel zu Stapel um den Faktor 6,25 vervielfacht. Die Verschiebung der Kurve von Stapel 2 zu Stapel 1 ist aber in etwa gleich groß wie die Verschiebung der Kurve von Stapel 3 zu Stapel 2. Damit bestätigt der Versuch die Vorhersage der Theorie, dass die Zahl der benötigten Schritte propotional ist zum Logarithmus der Zahl der Karten.

	julia> VERSION
	v"1.1.0"

Startseite

  1. Michael Scheutzow: Stochastische Modelle. Vorlesungsskript. TU Berlin 2017.