随机增加序列,O(1)访问任何元素?

时间:2014-08-25 18:45:12

标签: algorithm math random random-sample random-access

我有一个有趣的数学/ CS问题。我需要从增加值X的可能无限随机序列中采样,X(i)> X(i-1),它们之间有一些分布。您可以将此视为[0,d)中均匀随机数的不同序列D的总和。如果从第一个开始并从那里开始,这很容易做到;你只需每次添加一笔随机金额。但问题是,我希望能够比O(n)时间更快地获得序列的任何元素,理想情况下是O(1),而不存储整个列表。具体来说,让我说我选择d = 1,因此D(给定特定种子)及其相关X的一种可能性是:

D={.1, .5, .2, .9, .3, .3, .6 ...}  // standard random sequence, elements in [0,1)
X={.1, .6, .8, 1.7, 2.0, 2.3, 2.9, ...} // increasing random values; partial sum of D

(我并不真正关心D,我只是展示了构建X的一种概念性方法,即我感兴趣的序列。)现在我希望能够计算X的值[1]或X [1000]或X [1000000]同样快,不存储X或D的所有值。任何人都可以指出一些聪明的算法或思考这个问题的方法吗?

(是的,我正在寻找的是随机访问随机序列 - 具有两种不同的随机含义。难以谷歌!)

3 个答案:

答案 0 :(得分:2)

由于D是伪随机数,因此可以进行时空权衡: O(sqrt(n)) - 使用O(sqrt(n))存储位置进行时间检索(或 通常,O(n**alpha) - 使用O(n**(1-alpha))进行时间检索 存储位置)。假设基于零的索引和那个 X[n] = D[0] + D[1] + ... + D[n-1]。计算和存储

Y[s] = X[s**2]

感兴趣范围内的所有s**2 <= n。要查找X[n],请试试 s = floor(sqrt(n))并返回 Y[s] + D[s**2] + D[s**2+1] + ... + D[n-1]

编辑:这是基于以下想法的方法的开始。

令Dist(1)为[0,d)上的均匀分布,并且让dist(k)为k> 1。 1是来自Dist(1)的k个独立样本的总和的分布。我们需要快速,确定性的方法来(i)伪随机地采样Dist(2 ** p)和(ii)假设X和Y分布为Dist(2 ** p),伪随机样本X以X + Y的结果为条件。

现在假设D数组构成一个大小为2 ** q的完整二叉树的叶子。内部节点的值是两个孩子的值的总和。天真的方法是直接填充D数组,但是计算根条目需要很长时间。我提出的方法是从Dist(2 ** q)采样根。然后,根据Dist(2 **(q-1))给出一个孩子给出根值。这决定了另一个的价值,因为总和是固定的。以递归方式在树下工作。这样,我们在时间O(q)中查找树值。

这是高斯D的实现。 我不确定它是否正常工作。

import hashlib, math

def random_oracle(seed):
    h = hashlib.sha512()
    h.update(str(seed).encode())
    x = 0.0
    for b in h.digest():
        x = ((x + b) / 256.0)
    return x

def sample_gaussian(variance, seed):
    u0 = random_oracle((2 * seed))
    u1 = random_oracle(((2 * seed) + 1))
    return (math.sqrt((((- 2.0) * variance) * math.log((1.0 - u0)))) * math.cos(((2.0 * math.pi) * u1)))

def sample_children(sum_outcome, sum_variance, seed):
    difference_outcome = sample_gaussian(sum_variance, seed)
    return (((sum_outcome + difference_outcome) / 2.0), ((sum_outcome - difference_outcome) / 2.0))

def sample_X(height, i):
    assert (0 <= i <= (2 ** height))
    total = 0.0
    z = sample_gaussian((2 ** height), 0)
    seed = 1
    for j in range(height, 0, (- 1)):
        (x, y) = sample_children(z, (2 ** j), seed)
        assert (abs(((x + y) - z)) <= 1e-09)
        seed *= 2
        if (i >= (2 ** (j - 1))):
            i -= (2 ** (j - 1))
            total += x
            z = y
            seed += 1
        else:
            z = x
    return total

def test(height):
    X = [sample_X(height, i) for i in range(((2 ** height) + 1))]
    D = [(X[(i + 1)] - X[i]) for i in range((2 ** height))]
    mean = (sum(D) / len(D))
    variance = (sum((((d - mean) ** 2) for d in D)) / (len(D) - 1))
    print(mean, math.sqrt(variance))
    D.sort()
    with open('data', 'w') as f:
        for d in D:
            print(d, file=f)
if (__name__ == '__main__'):
    test(10)

答案 1 :(得分:0)

如果你没有在X中记录这些值,并且你不记得之前生成的X中的值,则无法保证你生成的X中的元素(动态)会增加订购。如果你不知道如何在任何m的选择中为D中的前m个随机变量的总和快速生成CDF,那么似乎没有办法避免每个查询的O(n)时间最坏情况。

答案 2 :(得分:0)

如果你想从特定实现中获得i th 值X(i),我无法在不生成序列的情况下看到你如何做到这一点对我也许其他人可以想出一些聪明的东西。

您是否愿意接受一个合理的价值,因为它与您在X流程的多个实现中观察到的X(i)具有相同的分布?如果是这样,那应该很容易。 X(i)将渐近正态分布为均值i/2(因为它是k=1,...,i的D k &的总和,D& #39; s是Uniform(0,1),并且D的期望值是1/2)和方差i/12(因为D的方差是1/12并且是独立之和的方差随机变量是它们方差的总和)。

由于渐近方面,我选择一些阈值让i从直接求和切换到使用法线。例如,如果您使用i = 12作为阈值,则可以使用实际的制服求和来表示i的值为1到11,并为i&gt;生成Normal(i/2, sqrt(i/12))值。这是一个O(1)算法,因为总工作量受到阈值的限制,并且所产生的结果将在分布上代表您实际完成求和时所看到的结果。