给定一组n
字频对:
[ (w0, f0), (w1, f1), ..., (wn-1, fn-1) ]
其中wi
是单词,fi
是整数频率,频率之和∑fi = m
,
我想使用伪随机数生成器(pRNG)来选择p
个单词wj0, wj1, ..., wjp-1
,以便
选择任何单词的概率与其频率成正比:
P(wi = wjk) = P(i = jk) = fi / m
(注意,这是选择替换,因此每次都可以选择相同的单词。
到目前为止,我已经提出了三种算法:
创建一个大小为m
的数组,并填充它以使第一个f0
条目为w0
,下一个f1
条目为w1
,依此类推,因此最后fp-1
个条目为wp-1
。
[ w0, ..., w0, w1,..., w1, ..., wp-1, ..., wp-1 ]然后使用pRNG选择范围
p
中的0...m-1
索引,并报告存储在这些索引中的字词。O(n + m + p)
工作,这不是很好,因为m
可能比n大得多。
逐步完成输入数组,计算
mi = ∑h≤ifh = mi-1 + fi并且在计算
mi
之后,使用pRNG为xk
中的每个0...mi-1
生成k
范围内的数字0...p-1
如果wi
,请为wjk
选择wjk
(可能会替换xk < fi
的当前值)。O(n + np)
工作。
mi
,并在n个字频 - 部分和三元组上生成以下数组:[ (w0, f0, m0), (w1, f1, m1), ..., (wn-1, fn-1, mn-1) ]然后,对于
0...p-1
中的每个k,使用pRNG生成xk
范围内的数字0...m-1
,然后对三元组数组进行二元搜索,以找到i
ST mi-fi ≤ xk < mi
,然后为wi
选择wjk
这需要O(n + p log n)
工作。我的问题是:我是否可以使用更高效的算法,或者这些算法是否合适?
答案 0 :(得分:6)
这听起来像轮盘赌选择,主要用于遗传/进化算法的选择过程。
答案 1 :(得分:2)
您可以创建目标数组,然后循环确定应该被拾取的概率的单词,并根据随机数替换数组中的单词。
对于第一个单词,概率为f 0 / m 0 (其中m n = f 0 + .. + f n ),即100%,因此目标数组中的所有位置都将用w 0 填充。
对于下面的单词,概率会下降,当你到达最后一个单词时,目标数组会被随机选择的单词填充,这些单词符合频率。
C#中的示例代码:
public class WordFrequency {
public string Word { get; private set; }
public int Frequency { get; private set; }
public WordFrequency(string word, int frequency) {
Word = word;
Frequency = frequency;
}
}
WordFrequency[] words = new WordFrequency[] {
new WordFrequency("Hero", 80),
new WordFrequency("Monkey", 4),
new WordFrequency("Shoe", 13),
new WordFrequency("Highway", 3),
};
int p = 7;
string[] result = new string[p];
int sum = 0;
Random rnd = new Random();
foreach (WordFrequency wf in words) {
sum += wf.Frequency;
for (int i = 0; i < p; i++) {
if (rnd.Next(sum) < wf.Frequency) {
result[i] = wf.Word;
}
}
}
答案 2 :(得分:1)
好的,我发现了另一种算法:the alias method(也提到in this answer)。基本上它创建了概率空间的分区,以便:
n
个分区,所有宽度r
s.t. nr = m
。wi
,fi = ∑partitions t s.t wi ∈ t r × ratio(t,wi)
由于所有分区都具有相同的大小,因此选择可以在常量工作中完成哪个分区(随机选择0...n-1
的索引),然后可以使用分区的比率来选择使用哪个单词在不断的工作中(比较pRNGed数字和两个单词之间的比率)。所以这意味着p
选项可以在O(p)
工作中完成,给定这样的分区。
存在这样的分区的原因是存在单词wi
s.t. fi < r
,当且仅当存在单词wi'
s.t. fi' > r
,因为r是频率的平均值。
鉴于这样一对wi
和wi'
,我们可以用频率w'i
的伪字f'i = r
替换它们(代表wi
概率为fi/r
{1}}和wi'
概率1 - fi/r
)和新词w'i'
的调整频率f'i' = fi' - (r - fi)
。所有单词的平均频率仍为r,前一段的规则仍然适用。由于伪字具有频率r并且由频率≠r的两个字组成,我们知道如果我们迭代这个过程,我们将永远不会从伪字中产生伪字,并且这样的迭代必须以n个伪词的序列,它是所需的分区。
在O(n)
时间内构建此分区,
如果分区数q > n
(您只需要以不同方式证明),这实际上仍然有效。如果你想确保r是积分的,你就不能轻易找到q
s.t的因子m
。 q > n
,您可以将所有频率填充n
因子,f'i = nfi
,更新m' = mn
并在r' = m
时设置q = n
。
在任何情况下,此算法只需要O(n + p)
工作,我认为这是最佳的。
在红宝石中:
def weighted_sample_with_replacement(input, p)
n = input.size
m = input.inject(0) { |sum,(word,freq)| sum + freq }
# find the words with frequency lesser and greater than average
lessers, greaters = input.map do |word,freq|
# pad the frequency so we can keep it integral
# when subdivided
[ word, freq*n ]
end.partition do |word,adj_freq|
adj_freq <= m
end
partitions = Array.new(n) do
word, adj_freq = lessers.shift
other_word = if adj_freq < m
# use part of another word's frequency to pad
# out the partition
other_word, other_adj_freq = greaters.shift
other_adj_freq -= (m - adj_freq)
(other_adj_freq <= m ? lessers : greaters) << [ other_word, other_adj_freq ]
other_word
end
[ word, other_word , adj_freq ]
end
(0...p).map do
# pick a partition at random
word, other_word, adj_freq = partitions[ rand(n) ]
# select the first word in the partition with appropriate
# probability
if rand(m) < adj_freq
word
else
other_word
end
end
end