当前位置:   article > 正文

游戏服务器算法-关于权重的随机抽取算法,抽一个次或抽多次的实现_权重随机

权重随机

    游戏服务器开发还真会常遇到,策划需求根据权重作概率发奖励,比如奖励和权重分别是:A10、B20、C70,这时候出现A的概率就要是10%,B就是20%,C是70%,就是出现的概率是当前权重 / 总权重。该怎么设计算法呢?抽多个奖励ID并且每个奖励ID只能出现一次时候改怎样实现呢?

单次抽取

方法一:普通的线性扫描

    通过总权重随机值,再线性扫描,通过权重随机值去查找所在的权重区间。

时间复杂度:O(N)

过程:

1、先计算出所有道具的权重总和S

2、然后调用随机函数得到一个区间在[1, S]的随机值N

3、扫描列表,如果N小于当前的权重,则返回当前道具

4、若N大于当前权重,则把N减去当前权重

  1. # 测试数据
  2. # [(奖励ID: 权重)]
  3. pool_data = [
  4. (1, 50),
  5. (2, 20),
  6. (3, 40),
  7. (4, 10),
  8. ]
  9. def linear_random(pool_data):
  10. sum_weight = sum(a[1] for a in pool_data)
  11. n = random.randint(1, sum_weight)
  12. for key, weight in pool_data:
  13. if n <= weight:
  14. return key
  15. else:
  16. n -= weight

方法二:有序的线性扫描

    在方法一的基础上做优化,即权重大的(概率大的)排序在前面,即线性查找时先从概率大的开始查找,命中率会更高。

时间复杂度:O(N)

过程:

1、将奖池根据权重从大到小排序

2、方法同上

  1. # 测试数据
  2. # [(奖励ID: 权重)]
  3. pool_data = [
  4. (1, 50),
  5. (2, 20),
  6. (3, 40),
  7. (4, 10),
  8. ]
  9. def sort_linear_random(pool_data):
  10. pool = sorted(pool_data, key=lambda a: a[1], reverse=True)
  11. sum_weight = sum(a[1] for a in pool)
  12. n = random.randint(1, sum_weight)
  13. for key, weight in pool:
  14. if n <= weight:
  15. return key
  16. else:
  17. n -= weight

方法三:跳跃扫描

    基于方法一线性扫描的优化,线性扫描既然每次只移动一个位置,既然权重表是从大到小排序,后面的权重值就一定小于等于当前值,就可以一次移动多个位置。随机权重值 /  当前权重值 = 3,下次可以直接移动3个位置。

时间复杂度:O(N)

过程:

1、先计算出所有道具的权重总和S

2、然后调用随机函数得到一个区间在[1, S]的随机值N

3、重建权重表,列表中每个权重是前面所有权重值的总和,得到有序权重表

3、通过n / 当前权重,得到的值J大于0,则是J是要跳跃得阶级数(因为有序,后面的权重肯定比当前小)

  1. # 测试数据
  2. # [(奖励ID: 权重)]
  3. pool_data = [
  4. (1, 50),
  5. (2, 20),
  6. (3, 40),
  7. (4, 10),
  8. ]
  9. def jump_random(pool_data):
  10. sort_pool = sorted(pool_data, key=lambda a: a[1], reverse=True)
  11. add = 0
  12. pool = []
  13. for key, weight in sort_pool:
  14. pool.append((key, weight + add))
  15. add += weight
  16. sum_weight = add
  17. pool_length = len(pool)
  18. n = random.randint(1, sum_weight)
  19. i = 0
  20. while i < pool_length - 1:
  21. key, weight = pool[i]
  22. if weight > n:
  23. return key
  24. else:
  25. multiple = n // weight
  26. i += multiple
  27. return pool[i][0]

方法三:有序的二分查找

    区别于前面的方法,将权重去累加得到一个递增权重表,再随机出一个目标权重值。因为递增,那就可以使用二分查找

时间复杂度:O(LogN)

过程:

1、先计算出所有道具的权重总和S,

2、然后调用随机函数得到一个区间在[1, S]的随机值N

3、重建权重表,列表中每个权重是前面所有权重值的总和,得到有序权重表

3、根据N在有序权重表二分查找

  1. # 测试数据
  2. # [(奖励ID: 权重)]
  3. pool_data = [
  4. (1, 50),
  5. (2, 20),
  6. (3, 40),
  7. (4, 10),
  8. ]
  9. def binary_random(pool_data):
  10. add = 0
  11. pool = []
  12. for k, w in pool_data:
  13. pool.append((k, w + add))
  14. add += w
  15. """
  16. pool= [
  17. (1, 50),
  18. (2, 20 + 50),
  19. (3, 20 + 50 + 40),
  20. (4, 20 + 50 + 40 + 10),
  21. ]
  22. """
  23. sum_weight = add
  24. n = random.randint(1, sum_weight)
  25. mid, left, right, = 0, 0, len(pool) - 1
  26. while left < right: # 二分查找
  27. mid = (right + left) // 2
  28. key, mid_num = pool[mid]
  29. if mid_num < n:
  30. left = mid + 1
  31. elif mid_num > n:
  32. right = mid
  33. else:
  34. return key
  35. return pool[mid][0]


 

方式四、Alias method/别名采样方法

时间复杂度O(1)

待写。。。

多次抽取

从一个奖池多次抽取奖励,并且个奖励ID只能出现一次。

很简单,每次抽完就把总权重减去当前抽中ID的权重,再在新的总权重中去随机生成权重值。

可以用字典来实现删减操作。

  1. # {奖励ID: 权重}
  2. pool_dict = {
  3. 1: 10,
  4. 2: 20,
  5. 3: 30,
  6. 4: 40,
  7. }
  8. def times_linear(pool_dict, cnt):
  9. pool = pool_dict.copy()
  10. sum_weight = sum(pool.values())
  11. rst = []
  12. for _ in range(cnt):
  13. n = random.randint(1, sum_weight)
  14. for key, weight in pool.items():
  15. if n <= weight:
  16. rst.append(key)
  17. sum_weight -= weight
  18. del pool[key]
  19. break
  20. else:
  21. n -= weight
  22. return rst

总结:抽取量大、权重较多的时候做优化还是非常必要的。

参考文章:

【数学】时间复杂度O(1)的离散采样算法—— Alias method/别名采样方法_haolexiao的专栏-CSDN博客_alias算法

基于权重 的随机选择算法 - 知乎

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/496265
推荐阅读
相关标签
  

闽ICP备14008679号