【问题标题】:Efficient algorithm to randomly select items with frequency随机选择具有频率的项目的有效算法
【发布时间】:2010-10-26 17:34:39
【问题描述】:

给定一组n 词频对:

[ (w0, f0), (w1, f1), .. ., (wn-1, fn-1) ]

其中w<sub>i</sub>是一个词,f<sub>i</sub>是一个整数频率,频率之和@9​​87654326@,

我想使用伪随机数生成器 (pRNG) 来选择 p 单词 w<sub>j<sub>0</sub></sub>, w<sub>j<sub>1</sub></sub>, ..., w<sub>j<sub>p-1</sub></sub> 这样 选择任何单词的概率与其频率成正比:

P(wi = wjk) = P(i = jk) = f i / m

(注意,这是带替换的选择,所以每次都可以选择同一个词)。

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

  1. 创建一个大小为m 的数组,并填充它,使第一个f<sub>0</sub> 条目是w<sub>0</sub>,下一个f<sub>1</sub> 条目是w<sub>1</sub>,依此类推,所以最后一个f<sub>p-1</sub> 条目是w<sub>p-1</sub>.

    [ w0, ..., w0, w1,..., w1, ..., wp-1, ..., wp-1 ]
    然后使用 pRNG 在0...m-1 范围内选择p 索引,并报告存储在这些索引处的单词。
    这需要 O(n + m + p) 工作,这不是很好,因为 m 可能比 n 大得多。
  2. 遍历输入数组一次,计算

    mi = ∑h≤ifh = mi-1 + f
    在计算m<sub>i</sub> 之后,使用pRNG 为0...p-1 中的每个k0...m<sub>i</sub>-1 范围内生成一个数字x<sub>k</sub> 如果x<sub>k</sub> &lt; f<sub>i</sub>,则为w<sub>j<sub>k</sub></sub> 选择w<sub>i</sub>(可能替换w<sub>j<sub>k</sub></sub> 的当前值)。
    这需要O(n + np) 工作。
  3. 按照算法 2 计算 m<sub>i</sub>,并在 n 个词频部分和三元组上生成以下数组:
    [ (w0, f0 sub>, m0), (w1, f1, m1), ..., (wn-1, fn-1, mn-1) ]
    然后,对于0...p-1 中的每个k,使用pRNG 在0...m-1 范围内生成一个数字x<sub>k</sub>,然后对三元组数组进行二进制搜索以找到i s.t。 m<sub>i</sub>-f<sub>i</sub> ≤ x<sub>k</sub> &lt; m<sub>i</sub>,然后选择 w<sub>i</sub>w<sub>j<sub>k</sub></sub>
    这需要O(n + p log n) 工作。

我的问题是:我可以为此使用更有效的算法吗,或者这些算法是否已经达到了最好的水平?

