Zpět na domů

Algoritmy hledání čísla: od Knutha do MCMC

Článek analyzuje tři algoritmy pro hru hádání čísla: maximin Knutha s optimálním počtem pokusů, náhodné hledání s O(1) složitostí na krok a MCMC na bázi Metropole-Hastings. Uvádí se kód v Pythonu, matematický důkaz a experimentální výsledky pro N=100.

Hádání čísla: Knuth vs MCMC vs Náhodný
Advertisement 728x90

Optimalizace strategií hádání čísla: Knuthův maximin, náhodné vyhledávání a MCMC

V hře hádání čísla musí hráč určit skryté číslo z rozsahu [1, N] pomocí dotazů a odpovědí typu „větší“ nebo „menší“. Cílem je minimalizovat počet pokusů při co nejnižší výpočetní náročnosti každého kroku. Probereme tři přístupy: Knuthův maximin, náhodné vyhledávání a MCMC.

Maximin dosahuje teoretického optima ⌈log₂N⌉ pokusů, ale vyžaduje O(N²) operací na tah. Náhodný výběr snižuje složitost na O(1), avšak střední počet pokusů vzroste 1,71×. MCMC nabízí kompromis mezi rychlostí a přesností prostřednictvím stochastického procházení.

Knuthova strategie maximinu

Algoritmus maximinu udržuje množinu S možných hodnot a vybírá dotaz, který maximalizuje zaručené zmenšení S bez ohledu na odpověď.

Google AdInline article slot
  • Inicializovat S = {1, ..., N}.
  • Pro každé a ∈ all_hiddens vypočítat min(|{c ∈ S | check(a,c) = r}| pro r ∈ {'>', '<'}).
  • Vybrat a s nejvyšší hodnotou min.
  • Aktualizovat S podle odpovědi.

Implementace ukazuje optimální výkon: pro N = 1024 stačí 10 pokusů.

from copy import deepcopy

def knut_maxmin(check, all_hiddens, results, cur_hiddens):
    """Strategie maximinu pro výběr optimálního tahu."""
    return max(
        [(a, min(
            [sum(1 for c in cur_hiddens if check(a, c) != r)
             for r in results]
        )) for a in all_hiddens],
        key=lambda p: p[1]
    )[0]

def play_game(N):
    hiddens = list(range(1, N + 1))
    results = ['>', '<']

    def check(guess, hidden):
        if guess == hidden:
            return True
        return '>' if hidden > guess else '<'

    cur = list(hiddens)
    step = 0
    while len(cur) > 1:
        step += 1
        guess = knut_maxmin(check, cur, results, cur)
        answer = check(guess, SECRET)  # SECRET — skryté číslo
        if answer is True:
            return guess, step
        cur = [c for c in cur if check(guess, c) == answer]
    return cur[0], step + 1

Omezení: O(N²) na krok činí metodu nepoužitelnou pro N > 10⁵.

Náhodné vyhledávání v intervalu

Tento přístup pracuje s binárním intervalem [lo, hi] místo úplné množiny S. Každý dotaz je náhodné celé číslo z rozsahu [lo, hi], hranice se pak aktualizují.

Google AdInline article slot
import random

def play_random(N, secret):
    lo, hi = 1, N
    steps = 0
    while lo < hi:
        steps += 1
        guess = random.randint(lo, hi)
        if guess == secret:
            return steps
        elif secret > guess:
            lo = guess + 1
        else:
            hi = guess - 1
    return steps + 1

Analýza: při výběru x ~ Uniform[0,L] je střední hodnota relativní délky zbytku ∫₀¹ (x² + (1−x)²) dx = 2/3. Počet pokusů k ≈ (ln N) / ln(3/2) ≈ 1,71 log₂N. Pro N = 1024 to znamená průměrně ~17 kroků oproti 10 u optimálního řešení — ale jen s konstantní výpočetní náročností O(1) na tah.

Výhody:

  • Konstantní časová složitost.
  • Asymptoticky O(log N) pokusů.
  • Žádné ukládání množiny S.

MCMC s Metropolisem–Hastingsem

Metoda simuluje Markovův řetězec konvergující k distribuci π(x) ∝ exp(−β · violations(x)), kde violations(x) udává počet rozporů s historií dotazů a odpovědí [(guess_i, r_i)].

Google AdInline article slot

Parametry: σ pro Gaussovo šumování, β jako „teplota“, n_iter počet iterací.

import random
import math

def mcmc_guess(N, history, n_iter=500, sigma=None, beta=10.0):
    """Metropolisův–Hastingsov algoritmus pro hádání čísla."""
    if sigma is None:
        sigma = N / 4

    def violations(x):
        count = 0
        for guess, response in history:
            if response == '>' and x <= guess:
                count += 1
            elif response == '<' and x >= guess:
                count += 1
        return count

    def target(x):
        return math.exp(-beta * violations(x))

    # Počáteční stav
    x = random.randint(1, N)

    for _ in range(n_iter):
        # Návrh nového stavu
        x_prime = x + int(random.gauss(0, sigma))
        x_prime = max(1, min(N, x_prime))

        # Přijetí nebo odmítnutí
        acceptance = target(x_prime) / max(target(x), 1e-300)
        if random.random() < min(1.0, acceptance):
            x = x_prime

    return x


def play_mcmc(N, secret):
    history = []
    for step in range(1, 10 * N):
        guess = mcmc_guess(N, history)
        if guess == secret:
            return step
        response = '>' if secret > guess else '<'
        history.append((guess, response))
    return None  # Nepodařilo se uhodnout

Experimenty (N = 100, 100 opakování): průměr 8,3 kroku, medián 8, maximum 17. MCMC se adaptuje na historii dotazů; doporučené hyperparametry jsou σ = N/4 a β = 10.

Klíčové body

  • Knuthův maximin: teoretické optimum ⌈log₂N⌉ kroků, ale O(N²) na tah — vhodné pro malá N.
  • Náhodné vyhledávání: 1,71× více kroků než optimum, ale O(1) výpočtu — ideální pro velká N.
  • MCMC: ~log N kroků s laděním, konverguje k kompatibilním hodnotám x během n_iter iterací.
  • Výběr strategie závisí na velikosti N a časových omezeních na jeden tah.
  • Všechny strategie po prvních krocích zachovávají intervalovou strukturu možných hodnot.

— Editorial Team

Advertisement 728x90

Číst dál