zoukankan      html  css  js  c++  java
  • [总结]随机抽样与蓄水池抽样问题

    现实中碰到很多需要随机抽样的问题,这也是算法工程师面试中常见的题型,特意记录在这里。下面以几个例题为例,展开随机抽样问题的解决方案。

    [leetcode]470.Implement Rand10() Using Rand7()

    已提供一个Rand7()的API可以随机生成1到7的数字,使用Rand7实现Rand10,Rand10可以随机生成1到10的数字。可以通过拒绝采样的方法来计算:用Rand49生成一个数,如果它位于41-49,则丢弃,继续生成,当生成一个1-40的数时返回。这样子做可以近似看成随机生成1-40之间的数。

    class Solution:
        def rand10(self):
            """
            :rtype: int
            """
            while True:
                val = (rand7() - 1) * 7 + rand7()  #减一是为了从0开始
                if(val <= 40):
                    break
            return (val-1) % 10 + 1
    
    [leetcode]478.Generate Random Point in a Circle

    拒绝采样(Rejection Sampling)。对于圆中任意小的面积内落入点的概率相等。注意刚才说的是任意面积落点的概率是相等的。而如果采用随机半径+随机角度的方式,那么在任意半径上落入点的概率相等。很明显的是靠近圆心的半径比较密,远离圆心的时候半径比较稀疏。

    class Solution:
        def __init__(self, radius: float, x_center: float, y_center: float):
            self.r = radius
            self.x = x_center
            self.y = y_center
    
        def randPoint(self) -> List[float]:
            nr = math.sqrt(random.random()) * self.r
            alpha = random.random() * 2 * 3.141592653
            newx = self.x + nr * math.cos(alpha)
            newy = self.y + nr * math.sin(alpha)
            return [newx, newy]
    
    [leetcode]519.Random Flip Matrix # Fisher-Yates Shuffle算法,利用dict字典映射 取前20个数字?

    给定一个全零矩阵的行和列,实现flip函数随机把一个0变成1并返回索引,实现rest函数将所有数归零。
    使用Fisher-Yates Shuffle算法,Fisher-Yates洗牌算法是用来打乱一个随机序列的算法,主要步骤为:在0到n(索引)之间生成一个数m,交换m和n(索引对应的数),n(索引)减掉1,循环这三步,直到n等于0。主要思想就是每次采样(索引)时,当前随机采样到的数(索引对应的数)交换到最后一个数(末尾索引对应的数),然后采样池数量减一(末尾索引减一),然后继续采样和交换(不断迭代),直到采样池为空。

    import random
    class Solution(object):
        def __init__(self, n_rows, n_cols):
            self.n_rows, self.n_cols = n_rows, n_cols
            self.reset()
    
        def flip(self):
            self.n -= 1
            i = random.randrange(0, self.n+1)
            index = self.dic.get(i, i)
            self.dic[i] = self.dic.get(self.n, self.n)
            return [index // self.n_cols, index % self.n_cols]
    
        def reset(self):
            self.n = self.n_rows * self.n_cols
            self.dic = {}
    

    在这个题中不能直接使用数组进行这个过程的模拟,内存不够。所以,使用一个字典保存已经被随机数选择过的位置,把这个位置和末尾的total交换的实现方式是使用字典保存这个位置交换成了末尾的那个数字。每次随机到一个数字,然后在字典中查,如果这个数字不在字典中,表示这个数字还没被选中过,那么就直接返回这个数字,把这个数字和末尾数字交换;如果随机数已经在字典中出现过,那么说明这个位置已经被选中过,使用字典里保存的交换后的数字返回。

    [leetcode]528.Random Pick with Weight

    把概率分布函数转化为累计概率分布函数。然后通过随机数,进行二分查找。
    比如,输入是[1,2,3,4],那么概率分布是[1/10, 2/10, 3/10, 4/10, 5/10],累积概率分布是[1/10, 3/10, 6/10, 10/10].总和是10。如果我们产生一个随机数,在1~10之中,然后判断这个数字在哪个区间中就能得到对应的索引。
    对于输入[1,2,3,4],计算出来的preSum是[1,3,6,10],然后随机选一个s,然后查找s属于哪个区间。

    import random
    class Solution:
        def __init__(self, w: List[int]):
            self.cursum = [0]
            for weight in w:
                self.cursum.append(self.cursum[-1]+weight)
    
        def pickIndex(self) -> int:
            r = random.randrange(0,self.cursum[-1])
            # r = random.random()*self.cursum[-1]
            return self.gethi(self.cursum,r)
    
        def gethi(self,nums,target):
            lo,hi = 0,len(nums)-1
            while lo<=hi:
                mid = lo + ((hi-lo)>>1)
                if target < nums[mid]:
                    hi = mid-1
                else:
                    lo = mid+1
            return hi
    
    [leetcode]382.Linked List Random Node

    对于数量居多无法实现内存加载、值从流中输入长度未知的情况,我们无法做到先统计数量再使用随机函数实现,所以就会用到蓄水池算法。由于限定了head一定存在,所以我们先让返回值res等于head的节点值,然后让cur指向head的下一个节点,定义一个变量i,初始化为1,若cur不为空我们开始循环,我们在[0, i - 1]中取一个随机数,如果取出来0,那么我们更新res为当前的cur的节点值,然后此时i自增一,cur指向其下一个位置,这里其实相当于我们维护了一个大小为1的水塘,然后我们随机数生成为0的话,我们交换水塘中的值和当前遍历到的值,这样可以保证每个数字的概率相等

    import random
    class Solution:
    
        def __init__(self, head: ListNode):
            """
            @param head The linked list's head.
            Note that the head is guaranteed to be not null, so it contains at least one node.
            """
            self.head = head
    
        def getRandom(self) -> int:
            """
            Returns a random node's value.
            """
            res = self.head.val
    
            i = 1
            cur = self.head.next
            while cur:
                j = random.randint(0,i)
                if j== 0:
                    res = cur.val
                i+=1
                cur = cur.next
            return res
    
    [leetcode]384.Shuffle an Array

    每次往后读取数组的时候,当读到第i个的时候以1/i的概率随机替换1~i中的任何一个数,这样保证最后每个数字出现在每个位置上的概率都是相等的。
    证明:
    (x)元素在第(m)次的时候出现在位置(i)的概率是(1/m),那么在第(m+1)次的时候,(x)仍然待在位置(i)的概率是 (1/m * m/(m+1) = 1/(m+1))

    import random
    class Solution:
    
        def __init__(self, nums: List[int]):
            self.nums = nums
            self.shuffle_nums = nums[:]
    
    
        def reset(self) -> List[int]:
            """
            Resets the array to its original configuration and return it.
            """
            return self.nums
    
    
        def shuffle(self) -> List[int]:
            """
            Returns a random shuffling of the array.
            """
            for i in range(len(self.shuffle_nums)):
                j = random.randint(0,i)
                self.shuffle_nums[i],self.shuffle_nums[j] = self.shuffle_nums[j], 
                self.shuffle_nums[i]
            return self.shuffle_nums
    
    [leetcode]497.Random Point in Non-overlapping Rectangles

    根据面积作为权重,按概率选到长方形。之后在这个长方形的范围内随机选x和y,输出。

    class Solution:
        def __init__(self, rects: List[List[int]]):
            self.rects = rects
            self.weights = []
            for x1,y1,x2,y2 in self.rects:
                self.weights.append((x2 - x1 + 1) * (y2 - y1 + 1))
    
    
        def pick(self) -> List[int]:
            x1,y1,x2,y2 = random.choices(
                self.rects, weights=self.weights)[0]
            res = [
                random.randrange(x1, x2 + 1),
                random.randrange(y1, y2 + 1)
            ]
            return res
    

    参考:
    470.Implement Rand10() Using Rand7() (拒绝采样Reject Sampling)
    519.Random Flip Matrix(Fisher-Yates洗牌算法)

  • 相关阅读:
    AJAX异步传输——以php文件传输为例
    js控制json生成菜单——自制菜单(一)
    vs2010中关于HTML控件与服务器控件分别和js函数混合使用的问题
    SQL数据库连接到服务器出错——无法连接到XXX
    PHP错误:Namespace declaration statement has to be the very first statement in the script
    【LeetCode】19. Remove Nth Node From End of List
    【LeetCode】14. Longest Common Prefix
    【LeetCode】38. Count and Say
    【LeetCode】242. Valid Anagram
    【LeetCode】387. First Unique Character in a String
  • 原文地址:https://www.cnblogs.com/hellojamest/p/11706708.html
Copyright © 2011-2022 走看看