高效的算法随机选择频率项目

时间:2009-05-16 14:48:41

标签: algorithm random big-o

给定一组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

(注意,这是选择替换,因此每次都可以选择相同的单词

到目前为止,我已经提出了三种算法:

  1. 创建一个大小为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大得多。

  2. 逐步完成输入数组,计算

    mi = ∑h≤ifh = mi-1 + fi
    并且在计算mi之后,使用pRNG为xk中的每个0...mi-1生成k范围内的数字0...p-1 如果wi,请为wjk选择wjk(可能会替换xk < fi的当前值)。
    这需要O(n + np)工作。

  3. 如算法2中那样计算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)工作。
  4. 我的问题是:我是否可以使用更高效的算法,或者这些算法是否合适?

3 个答案:

答案 0 :(得分:6)

这听起来像轮盘赌选择,主要用于遗传/进化算法的选择过程。

查看Roulette Selection in Genetic Algorithms

答案 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
  • 每个分区包含一些比例的两个单词(与分区一起存储)。
  • 代表每个单词wifi = ∑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是频率的平均值。

鉴于这样一对wiwi',我们可以用频率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)时间内构建此分区,

  • 浏览一次单词列表,构建两个列表:
    • 频率≤r
    • 的一个词
    • 频率为&gt;的单词之一r
  • 然后从第一个列表中拉出一个单词
    • 如果频率= r,则将其设为单元素分区
    • 否则,从另一个列表中拉出一个单词,并用它来填写一个双字分区。然后根据调整后的频率将第二个单词放回第一个或第二个列表中。

如果分区数q > n(您只需要以不同方式证明),这实际上仍然有效。如果你想确保r是积分的,你就不能轻易找到q s.t的因子mq > 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