如何在 O(n) 中编写优先左洗牌算法?

问题描述

有像 FisherYates 这样的 shuffle 算法。它们采用一个数组并返回一个元素以随机顺序排列的数组。这在 O(n) 中运行。

我想要做的是实现一个优先左洗牌算法。这是什么意思?

  • 优先级:它不接受一组值。它需要一组值-概率对。例如。 [ (1,60),(2,10),(3,(4,20) ]。值 1 有 60%,值 2 有 10%,...
  • left-shuffle一个值出现的概率越高,它在数组左边的可能性就越大。

让我们以这个例子 [ (1,20) ]。最可能的结果应该是 [ 3,4,1,2 ][ 3,2,1 ]


我尝试实现这个,但我没有在 O(n) 中找到任何解决方案。

O(n^2) 基于 FisherYates 的伪代码

sum = 100  #100%
for i = 0 to n-2:
    r = random value between 0 and sum
    localsum = 0
    for j = i to n-1:
        localsum = localsum + pair[j].Probability
        if localsum >= r + 1:
            swap(i,j)
            break
    sum = sum - pair[i].Probability

什么可以稍微改进一下:在开始时按概率递减的元素对元素进行排序,以尽量减少交换次数和内部循环中的迭代次数

是否有更好的解决方案(甚至可能在 O(n) 中)?

解决方法

更新我的第一个答案:

我发现了一个 paper,其中引入了 O(1) 的“通过随机接受的轮盘选择”。这使得算法为 O(n) 并且易于实现

from random import randint
from random import random
import time

data = [ (1,10),(2,(3,60),(4,20) ]

def swap(i,j,array):
    array[j],array[i] = array[i],array[j]

def roulette_wheel_selection(data,start,max_weight_limit):
    while True:
        r = random()
        r_index = randint(start,len(data) - 1)
        if r <= data[r_index][1] / max_weight_limit:
            return r_index
    

def shuffle(data,max_weight):
    data = data.copy()
    n = len(data)
    for i in range(n-1):
        r_index = roulette_wheel_selection(data,i,max_weight)
        swap(i,r_index,data)
    return data

def performance_test(iterations,data):
    start = time.time()
    max_weight = max([item[1] for item in data])
    for i in range(iterations):
        shuffle(data,max_weight)
    end = time.time()
    print(len(data),': ',end - start)
    return end - start

performance_test(1000,data)

data2 = []
for i in range(10):
    data2 += data
performance_test(1000,data2)  

data3 = []
for i in range(100):
    data3 += data
performance_test(1000,data3) 

data4 = []
for i in range(1000):
    data4 += data
performance_test(1000,data4) 

性能输出

4 :  0.09153580665588379
40 :  0.6010794639587402
400 :  5.142168045043945
4000 :  50.09365963935852

所以它是 n(数据大小)的线性时间。我从我的第一个答案中将常量从“更新总和”更新为“所有数据项的最大权重”,但确定它取决于 max_weight konstant。如果有人有策略以适当的方式更新 max_weight,性能会提高。