从有序列表生成随机子列表以维护排序

时间:2012-04-11 11:37:59

标签: algorithm sublist

考虑一个问题,其中必须从X中选择k个项目的随机子列表Y,这是n个项目的列表,其中Y中的项目必须以与它们在X中相同的顺序出现.Y中的所选项目不必分明。一个解决方案就是:

for i = 1 to k
    A[i] = floor(rand * n) + 1
    Y[i] = X[A[i]]
sort Y according to the ordering of A

但是,由于排序操作,它具有运行时间O(k log k)。要删除它,这很诱人

high_index = n
for i = 1 to k
    index = floor(rand * high_index) + 1
    Y[k - i + 1] = X[index]
    high_index = index

但由于统一的索引选择,这给返回列表提供了明显的偏差。如果第二溶液中的指数非均匀分布,则感觉可以获得O(k)溶液。有没有人知道是否是这种情况,如果是这样,边际指数的分布具有哪些属性?

6 个答案:

答案 0 :(得分:1)

无偏O(n+k)解决方案是一个简单的高级伪代码。

  • 创建一个大小为n的空直方图[用所有元素初始化为零]
  • 在范围内使用k个均匀分布的变量填充它。 (做k次histogram[inclusiveRand(1,n)]++
  • 迭代初始列表[A],同时减少直方图中的元素并将元素附加到结果列表。

解释[edit]:

  • 我们的想法是随机选择k ni元素 每个分发,并从中创建histogram
  • 此直方图现在包含每个索引A[i]Y列表中显示的A次。
  • 现在,按顺序迭代列表i,对于每个元素A[i],将Y插入生成的histogram[i]列表P(histogram[i]=K) = P(histogram[j]=K)次。< / LI>
  • 这可以保证您维护订单,因为您按顺序插入元素,并且“永不回头”。
  • 它还保证了无偏的解决方案,因为对于每个i,j,K:K,因此对于每个K,每个元素都有相同的概率出现在结果列表中O(k)

我相信可以<{1>}使用订单统计信息[X (i)]完成,但我无法理解:\

答案 1 :(得分:1)

通过第一个算法,按排序顺序生成[0,1]的k个均匀随机样本就足够了。

让X1,...,Xk成为这些样本。鉴于Xk = x,X1,...,Xk-1的条件分布是按顺序排列的[0,x]的k-1个均匀随机样本,因此足以对Xk进行采样并递归。

Xk&lt; X? [0,1]的k个独立样本中的每一个必须小于x,因此答案(Xk的累积分布函数)是x ^ k。要根据cdf进行采样,我们所要做的就是在[0,1]的均匀随机样本上反转它:pow(random(), 1.0 / k)


这是我实际考虑实施的(预期)O(k)算法。我们的想法是将样本转储到k个bin中,对每个bin进行排序,然后进行连接。这是一些未经测试的Python:

def samples(n, k):
    bins = [[] for i in range(k)]
    for i in range(k):
        x = randrange(n)
        bins[(x * k) // n].append(x)
    result = []
    for bin in bins:
        bin.sort()
        result.extend(bin)
    return result

为什么这种期望有效?假设我们在每个bin上使用插入排序(每个bin的预期大小为O(1)!)。在O(k)的操作之上,我们将按照容器大小的平方和的数量成比例地支付,这基本上是碰撞的数量。由于两个样本碰撞的概率最多为4 / k,我们有O(k ^ 2)个样本对,预期的碰撞数为O(k)。

我非常怀疑O(k)保证可能很有可能。

答案 2 :(得分:0)

您可以使用计数排序对Y进行排序,从而使排序相对于k成线性。但是,为此需要一个长度为n的额外数组。如果我们假设您已经分配了该代码,那么您可以执行您要求的代码,其复杂度为O(k)。

这个想法就像你描述的那样,但我会再使用一个大小为n的数组cnt,我假设它被初始化为0,而另一个我认为是“堆栈”的数据是空的。

for i = 1 to k
    A[i] = floor(rand * n) + 1
    cnt[A[i]]+=1
    if cnt[A[i]] == 1  // Needed to be able to traverse the inserted elements faster
      st.push(A[i])

for elem in st
  for i = 0 to cnt[elem]
    Y.add(X[elem])

for elem in st
  cnt[elem] = 0
编辑:正如老人所说,我在帖子中说的不是真的 - 我仍然需要排序st,这可能比原始命题更好但不是太多。因此,如果k与n相当,那么这种方法将是好的,然后我们只是通过cnt线性迭代并以这种方式构造Y.这种方式不需要st:

for i = 1 to k
    A[i] = floor(rand * n) + 1
    cnt[A[i]]+=1

for i = 1 to k
  for j = 0 to cnt[i]
    Y.add(X[i])
  cnt[i] =0

答案 3 :(得分:0)

对于Y中的第一个索引,X中索引的分布由下式给出:

P(x; n,k)=二项式(n - x + k - 2,k - 1)/ norm

其中二项式表示二项式系数的计算,而norm是一个归一化因子,等于可能的子列表配置的总数。

norm =二项式(n + k - 1,k)

因此,对于k = 5和n = 10,我们有:

  • norm = 2002
  • P(x = 0)= 0.357,P(x <= 0)= 0.357
  • P(x = 1)= 0.245,P(x <= 1)= 0.604
  • P(x = 2)= 0.165,P(x <= 2)= 0.769
  • P(x = 3)= 0.105,P(x <= 3)= 0.874
  • P(x = 4)= 0.063,P(x <= 4)= 0.937
  • ...(我们可以继续这个到x = 10)

我们可以从这个分布中抽取Y中第一个项目的X索引(称之为x1)。然后可以用P(x;(n - x1),(k - 1))以相同的方式对Y中第二个索引的分布进行采样,依此类推所有后续索引。

我现在的感觉是问题在O(k)中无法解决,因为通常我们无法从恒定时间内描述的分布中进行采样。如果k = 2那么我们可以使用二次公式在恒定时间内求解(因为概率函数简化为0.5(x ^ 2 + x))但我看不到将其扩展到所有k的方法(我的数学是'n'但很棒)。

答案 4 :(得分:0)

原始列表X有n个项目。有2个可能的子列表,因为每个项目或将不会出现在结果子列表中:每个项目都会在可能的子列表的枚举中添加一些内容。您可以查看n位的位字的枚举。

由于您只想要具有k个项目的子列表,因此您对设置了k位的位词感兴趣。

一个实用的算法可以从X中挑选(或选择不)第一个元素,然后递归到X的最右边的n-1个子串,同时考虑所选项的累计数量。由于按顺序处理X列表,Y列表也将按顺序排列。

答案 5 :(得分:0)

原始列表X有n个项目。有两个可能的子列表,因为每个项目都会或不会出现在子列表中:每个项目都会在可能的子列表的枚举中添加一些内容。您可以查看n位的位字的枚举。

由于您只想要具有k个项目的子列表,因此您对设置了k位的位词感兴趣。一个实用的算法可以从X中挑选(或选择不是)第一个元素,然后递归到X的最右边的n-1子串中,同时考虑所选项的累积数量。由于按顺序处理X列表,Y列表也将按顺序排列。

#include <stdio.h>
#include <string.h>

unsigned pick_k_from_n(char target[], char src[], unsigned k, unsigned n, unsigned done);
unsigned pick_k_from_n(char target[], char src[]
                , unsigned k, unsigned n, unsigned done)
{
unsigned count=0;

if (k>n) return 0;

if (k==0) {
        target[done] = 0;
        puts(target);
        return 1;
        }
if (n > 0) {
        count += pick_k_from_n(target, src+1, k, n-1, done);

        target[done] = *src;
        count += pick_k_from_n(target, src+1, k-1, n-1, done+1);
        }

return count;
}

int main(int argc, char **argv) {

char result[20];
char *domain = "OmgWtf!";
unsigned cnt ,len, want;
want = 3;

switch (argc) {
default:
case 3:
        domain = argv[2];
case 2:
        sscanf(argv[1], "%u", &want);
case 1:
        break;
        }
len = strlen(domain);

cnt = pick_k_from_n(result, domain, want, len, 0);

fprintf(stderr, "Count=%u\n", cnt);

return 0;
}

删除递归是留给读者的练习。 一些输出:

plasser@pisbak:~/hiero/src$ ./a.out 3 ABBA
BBA
ABA
ABA
ABB
Count=4
plasser@pisbak:~/hiero/src$