關於權重的隨機抽取演算法,抽乙個次或抽多次的實現

2021-10-06 22:19:32 字數 3586 閱讀 8773

遊戲伺服器開發還真會常遇到,策劃需求根據權重作概率發獎勵,比如獎勵和權重分別是:a10、b20、c70,這時候出現a的概率就要是10%,b就是20%,c是70%,就是出現的概率是當前權重 / 總權重。該怎麼設計演算法呢?抽多個獎勵id並且每個獎勵id只能出現一次時候改怎樣實現呢?

通過總權重隨機值,再線性掃瞄,通過權重隨機值去查詢所在的權重區間。

時間複雜度:o(n)

過程:1、先計算出所有道具的權重總和s

2、然後呼叫隨機函式得到乙個區間在[1, s]的隨機值n

3、掃瞄列表,如果n小於當前的權重,則返回當前道具

4、若n大於當前權重,則把n減去當前權重

# 測試資料 

# [(獎勵id: 權重)]

pool_data = [

(1, 50),

(2, 20),

(3, 40),

(4, 10),

]def linear_random(pool_data):

sum_weight = sum(a[1] for a in pool_data)

n = random.randint(1, sum_weight)

for key, weight in pool_data:

if n <= weight:

return key

else:

n -= weight

在方法一的基礎上做優化,即權重大的(概率大的)排序在前面,即線性查詢時先從概率大的開始查詢,命中率會更高。

時間複雜度:o(n)

過程:1、將獎池根據權重從大到小排序

2、方法同上

# 測試資料 

# [(獎勵id: 權重)]

pool_data = [

(1, 50),

(2, 20),

(3, 40),

(4, 10),

]def sort_linear_random(pool_data):

pool = sorted(pool_data, key=lambda a: a[1], reverse=true)

sum_weight = sum(a[1] for a in pool)

n = random.randint(1, sum_weight)

for key, weight in pool:

if n <= weight:

return key

else:

n -= weight

既然是在序的權重中查到隨機權重值,那就可以使用二分查詢

時間複雜度:o(logn)

過程:1、先計算出所有道具的權重總和s

2、然後呼叫隨機函式得到乙個區間在[1, s]的隨機值n

3、重建權重表,列表中每個權重是前面所有權重值的總和,得到有序權重表

3、根據n在有序權重表二分查詢

# 測試資料 

# [(獎勵id: 權重)]

pool_data = [

(1, 50),

(2, 20),

(3, 40),

(4, 10),

]def binary_random(pool_data):

add = 0

pool =

for k, w in pool_data:

add += w

sum_weight = add

n = random.randint(1, sum_weight)

mid, left, right, = 0, 0, len(pool) - 1

while left < right: # 二分查詢

mid = (right + left) // 2

key, mid_num = pool[mid]

if mid_num < n:

left = mid + 1

elif mid_num > n:

right = mid

else:

return key

return pool[mid][0]

基於方法一線性掃瞄的優化,線性掃瞄既然每次只移動乙個位置,既然權重表是從大到小排序,後面的權重值就一定小於等於當前值,就可以一次移動多個位置。隨機權重值 /  當前權重值 = 3,下次可以直接移動3個位置。

時間複雜度:o(n)

過程:1、先計算出所有道具的權重總和s

2、然後呼叫隨機函式得到乙個區間在[1, s]的隨機值n

3、重建權重表,列表中每個權重是前面所有權重值的總和,得到有序權重表

3、通過n / 當前權重,得到的值j大於0,則是j是要跳躍得階級數(因為有序,後面的權重肯定比當前小)

# 測試資料 

# [(獎勵id: 權重)]

pool_data = [

(1, 50),

(2, 20),

(3, 40),

(4, 10),

]def jump_random(pool_data):

sort_pool = sorted(pool_data, key=lambda a: a[1], reverse=true)

add = 0

pool =

for key, weight in sort_pool:

add += weight

sum_weight = add

pool_length = len(pool)

n = random.randint(1, sum_weight)

i = 0

while i < pool_length - 1:

key, weight = pool[i]

if weight > n:

return key

else:

multiple = n // weight

i += multiple

return pool[i][0]

時間複雜度o(1)

從乙個獎池多次抽取獎勵,並且個獎勵id只能出現一次。

很簡單,每次抽完就把總權重減去當前抽中id的權重,再在新的總權重中去隨機生成權重值。

可以用字典來實現刪減操作。

# 

pool_dict =

def times_linear(pool_dict, cnt):

pool = pool_dict.copy()

sum_weight = sum(pool.values())

rst =

for _ in range(cnt):

n = random.randint(1, sum_weight)

for key, weight in pool.items():

if n <= weight:

sum_weight -= weight

del pool[key]

break

else:

n -= weight

return rst

總結:抽取量大、權重較多的時候做優化還是非常必要的。

從乙個檔案中隨機抽取N行方法

從m行的檔案隨機抽取n行 可以假定m n 這是需要對資料進行抽樣處理時很長常見的需求。首先想到的方法是每讀取一行,扔乙個0到m 1的隨機數,如果隨機數小於n,則輸出該行,否則不輸出。perl源 如下 usr bin perl subset.pl usage sub set.pl file sampl...

關於生成乙個隨機數組

生成隨機數的最基本 是 random rand new random int i rand.next 0,100 next函式的引數可以為空,也可以是乙個值的範圍。像這種方法通常生成乙個隨機數不會有問題,但是如果要生成乙個隨機數組的話就不見得好用了,比如 listlstrnd new list ra...

關於生成乙個隨機數組

生成隨機數的最基本 是 random rand new random int i rand.next 0,100 next函式的引數可以為空,也可以是乙個值的範圍。像這種方法通常生成乙個隨機數不會有問題,但是如果要生成乙個隨機數組的話就不見得好用了,比如 listlstrnd new list ra...