lua实现游戏后端抽奖的几种方法

^_^内容原创,禁止转载


 假设配置格式如下:

1 local reward_pool = {
2     {weight = 1000, item = {type = 100218, num = 12}}, 
3     {weight = 1000, item = {type = 100218, num = 12}},
4     {weight = 1000, item = {type = 100218, num = 12}}, 
5     {weight = 1000, item = {type = 100218, num = 12}},
6     {weight = 1000, item = {type = 100218, num = 12}}, 
7     {weight = 1000, item = {type = 100218, num = 12}},
8 }

1. 顺序查找方式,预处理时间复杂度O(n),抽奖最坏情况O(n),这是最为直观简单的一种抽奖实现,总体思路:生成一个1到总权重之间的随机数,以上述配置为例,[1, 1000]表示抽到第一个物品,[1001, 2000]表示抽到第二个物品,采用顺序遍历的方式,看随机数落在哪个区间内,便得出相应物品,下面是代码实现:

 1 --预处理
 2 local N = #reward_pool
 3 local total_weight = 0
 4 for _, v in ipairs(reward_pool) do
 5     total_weight = total_weight + v.weight
 6 end
 7 
 8 --实现
 9 local rand_weight = math.random(total_weight)
10 local reward_index
11 local _total_weight = 0
12 for k, v in ipairs(reward_pool) do
13     _total_weight = _total_weight + v.weight
14     if _total_weight >= rand_weight then
15         reward_index = k
16         break
17     end
18 end

 2. 按照离散思路对权重进行分配,预处理时间复杂度O(n),抽奖最坏情况O(logn),总体思路:此方法实质上是第一种方法的优化,将当前配置的权重设置为前面所有权重包括自身权重之和,采用二分查找的方式,获取抽奖结果,下面是代码实现:

 1 --预处理
 2 local N = #reward_pool
 3 local com_weight = 0
 4 for _, v in ipairs(reward_pool) do
 5     com_weight = com_weight + v.weight
 6     v.weight = com_weight
 7 end
 8 
 9 --实现
10 local left, right = 1, #reward_pool
11 while right >= left then
12     local mid = math.floor((left + right) / 2)
13     local mid_weight = reward_pool[mid].weight
14     if value == mid_weight then
15         right = right - 1
16         break
17     elseif value < mid_weight then
18         right = mid - 1
19     else
20         left = mid + 1
21     end
22 end
23 right = right + 1 --此时right为reward_pool中抽到的索引

此方法在大多数情况下都可以取代第一种方式,但如果只从效率上考虑的话还要看实际情况,因为抽奖权重配置大多数都是“头重脚轻”,一个极端且明显的例子如下,读者自行体会^_^:

1 local reward_pool = {
2     {weight = 1000, item = {type = 100218, num = 12}}, {weight = 1000, item = {type = 100218, num = 12}},
3     {weight = 1, item = {type = 100218, num = 12}}, {weight = 1, item = {type = 100218, num = 12}},
4     {weight = 1, item = {type = 100218, num = 12}}, {weight = 1, item = {type = 100218, num = 12}},
5     {weight = 1, item = {type = 100218, num = 12}}, {weight = 1, item = {type = 100218, num = 12}},
6     {weight = 1, item = {type = 100218, num = 12}}, {weight = 1, item = {type = 100218, num = 12}},
7 }

3. AliasMethod,个人实现的预处理时间复杂度O(3n),抽奖时间复杂度O(1),该方法事先对权重进行采样,在抽奖过程中仅需生成两个随机数便可获得抽奖结果,网上对AliasMethod有着较为详细的介绍,读者可自行百度了解具体理论,这里只给出实现的具体思路:

  • Prob:当前事件在当前矩形所占权重;Alias:记录当前矩形中另一事件下标
  • 遍历配置,得出配置数为N,总权重为total_weight,N <= total_weight
  • 再次遍历,将权重*N,如果比total_weight大,放到队列qU,如果比total_weight小,放到队列qL,如果相等,则以其下标在Prob赋值
  • 从队列qL取出数l,从队列qU取出数u,从数u取出一部分值sub_value,使得l + sub_value = total_weight,Prob以数l下标记录数l,Alias以数l下标记录数u下标,将数u进行下划线逻辑
  • 重复上条逻辑,直至qL队列为空,qL为空,qU也必为空
  • 抽奖实现,生成随机数a,b,如果b不大于Prob[a],返回a,否则返回Alias[a]
 1 queue = {}
 2 
 3 function queue:new()
 4     local res = {first = 0, last = -1}
 5     self.__index = self
 6     setmetatable(res, self)
 7     return res
 8 end
 9 
10 function queue:push(value)
11     self.last = self.last + 1
12     self[self.last] = value
13 end
14 
15 function queue:pop()
16     local first = self.first
17     if first > self.last then
18         self.first = 0
19         self.last = -1
20         return nil
21     end
22     local value = self[first]
23     self[first] = nil
24     self.first = self.first + 1
25     return value
26 end
27 
28 function queue:front()
29     return self[self.first]
30 end
31 
32 --预处理
33 local N = #reward_pool
34 local total_weight = 0
35 for _, v in ipairs(reward_pool) do
36     total_weight = total_weight + v.weight
37 end
38 
39 local Prob = {}
40 local Alias = {}
41 local weightN_queue_L = queue:new()
42 local weightN_queue_U = queue:new()
43 for k, v in ipairs(reward_pool) do
44     local weight_N = v.weight * N
45     if weight_N == total_weight then
46         Prob[k] = weight_N
47     else
48         local tb = {index = k, value = weight_N}
49         local qu = weight_N > total_weight and weightN_queue_U or weightN_queue_L
50         qu:push(tb)
51     end
52 end
53 
54 while true do
55     local l_qu = weightN_queue_L:pop()
56     if not l_qu then
57         break
58     end
59     local u_qu = weightN_queue_U:front() --或直接pop,比total_weight大再push回去
60     Prob[l_qu.index] = l_qu.value
61     Alias[l_qu.index] = u_qu.index
62     u_qu.value = u_qu.value + l_qu.value - total_weight
63     if u_qu.value < total_weight then
64         weightN_queue_U:pop()
65         weightN_queue_L:push(u_qu)
66     elseif u_qu.value == total_weight then
67         weightN_queue_U:pop()
68         Prob[u_qu.index] = total_weight
69     end
70 end
71 weightN_queue_U = nil
72 weightN_queue_L = nil
73 
74 --实现
75 local n = math.random(N)
76 local weight = math.random(total_weight)
77 local reward_index = weight > Prob[n] and Alias[n] or n

 

posted @ 2020-04-29 19:32  我的超级帅发型丶  阅读(1137)  评论(0编辑  收藏  举报