编写一个具有以下功能的函数:
input: array of pairs (unique id and weight) length of N, K =< N
output: K random unique ids (from input array)
注意:在输出中出现一些Id的频率被多次调用应该越大,它的重量就越大。 示例:权重为5的id应出现在输出中比id为1的频率多5倍。此外,分配的内存量应在编译时知道,即不应分配额外的内存。
我的问题是:如何解决这个问题?
修改
感谢大家的回复!
目前我无法理解对的重量如何影响输出对的出现频率,你能不能给我更清楚,“虚拟”解释它是如何工作的?
答案 0 :(得分:7)
假设一个足够好的随机数生成器:
total_weight
)total_weight
(selection
)selection
您需要足够的存储空间来存储总重量。
答案 1 :(得分:5)
好的,所以你得到的输入如下:
(3, 7)
(1, 2)
(2, 5)
(4, 1)
(5, 2)
并且您想要选择一个随机数,以便每个ID的权重反映在拣选中,即从以下列表中选择一个随机数:
3 3 3 3 3 3 3 1 1 2 2 2 2 2 4 5 5
最初,我创建了一个临时数组,但这也可以在内存中完成,你可以通过将所有权重加起来来计算列表的大小= X,在这个例子中= 17
在[0,X-1]之间选择一个随机数,并通过循环遍历列表来计算应该返回哪个id,对权重进行累加。假设我有一个随机数8
(3, 7) total = 7 which is < 8
(1, 2) total = 9 which is >= 8 **boom** 1 is your id!
现在,由于您需要K随机唯一 ID,因此您可以从传递给您的初始数组创建哈希表。找到id后,将其从哈希中删除并继续算法。 修改请注意,您最初只创建一次hashmap!您的算法将在此处理,而不是查看数组。我没有放在顶部以保持答案清晰
只要您的随机计算没有秘密使用任何额外的内存,您将需要存储K个随机选择,其中&lt; = N和原始数组的副本,因此运行时的最大空间要求为O(2 * N)
渐近运行时是:
O(n) : create copy of original array into hastable +
(
O(n) : calculate sum of weights +
O(1) : calculate random between range +
O(n) : cumulative totals
) * K random pickings
= O(n*k) overall
这是一个很好的问题:)
答案 2 :(得分:3)
此解决方案适用于非整数权重并使用常量空间(即:空间复杂度= O(1))。但它确实修改了输入数组,但最终的唯一区别是元素的顺序不同。
将每个输入的重量添加到以下输入的重量,从底部开始向上。现在每个权重实际上是输入权重和所有先前权重的总和。
sum_weights =所有权重的总和,n = N.
K次:
选择[0,sum_weights]范围内的随机数r
二元搜索第一个槽的前n个元素,其中(现在求和)权重大于或等于r,i。
将input [i] .id添加到输出。
从input [i] .weight减去输入[i-1] .weight(除非i == 0)。现在将input [i] .weight从以下(&gt; i)输入权重和sum_weight减去。
将输入[i]移动到位置[n-1](将插入元素向下滑动一个插槽)。这是昂贵的部分,因为它是O(N),我们做了K次。您可以在最后一次迭代中跳过此步骤。
从n
通过减去前面输入的重量,将所有权重从n-1减少到1
时间复杂度为O(K * N)。 (时间复杂性的)昂贵部分正在改变所选元素。我怀疑有一种聪明的方法可以避免这种情况,但还没有想到任何东西。
目前还不清楚“输出:K随机唯一ID”是什么意思。上面的解决方案假设这意味着输出id应该是唯一/不同的,但如果不是这样,那么问题就更简单了:
将每个输入的重量添加到以下输入的重量,从底部开始向上。现在每个权重实际上是输入权重和所有先前权重的总和。
sum_weights =所有权重的总和,n = N.
K次:
选择[0,sum_weights]范围内的随机数r
二元搜索第一个槽的前n个元素,其中(现在求和)权重大于或等于r,i。
将input [i] .id添加到输出。
通过减去前面输入的重量,将所有权重从n-1减少到1
时间复杂度为O(K * log(N))。
答案 3 :(得分:2)
我的简短回答:绝不是。
仅仅因为问题定义不正确。正如Axn出色地注意到:
要求中存在一些矛盾。它表明K <= N.但是当K接近N时,频率要求将与唯一性要求相矛盾。最坏的情况是,如果K = N,所有元素都将被返回(即以相同的频率出现),而不管它们的重量。
无论如何,当K相对于N非常小时,计算的频率将非常接近理论值。
任务可以分为两个子任务:
sumOfWeights
)[1; sumOfWeights]
代码
#include <iostream>
#include <cstdlib>
#include <ctime>
// 0 - id, 1 - weight
typedef unsigned Pair[2];
unsigned Random(Pair* i_set, unsigned* i_indexes, unsigned i_size)
{
unsigned sumOfWeights = 0;
for (unsigned i = 0; i < i_size; ++i)
{
const unsigned index = i_indexes[i];
sumOfWeights += i_set[index][2];
}
const unsigned random = rand() % sumOfWeights + 1;
sumOfWeights = 0;
unsigned i = 0;
for (; i < i_size; ++i)
{
const unsigned index = i_indexes[i];
sumOfWeights += i_set[index][3];
if (sumOfWeights >= random)
{
break;
}
}
return i;
}
它需要N个字节的空间,因此如果在编译时定义了N值,我们就可以在编译时分配必要的空间。
现在,我们必须将这两种算法结合起来。我们只需要在唯一数字生成算法中使用我们自己的Random()
函数而不是标准rand()
。
代码
template<unsigned N, unsigned K>
void Generate(Pair (&i_set)[N], unsigned (&o_res)[K])
{
unsigned deck[N];
for (unsigned i = 0; i < N; ++i)
{
deck[i] = i;
}
unsigned max = N - 1;
for (unsigned i = 0; i < K; ++i)
{
const unsigned index = Random(i_set, deck, max + 1);
std::swap(deck[max], deck[index]);
o_res[i] = i_set[deck[max]][0];
--max;
}
}
int main()
{
srand((unsigned)time(0));
const unsigned c_N = 5; // N
const unsigned c_K = 2; // K
Pair input[c_N] = {{0, 5}, {1, 3}, {2, 2}, {3, 5}, {4, 4}}; // input array
unsigned result[c_K] = {};
const unsigned c_total = 1000000; // number of iterations
unsigned counts[c_N] = {0}; // frequency counters
for (unsigned i = 0; i < c_total; ++i)
{
Generate<c_N, c_K>(input, result);
for (unsigned j = 0; j < c_K; ++j)
{
++counts[result[j]];
}
}
unsigned sumOfWeights = 0;
for (unsigned i = 0; i < c_N; ++i)
{
sumOfWeights += input[i][1];
}
for (unsigned i = 0; i < c_N; ++i)
{
std::cout << (double)counts[i]/c_K/c_total // empirical frequency
<< " | "
<< (double)input[i][1]/sumOfWeights // expected frequency
<< std::endl;
}
return 0;
}
N = 5, K = 2
Frequencies
Empiricical | Expected
0.253813 | 0.263158
0.16584 | 0.157895
0.113878 | 0.105263
0.253582 | 0.263158
0.212888 | 0.210526
实际忽略权重时的角落情况
N = 5, K = 5
Frequencies
Empiricical | Expected
0.2 | 0.263158
0.2 | 0.157895
0.2 | 0.105263
0.2 | 0.263158
0.2 | 0.210526
答案 4 :(得分:1)
我确实假设输出中的ID必须是唯一的。这使得这个问题成为随机抽样问题的一个特定实例。
我能想到的第一种方法是在O(N ^ 2)时间内使用O(N)存储器(输入数组本身加常量存储器)解决这个问题。 我假设权重是正确的。
设A是对的数组。
1)将N设为A.length
2)计算所有权重W的总和。
3)循环K次
3.1)r = rand(0,W)
3.2)在A上循环并找到第一个索引i,使得A [1] .w + ... + A [i] .w&lt; = r&lt; A [1] .w + ... + A [i + 1] .w
3.3)将A [i] .id添加到输出
3.4)A [i] = A [N-1](如果应保留数组内容,则交换)
3.5)N = N - 1
3.6)W = W - A [i] .w