我有以下问题:
从0-N范围生成M个均匀随机整数,其中N>> M,并且其中没有对具有小于K的差异。其中M>> ķ击>
目前我能想到的最好的方法是维护一个排序列表,然后确定当前生成的整数的下限,并用下面和上面的元素测试它,如果可以,那么在中间插入元素。这是复杂的O(nlogn)。
是否会出现更高效的算法?
问题的一个例子:
生成1000到100万之间的1000个均匀随机整数,其中任意两个整数之间的差值不小于1000
解决这个问题的综合方法是:
当n-choose-m很大时,这个解决方案是有问题的,因为枚举和存储所有可能的组合将是非常昂贵的。因此,寻求一种有效的在线生成解决方案。
注意:以下是 pentadecagon
提供的解决方案的C ++实现std::vector<int> generate_random(const int n, const int m, const int k)
{
if ((n < m) || (m < k))
return std::vector<int>();
std::random_device source;
std::mt19937 generator(source());
std::uniform_int_distribution<> distribution(0, n - (m - 1) * k);
std::vector<int> result_list;
result_list.reserve(m);
for (int i = 0; i < m; ++i)
{
result_list.push_back(distribution(generator));
}
std::sort(std::begin(result_list),std::end(result_list));
for (int i = 0; i < m; ++i)
{
result_list[i] += (i * k);
}
return result_list;
}
答案 0 :(得分:3)
编辑:我根据需要修改了有序序列的文本,每个序列都有相同的概率。
为a_i
创建随机数i=0..M-1
,不重复。排序他们。然后创建数字
b_i=a_i + i*(K-1)
鉴于构造,这些数字b_i
具有所需的差距,因为a_i
已经存在至少1
的差距。为了确保这些b
值完全涵盖所需的范围[1..N]
,您必须确保从范围a_i
中选择[1..N-(M-1)*(K-1)]
。这样你就可以得到真正独立的数字。那么,考虑到所需的差距,尽可能独立。由于排序,你再次获得O(M log M)性能,但这不应该太糟糕。排序通常非常快。在Python中它看起来像这样:
import random
def random_list( N, M, K ):
s = set()
while len(s) < M:
s.add( random.randint( 1, N-(M-1)*(K-1) ) )
res = sorted( s )
for i in range(M):
res[i] += i * (K-1)
return res
答案 1 :(得分:2)
首先关闭:这将尝试显示(M+1)
- compositions之间有一个双向投标(轻微的修改,我们将允许加数为{值0
的{1}})以及问题的有效解决方案。在那之后,我们只需要随机选择其中一种成分并应用双射。
<强>双向注入:强>
让
然后x i 形成一个N - (M-1)*K
- 组合(允许M+1
加数)左边的值(注意x i 不必单调增加!)。
由此我们得到一个有效的解决方案
通过设置值m i ,如下所示:
我们看到m i 和m i + 1 之间的距离至少是0
,m M 是最多K
(比较我们开始的成分选择)。这意味着满足上述条件的每个N
- 组合都只能为您的问题定义一个有效的解决方案。 (你会注意到我们只使用x M 作为一种方法使得总和正确,我们不会用它来构造m i 。)
为了看到这给出了一个双射,我们需要看到构造可以逆转;为此,让
是满足您条件的特定解决方案。为了得到这个构造,我可以按如下方式定义x i :
现在首先,所有x i 至少是(M+1)
,所以没关系。要查看它们是否构成上述值的有效合成(同样,每个x i 允许为0
),请考虑:
第三个相等,因为我们有这个伸缩的和几乎所有的m i 。
所以我们已经看到所描述的结构在所描述的0
组合与问题的有效解决方案之间产生了双射。我们现在要做的就是随机选择其中一种成分并应用结构来获得解决方案。
随机均匀地挑选作文
可以通过以下方式唯一标识每个描述的组合物(比较this以进行说明):为该值的一元表示法保留N - (M-1)*K
个空格,以及另一个N - (M-1)*K
个空格对于M
逗号。我们得到M
- (M+1)
的组合,选择N - (M-1)*K
空格中的M
,在其中添加逗号,然后用N - (M-1)*K + M
填充其余内容。然后让x 0 为第一个逗号前的|
,x M + 1 最后一个逗号后的|
个数,以及所有其他x i 逗号|
和|
之间i
的数量。所以我们所要做的就是随机选择整数区间i+1
的{{1}} - 元素子集,我们可以通过M
中的Fisher-Yates shuffle来做(我们可以需要对[1; N - (M-1)*K + M]
分隔符进行排序以构建组合,因为O(N + M log M)
需要在M
中才能存在任何解决方案。因此,如果M*K
比O(N)
大至少一个对数因子,那么这在N
中是线性的。
注意:@DavidEisenstat建议有更多节省空间的方法来选择该区间的M
- 元素子集;我不知道,我很害怕。
您可以通过我们从N
上面的构造中获得的简单输入验证来获得防错算法,并且所有三个值至少为M
(或{{1如果您将空集定义为该案例的有效解决方案,那么。
答案 2 :(得分:1)
为什么不这样做:
for (int i = 0; i < M; ++i) {
pick a random number between K and N/M
add this number to (N/M)* i;
现在你有M个随机数,沿N均匀分布,所有这些数字的差值至少为K.它在O(n)时间内。作为一个额外的奖励,它已经排序。 : - )
编辑:
实际上,“选择随机数”部分不应介于K和N / M之间,而应介于min(K, [K - (N/M * i - previous value)])
之间。这将确保差异仍然至少为K,并且不排除不应错过的值。
第二次编辑:
嗯,第一种情况不应该在K和N / M之间 - 它应该在0和N / M之间。就像你需要特殊的外壳,当你靠近N / M * i边界时,我们需要特殊的初始套管。
除此之外,你在评论中提出的问题是公平的,你是对的。当我的伪代码出现时,它目前完全错过了N / M * M和N之间的过剩。这是另一个边缘情况;只需更改上一个范围的随机值。
现在,在这种情况下,您的分布将在最后一个范围内有所不同。由于您拥有更多数字,因此每个数字的可能性略小于所有其他范围。我的理解是因为你使用“&gt;&gt;”,这不应该真正影响分布,即样本集中的大小差异应该是名义上的。但是如果你想让它更公平,你可以在每个范围内平均分配多余的东西。这使得您的初始范围计算更加复杂 - 您必须根据剩余的剩余部分除以M来增加每个范围。
有许多特殊情况需要注意,但它们都能够得到处理。我保持伪代码非常基本只是为了确保一般概念清楚地通过。如果不出意外,它应该是一个很好的起点。
第三次和最后一次编辑:
对于那些担心分配强迫均匀的人,我仍然声称没有什么可以说它不能。选择在每个段中均匀分布。有一种线性的方法来保持它不均匀,但这也有一个权衡:如果选择一个非常高的值(在非常大的N时应该不太可能),那么所有其他值都受到约束:
int prevValue = 0;
int maxRange;
for (int i = 0; i < M; ++i) {
maxRange = N - (((M - 1) - i) * K) - prevValue;
int nextValue = random(0, maxRange);
prevValue += nextValue;
store previous value;
prevValue += K;
}
这仍然是线性和随机的并且允许不均匀,但是越大prevValue
得到,其他数字变得越受约束。就个人而言,我更喜欢我的第二个编辑答案,但这是一个可用的选项,给定足够大的N可能满足所有发布的要求。
想想看,这是另一个想法。它需要更多的数据维护,但仍然是O(M),可能是最公平的分布:
您需要做的是维护有效数据范围的向量和概率标度向量。有效数据范围只是K仍然有效的高 - 低值列表。您的想法是首先使用缩放概率来选择随机数据范围,然后随机选择该范围内的值。您删除旧的有效数据范围,并将其替换为相同位置的0,1或2个新数据范围,具体取决于仍有效的数据范围。所有这些动作都是恒定时间,而不是处理加权概率,即O(M),在循环中完成M次,所以总数应该是O(M ^ 2),这应该比O(NlogN)好得多因为N&gt;&gt;微米。
让我使用OP的原始示例:
,而不是伪代码在接下来的迭代中,你会做同样的事情:随机选择一个介于0和1之间的数字,并确定缩放的范围。然后随机选择该范围内的数字,并替换您的范围和比例。
当它完成时,我们不再在O(M)中行动,但我们仍然只依赖于M而不是N的时间。这实际上是统一和公平的分配。
希望其中一个想法适合你!