从 fair coin 说起
这是一个经典老题了,如何用 fair coins 来构造一个 biased coin,以及如何用 fair coins 来构造一个 discrete uniform distribution。基于这两个问题,我又 yy 了另外几个小 follow-up,试图去分析其时间复杂度。
Case 1:
先来一个 warm-up exercise:在只有 fair coins (即 )的情况下,给定任意一个 ,如何从 中 sample 一个元素?
这个很好做:我们只要 sample 之间的一个实数 ,并和 比较就好了。我们考虑逐位 sample 并比较:每次得到 的二进制表示第 位,,如果 ,则我们就可以得到 和 的相对大小关系了,否则继续下一位。
def sample(p):
while True:
x = random.randint(2)
p_i = int(p * 2)
if x != p_i:
return int(x < p_i)
p = p * 2 - p_i
follow-up: 时间复杂度
上面这个算法有最坏时间复杂度保证吗?
显然没有。
那么存在一个最坏时间复杂度保证的算法吗?
对于几乎所有的 都不存在。假设存在一个算法能保证 步内结束,那么我们考虑用到的所有 个 random bit,一共有 种组合。对于每种组合我们一定会返回一个 0 或者 1,所以算法一定是从 中 sample,其中 为整数。而几乎所有的 都不能表示成 这样的形式。
那上面这个算法的期望复杂度是多少?
显然,每一步有 的概率直接结束,所以期望就是 2 步结束。挺意外的一个结果,和 无关。
Case 2:
令 ,如何用 fair coins 来得到一个 的 sample 呢?这里 指 上的 uniform distribution。
我们每次 sample 个 bit,得到一个 的 sample。如果这个数小于 ,那么返回即可,否则重新开始。
def sample(n):
k = int(math.ceil(math.log2(n)))
while True:
x = 0
for i in range(k):
x = x * 2 + random.randint(2)
if x < n:
return x
follow-up:时间复杂度
上面这个算法有最坏时间复杂度保证吗?
显然没有。
那存在一个有时间复杂度保证的算法吗?
和之前一模一样的 argument,没有。
那这个算法的期望时间复杂度是多少?
每一轮需要 个 bit,并且有 的概率结束,所以期望时间复杂度就是 。
follow-up:更优的算法
存在期望复杂度更低的算法吗?
存在。和 warm-up exercise 类似,还是 sample 一个 之间的实数 ,答案即为 。我们从高位到低位枚举 二进制表示,然后用一个区间 表示 可能取值,一旦发现 ,那么我们就已经得到了 ,返回即可。
def sample(n):
l, r = 0, 1
while True:
if random.randint(2):
l = (l + r) / 2
else:
r = (l + r) / 2
if int(l * n) == int(r * n):
return int(l * n)
follow-up:避免浮点数操作
上面这个做法需要用到两个浮点数 ,其精度有限。能避免使用无限精度的数据类型吗?
可以。我们回到一开始的算法,它没有达到最优是因为,如果 ,我们只会重来,白白浪费了这里面用到的 randomness。新算法如下:我们维护两个数 ,表示现在我们有一个 的 sample 。如果 ,那么我们观察 :
- 如果 ,那么 就是 的一个 sample,直接返回即可;
- 否则我们就知道 是 的一个 sample,故 就是 的一个 sample。 而每次拿到一个新 bit 后,我们可以得到一个 下的 sample ,重复操作直到 为止。
def sample(n):
x, m = 0, 1
while True:
x, m = x * 2 + random.randint(2), m * 2
if m >= n:
if x < n:
return x
x, m = x - n, m - n
follow-up:时间复杂度分析
上面这个算法期望时间复杂度,,具体为多少?
显然对 整一个高斯消元是可行的,但是这有点复杂了。聪明一点的同学可以考虑 表示在已经有了一个 的 sample 后得到 的 sample 的期望复杂度,我们有:
这样未知数会少一些……
但是 somehow 我们有更聪明的方法。令 表示几轮之后算法结束,我们先来一个常用技巧:
于是问题转化为计算 步之后算法还没有结束的概率是多少。我们把算法作一点点修改:之前是如果 那么我们返回 ,现在是如果 我们返回 ,在 的情况下这两者是等价的。
def sample(n):
x, m = 0, 1
while True:
x, m = x * 2 + random.randint(2), m * 2
if m >= n:
if x >= m - n:
return x % n
m = m - n
我们可以观察到,第 轮结束之后的 刚好就是 ,于是假设有无限精度,再改动一点点点点:
def sample(n):
x, m = 0, 1
while True:
x, m = x * 2 + random.randint(2), m * 2
if x >= m % n:
return x % n
然后就可以很清晰的看到, 轮之后仍然不结束的概率是
故算法的期望时间复杂度为
虽然这个求和有无数项,但是只要注意到 有循环节就好办了。
follow-up:时间复杂度上下界
刚才我们得到了 的精确表达式,但是看起来不存在一个对于任何一个 均成立的 closed-form。那我们能否得到一个 的上下界?
一个显然的,不需要任何推导的下界是:
因为 包含 bit 的信息,所以我们期望至少 次才能得到一个 sample……
稍微一分析,我们就可以得到一个 的更强的下界。令 ,而 内的 均有 ,故有:
那上界呢?我能想到的最好的一个是:我们把 分成三组来估计 ,
- :很显然,此时 ,这一组内所有的和为 ;
- :有 ;
- :有 ,这一组内所有的和有上界 ;
故 有上界
综上,我对 的最好的 bound 就是
前面的 就是信息论的 lower bound。可以看到,这个算法最多比理论下界多 2 bits,还算是不错的吧……