基本概率与条件概率

package main

import (
    "fmt"
    "math/rand"
)

// 蒙特卡洛估算 π
// 原理:单位圆面积 = π,单位正方形面积 = 4,比值 = π/4
func estimatePi(n int) float64 {
    inside := 0
    for i := 0; i < n; i++ {
        x := rand.Float64()*2 - 1
        y := rand.Float64()*2 - 1
        if x*x+y*y <= 1 {
            inside++
        }
    }
    return 4 * float64(inside) / float64(n)
}

// 模拟掷骰子,验证大数定律
func diceMean(n int) float64 {
    sum := 0
    for i := 0; i < n; i++ {
        sum += rand.Intn(6) + 1
    }
    return float64(sum) / float64(n)
}

func main() {
    fmt.Printf("π ≈ %.5f (理论 3.14159)\n", estimatePi(1_000_000))

    // 大数定律验证:骰子期望 = 3.5
    for _, n := range []int{10, 100, 10000, 1000000} {
        fmt.Printf("n=%7d: 均值=%.4f\n", n, diceMean(n))
    }
}

生日悖论:碰撞比你想的早

「23 个人中有两个人生日相同的概率 > 50%」——这就是生日悖论。直觉上应该需要 183 人,但数学告诉我们只需要 23 人。原因:碰撞发生在任意两人之间,而不是你和某个特定人之间。这直接解释了为什么哈希表在装载率 70% 时冲突率就已经很显著了。

package main

import (
    "fmt"
    "math"
)

// 计算 k 个人中至少两人生日相同的概率
func birthdayCollisionProb(k, n int) float64 {
    // 所有人生日不同的概率
    pNone := 1.0
    for i := 0; i < k; i++ {
        pNone *= float64(n-i) / float64(n)
    }
    return 1 - pNone
}

// 哈希表碰撞:m 个槽,n 个键,碰撞概率
func hashCollisionProb(m, n int) float64 {
    return birthdayCollisionProb(n, m)
}

func main() {
    // 生日悖论:365 天,多少人时概率超 50%
    for k := 1; k <= 30; k++ {
        p := birthdayCollisionProb(k, 365)
        if p > 0.5 {
            fmt.Printf("%d 个人时概率首次超 50%%: %.1f%%\n", k, p*100)
            break
        }
    }

    // 哈希表:1024 个槽,放多少个键时碰撞概率超 10%?
    for n := 1; n <= 1024; n++ {
        if hashCollisionProb(1024, n) > 0.1 {
            fmt.Printf("槽=1024,键=%d 时碰撞率>10%%\n", n)
            break
        }
    }

    // 近似公式:n ≈ sqrt(2m * ln(1/(1-p)))
    approx := math.Sqrt(2 * 1024 * math.Log(1/(1-0.1)))
    fmt.Printf("近似公式结果: %.0f\n", approx)
}

期望与方差:量化随机

package main

import (
    "fmt"
    "math"
)

// 离散随机变量的期望和方差
func expectation(vals []float64, probs []float64) float64 {
    e := 0.0
    for i, v := range vals {
        e += v * probs[i]
    }
    return e
}

func variance(vals []float64, probs []float64) float64 {
    e := expectation(vals, probs)
    v := 0.0
    for i, x := range vals {
        d := x - e
        v += d * d * probs[i]
    }
    return v
}

func main() {
    // 骰子
    vals := []float64{1, 2, 3, 4, 5, 6}
    probs := []float64{1.0 / 6, 1.0 / 6, 1.0 / 6, 1.0 / 6, 1.0 / 6, 1.0 / 6}
    e := expectation(vals, probs)
    v := variance(vals, probs)
    fmt.Printf("期望=%.2f 方差=%.4f 标准差=%.4f\n", e, v, math.Sqrt(v))
    // 期望=3.50 方差=2.9167 标准差=1.7078

    // 缓存命中期望:命中率 0.8,单次查询期望 IO 次数
    // 命中:0 次磁盘 IO,未命中:1 次磁盘 IO
    hitRate := 0.8
    expectedIO := 0*hitRate + 1*(1-hitRate)
    fmt.Printf("命中率 80%% 时,每次查询期望 IO: %.1f 次\n", expectedIO)
}

贝叶斯定理:更新信念

贝叶斯定理 P(A|B) = P(B|A)×P(A) / P(B) 描述了如何用新证据更新概率估计。在垃圾邮件过滤、异常检测、医疗诊断中大量使用。==先验 × 似然 = 后验==(差一个归一化常数)。

推荐做法
  • 哈希表负载因子控制在 0.7 以下(碰撞概率急剧上升前)
  • A/B 测试前先算所需样本量,而不是靠感觉
  • 蒙特卡洛方法适用于高维积分、期权定价等解析解难求的场景
不推荐
  • 把概率当百分比随意计算——注意独立性假设是否成立
  • 忽视小概率大影响事件——期望值不是全部,方差也很重要
常见误区
  • 条件概率 P(A|B) ≠ P(B|A)——混淆两者叫做「检察官谬误」

判断标准:能计算哈希表碰撞概率并解释为什么负载因子不能太高 → 掌握本章。

概率论是量化无知的科学——它不消除不确定性,它让你诚实地面对它。

— E.T. Jaynes《概率论:科学的逻辑》