有没有一种有效的方法来生成n个整数的随机组合-
每个整数都在区间内[ min
, max
],
整数的和为 sum
,
整数可以以任何顺序出现(例如,随机顺序),并且
从满足其他要求的所有组合中随机均匀地选择组合?
对于整数必须按其值排序(而不是按任何顺序)的随机组合,是否有类似的算法?
(选择适当的组合,平均值为 mean
是特例,如果 sum = N * mean
. 这个问题等价于生成一个均匀的随机分区 sum
分成n个部分,每个部分在间隔中[ min
, max
]并按任意顺序或按其值排序(视情况而定)
我知道,对于以随机顺序出现的组合,这个问题可以通过以下方式解决(编辑[4月27日]:算法修改):
如果 N * max < sum
或者 N * min > sum
,没有解决方案。
如果 N * max == sum
,只有一个解决方案,其中 N
数字等于 max
. 如果 N * min == sum
,只有一个解决方案,其中 N
数字等于 min
.
使用smith和tromble中给出的算法(“从单位单纯形采样”,2004年)生成n个随机非负整数和 sum - N * min
.
添加 min
以这种方式生成的每个数字。
如果任何数字大于 max
,转至步骤3。
但是,如果 max
远小于 sum
. 例如,根据我的测试(带有一个实现上面涉及的特例) mean
),算法平均拒绝-
约1.6个样品,如果 N = 7, min = 3, max = 10, sum = 42
,但是
约30.6个样品,如果 N = 20, min = 3, max = 10, sum = 120
.
有没有一种方法可以在满足上述要求的情况下,对这个算法进行修改,使其对大n有效?
编辑:
作为评论中建议的另一种选择,产生有效随机组合(满足除最后一个要求外的所有要求)的有效方法是:
计算 X
,可能给定的有效组合数 sum
, min
,和 max
.
选择 Y
,中的均匀随机整数 [0, X)
.
转换(“取消银行”) Y
一个有效的组合。
但是,是否有一个公式来计算有效组合(或置换)的数量,是否有一种方法可以将整数转换为有效组合[编辑(4月28日):相同的排列而不是组合]。
编辑(4月27日):
在阅读了devroye的非均匀随机变量生成(1986)之后,我可以确认这是一个生成随机分区的问题。此外,第661页的练习2(特别是e部分)也与这个问题有关。
编辑(4月28日):
结果证明,我给出的算法是一致的,其中所涉及的整数是按随机顺序给出的,而不是按值排序。由于这两个问题都是大家普遍关心的问题,我修改了这个问题,以寻求这两个问题的规范答案。
下面的ruby代码可用于验证一致性的潜在解决方案(其中 algorithm(...)
是候选算法):
combos={}
permus={}
mn=0
mx=6
sum=12
for x in mn..mx
for y in mn..mx
for z in mn..mx
if x+y+z==sum
permus[[x,y,z]]=0
end
if x+y+z==sum and x<=y and y<=z
combos[[x,y,z]]=0
end
end
end
end
3000.times {|x|
f=algorithm(3,sum,mn,mx)
combos[f.sort]+=1
permus[f]+=1
}
p combos
p permus
编辑(4月29日):重新添加了当前实现的ruby代码。
下面的代码示例是用ruby给出的,但我的问题与编程语言无关:
def posintwithsum(n, total)
raise if n <= 0 or total <=0
ls = [0]
ret = []
while ls.length < n
c = 1+rand(total-1)
found = false
for j in 1...ls.length
if ls[j] == c
found = true
break
end
end
if found == false;ls.push(c);end
end
ls.sort!
ls.push(total)
for i in 1...ls.length
ret.push(ls[i] - ls[i - 1])
end
return ret
end
def integersWithSum(n, total)
raise if n <= 0 or total <=0
ret = posintwithsum(n, total + n)
for i in 0...ret.length
ret[i] = ret[i] - 1
end
return ret
end
# Generate 100 valid samples
mn=3
mx=10
sum=42
n=7
100.times {
while true
pp=integersWithSum(n,sum-n*mn).map{|x| x+mn }
if !pp.find{|x| x>mx }
p pp; break # Output the sample and break
end
end
}
4条答案
按热度按时间fiei3ece1#
正如op所指出的,有效地拆借的能力非常强大。如果我们能够做到这一点,那么生成分区的统一分布可以分为三个步骤(重申op在问题中的布局):
计算长度为n的分区的总数m
sum
使零件在范围内[min
,max
].生成整数的均匀分布
[1, M]
.将步骤2中的每个整数解列到其各自的分区中。
下面,我们只关注生成第n个分区,因为在给定范围内生成整数的均匀分布有大量的信息。这里有一个简单的例子
C++
解题算法,应该很容易翻译成其他语言(注意,我还没有弄清楚如何解题的组成情况(即顺序问题))。主力军
pCount
函数由下式给出:这个函数是基于一个很好的答案:有没有一个有效的算法,整数分割与限制
ohfgkhjo2#
我还没有测试过这个,所以这不是一个真正的答案,只是一些尝试,这是太长了,不能纳入一个评论。从一个满足前两个条件的数组开始玩,这样它仍然满足前两个条件,但随机性要大得多。
如果均值是整数,那么初始数组可以是[4,4,4。。。4] 或者[3,4,5,3,4,5。。。5,8,0]或者类似的简单的东西。如果平均值为4.5,请尝试[4,5,4,5。。。4, 5].
接下来选一对数字,
num1
以及num2
,在数组中。可能第一个数字应该按顺序取,就像fisher-yates洗牌一样,第二个数字应该随机选取。将第一个数字按顺序排列可以确保每个数字至少拾取一次。现在计算
max-num1
以及num2-min
. 这是从两个数字到另一个数字的距离max
以及min
边界。套limit
两个距离中的较小者。这是允许的最大变化,不会使一个或另一个数字超出允许的限制。如果limit
如果为零,则跳过这一对。选取[1]范围内的随机整数,
limit
]:叫它change
. 我从可选取范围中省略了0,因为它没有效果。测试可能会显示,你得到更好的随机性,包括它;我不确定。现在开始
num1 <- num1 + change
以及num2 <- num2 - change
. 这不会影响平均值,并且数组的所有元素仍在所需边界内。您需要至少遍历整个阵列一次。测试应该显示您是否需要多次运行它以获得足够的随机性。
eta:包含伪码
flvtvl503#
这是我的java解决方案。它功能齐全,包含两个发电机:
PermutationPartitionGenerator
对于未排序的分区和CombinationPartitionGenerator
对于已排序的分区。生成器也在类中实现SmithTromblePartitionGenerator
作为比较。班级SequentialEnumerator
按顺序枚举所有可能的分区(未排序或已排序,取决于参数)。我已经为所有这些生成器添加了全面的测试(包括您的测试用例)。实现在很大程度上是可以自我解释的。如果你有任何问题,我会在几天内回答。你可以在ideone上试试这个。
r7s23pms4#
这是约翰·麦克莱恩的置换分区生成器的算法,在本页的另一个答案中。它有两个阶段,即设置阶段和采样阶段,并生成
n
随机数[min
,max
]与总和sum
,其中数字以随机顺序列出。设置阶段:首先,使用以下公式构建解决方案表(
t(y, x)
哪里y
在[0,n
]以及x
在[0,sum - n * min
]):如果j==0,t(0,j)=1;否则为0
t(i,j)=t(i-1,j)+t(i-1,j-1)+…+t(i-1,j-(最大-最小))
这里,t(y,x)存储
y
数字(在适当的范围内)将相等x
. 这个概率是相对于所有t(y,x)具有相同的y
.采样阶段:这里我们生成
n
数字。套s
至sum - n * min
,然后针对每个位置i
,从n - 1
并向后工作到0:套
v
到[0,t(i+1,s)中的随机整数。套
r
至min
.从中减去t(i,s)
v
.而
v
保持0或更大,从中减去t(i,s-1)v
,将1添加到r
,并从中减去1s
.位置上的数字
i
在示例中设置为r
.编辑:
通过对上述算法的细微更改,可以让每个随机数使用单独的范围,而不是对所有随机数使用相同的范围:
每个位置的随机数
i
∈ [0,n
)具有最小值min(i)和最大值max(i)。让
adjsum
=sum
- σ最小值(i)。设置阶段:首先,使用以下公式构建解决方案表(
t(y, x)
哪里y
在[0,n
]以及x
在[0,adjsum
]):如果j==0,t(0,j)=1;否则为0
t(i,j)=t(i-1,j)+t(i-1,j-1)+…+t(i-1,j-(最大值(i-1)-最小值(i-1)))
采样阶段与之前完全相同,只是我们设置了
s
至adjsum
(而不是sum - n * min
)并设置r
至min(i)(而不是min
).编辑:
对于john mcclane的combinationpartitiongenerator,设置和采样阶段如下。
设置阶段:首先,使用以下公式构建解决方案表(
t(z, y, x)
哪里z
在[0,n
],y
在[0,max - min
],和x
在[0,sum - n * min
]):如果k==0,t(0,j,k)=1;否则为0
t(i,0,k)=t(i-1,0,k)
t(i,j,k)=t(i,j-1,k)+t(i-1,j,k-j)
采样阶段:这里我们生成
n
数字。套s
至sum - n * min
以及mrange
至max - min
,然后针对每个位置i
,从n - 1
并向后工作到0:套
v
到[0,t(i+1,m范围,s)中的随机整数。套
mrange
至最小值(mrange
,s
)减法
mrange
从s
.套
r
至min + mrange
.减去t(
i
,mrange
,s
)从v
.而
v
保持0或更大,将1添加到s
,从中减去1r
一个来自mrange
,然后减去t(i
,mrange
,s
)从v
.位置上的数字
i
在示例中设置为r
.