【问题讨论】:

  • 这是OT,请不要因此而杀了我,但是您是如何获得子/超级脚本以及和等式符号的?
  • 只需在 ... 块(用于内联)或
    ...
    块(用于全线)。
  • 对于 sum 符号,只需使用 ∑ (有关数学符号的更多 html 实体,请参阅 w3.org/TR/WD-entities-961125
  • 顺便说一句,当性能无关紧要时,这里的复制和粘贴代码可以节省您输入stackoverflow.com/a/33991225/294884
  • 请注意,算法 1 的效率当然要高得多,前提是您不计算开始组装阵列的时间(即,如果您在开发时只这样做一次)。

标签: algorithm random big-o


【解决方案1】:

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

Roulette Selection in Genetic Algorithms

【讨论】:

  • 是的,这正是算法所需要的。你肯定不会比 O(n) 复杂度更快。
  • 好的。他们只是使用迭代搜索,这需要 O(n log m) 来选择每个,并且总工作量为 O(n log m + pn log m),就像我的算法 2。谢谢!
  • 使用二分查找是 O(n + p * log n)。为什么你有 m 呢?它不会影响算法的复杂性。
【解决方案2】:

您可以创建目标数组,然后遍历确定应该选择它的概率的单词,并根据随机数替换数组中的单词。

对于第一个单词,概率为 f0/m0(其中 mn=f0+..+fn),即100%,所以目标数组中的所有位置都会被w0填充。

对于以下单词,概率下降,当您到达最后一个单词时,目标数组中会根据频率随机选择单词填充。

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。
  • 你是这个意思吗?我被 O() 计算吓跑了。频率值与工作量无关,因此 m 与 O() 值无关。它应该只是 O(np)。
  • 不,频率值很重要 - 存储频率需要 O(log m) 位,并且 O(log m) 需要添加两个频率或比较两个频率。通常这只是在 log m
  • 如果你想要那种复杂性,那么你必须考虑每个操作的数据大小......循环对不是 O(n) 操作,而是 O(n log n ) 操作...创建数组不是O(p)操作,而是O(p log p)操作...
  • 好点。我会相应地调整我的复杂性描述。
【解决方案3】:

好的,我找到了另一个算法:the alias method(也提到了in this answer)。基本上它会创建一个概率空间的分区,这样:

  • n 分区,所有分区的宽度都相同r s.t. nr = m
  • 每个分区包含一定比例的两个单词(与分区一起存储)。
  • 对于每个单词w<sub>i</sub>, f<sub>i</sub> = ∑<sub>partitions t s.t w<sub>i</sub> ∈ t</sub> r × ratio(t,w<sub>i</sub>)

由于所有分区的大小相同,选择哪个分区可以在常量工作中完成(从0...n-1中随机选择一个索引),然后可以使用分区的比率来选择哪个单词在常量中使用工作(将 pRNGed 数字与两个单词之间的比率进行比较)。所以这意味着p 的选择可以在O(p) 工作中完成,给定这样的分区。

之所以存在这样的划分,是因为存在一个词w<sub>i</sub>s.t。 f<sub>i</sub> &lt; r,当且仅当存在一个词 w<sub>i'</sub> s.t. f<sub>i'</sub> &gt; r,因为 r 是频率的平均值。

给定这样一对w<sub>i</sub>w<sub>i'</sub>,我们可以用频率为f'<sub>i</sub> = r 的伪词w'<sub>i</sub> 替换它们(表示w<sub>i</sub>,概率为f<sub>i</sub>/rw<sub>i'</sub>,概率为@987654342 @) 和一个新词w'<sub>i'</sub> 分别调整频率f'<sub>i'</sub> = f<sub>i'</sub> - (r - f<sub>i</sub>)。所有单词的平均频率仍然是 r,并且上一段的规则仍然适用。由于伪词的频率为r,并且由频率≠r的两个词组成,我们知道如果我们迭代这个过程,我们永远不会从一个伪词中产生一个伪词,并且这样的迭代必须以a结束n个伪词的序列,它们是所需的分区。

要在O(n)时间构造这个分区,

  • 遍历单词列表一次,构建两个列表:
    • 频率≤r的单词之一
    • 出现频率>r的词之一
  • 然后从第一个列表中提取一个单词
    • 如果它的频率 = r,则将其变成一个元素分区
    • 否则,从另一个列表中拉出一个单词,并用它来填充两个单词的分区。然后根据调整后的频率将第二个单词放回第一个或第二个列表中。

如果分区数为q &gt; n,这实际上仍然有效(您只需要以不同的方式证明它)。如果您想确保 r 是整数,并且您无法轻松找到 q 的因子 m s.t. q &gt; n,您可以将所有频率填充n 的系数,所以f'<sub>i</sub> = nf<sub>i</sub> 会更新m' = mn 并在q = n 时设置r' = m

无论如何,这个算法只需要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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-15
    • 2013-03-11
    • 1970-01-01
    相关资源
    最近更新 更多