适用情况

当大量数据位于数据流中,无法一次性读取进内存,未知数据的大小,从中抽取k个数,并且要保证每次抽取的概率相等,这个时候,就适用水塘抽样算法

算法详解

当k=1时

当k=1时,即只抽取一个数

算法:每次只保留一个数,当遇到第 i 个数时,以 $\frac{1}{i} $的概率保留它,$\frac{(i-1)}{i} $的概率保留原来的数。

对于数字1~4,依次读取之,有:

  • 读取1时,概率为1,保留1
  • 读取2时

    • 保留2的概率概率为$\frac{1}{2} $
    • 保留1的概率为$\frac{2-1}{2} =\frac{1}{2} $
  • 读取3时

    • 保留3的概率为$\frac{1}{3} $
    • 保留2的概率:读取2时保留2,本轮保留2,即$\frac{1}{2} \times \frac{3-1}{3} =\frac{1}{3} $
    • 保留1的概率:读取2时保留1,本轮保留1,即$\frac{1}{2} \times \frac{3-1}{3} =\frac{1}{3} $
  • 读取4时

    • 保留4的概率为$\frac{1}{4} $
    • 保留3的概率:读取3时保留3,本轮保留3,即$\frac{1}{3} \times \frac{4-1}{4} =\frac{1}{4} $
    • 保留2的概率:读取3时保留2,读取2时保留2,本轮保留2,即$\frac{1}{2} \times \frac{3-1}{3} \times \frac{4-1}{4} =\frac{1}{4} $
    • 保留1的概率:读取3时保留1,读取2时保留1,本轮保留1,即$\frac{1}{2} \times \frac{3-1}{3} \times \frac{4-1}{4} =\frac{1}{4} $

由此可见,每次对先前的数和当前的数的保留概率均相等,满足条件

证明可用数学归纳法,本文略

当k=m时

当k=m时,即抽取m个数,流程与k=1时相似,只需要将每次的概率都乘上m即可

Leetcode 382.链表随机节点

题目描述

给你一个单链表,随机选择链表的一个节点,并返回相应的节点值。每个节点 被选中的概率一样 。

实现 Solution 类:

Solution(ListNode head) 使用整数数组初始化对象。

int getRandom() 从链表中随机选择一个节点并返回该节点的值。链表中所有节点被选中的概率相等。

题解

方法1——使用数组保存节点

保存所有节点到数组,随机抽取

空间复杂度:$O(n)$

时间复杂度:初始化为$O(n)$,随机取数为$O(1)$

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */

type Solution struct {
    Arr []int
}

func Constructor(head *ListNode) Solution {
    s := Solution{Arr: make([]int, 0)}
    for head != nil {
        s.Arr = append(s.Arr, head.Val)
        head = head.Next
    }
    return s
}

func (this *Solution) GetRandom() int {
    return this.Arr[rand.Intn(len(this.Arr))]
}

/**
 * Your Solution object will be instantiated and called as such:
 * obj := Constructor(head);
 * param_1 := obj.GetRandom();
 */

结果:

执行用时:12 ms, 在所有 Go 提交中击败了98.25%的用户

内存消耗:7.3 MB, 在所有 Go 提交中击败了9.65%的用户

方法2——水塘抽样算法

使用水塘抽样算法

空间复杂度:$O(1)$

时间复杂度:初始化为$O(1)$,随机取数为$O(n)$

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
type Solution struct {
    Head *ListNode
}

func Constructor(head *ListNode) Solution {
    s := Solution{Head: head}
    return s
}

func (this *Solution) GetRandom() int {
    node, i := this.Head, 1
    res := node.Val
    for node != nil {
        //从[0,i)取数,取到0的概率为1/i
        if rand.Intn(i) == 0 {
            res = node.Val
        }
        node = node.Next
        i++
    }
    return res
}

/**
 * Your Solution object will be instantiated and called as such:
 * obj := Constructor(head);
 * param_1 := obj.GetRandom();
 */

结果:

执行用时:16 ms, 在所有 Go 提交中击败了74.56%的用户

内存消耗:7.2 MB, 在所有 Go 提交中击败了40.35%的用户

最后修改:2022 年 01 月 16 日
如果觉得我的文章对你有用,请随意赞赏