C ++:使用数组创建函数

时间:2010-11-30 20:53:11

标签: c++

编写一个具有以下功能的函数:

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倍。此外,分配的内存量应在编译时知道,即不应分配额外的内存。

我的问题是:如何解决这个问题?

修改
感谢大家的回复!
目前我无法理解对的重量如何影响输出对的出现频率,你能不能给我更清楚,“虚拟”解释它是如何工作的?

5 个答案:

答案 0 :(得分:7)

假设一个足够好的随机数生成器:

  • 总和权重(total_weight
  • 重复K次:
    • 选择0到total_weightselection
    • 之间的数字
    • 找到第一对,其中从数组开头到该对的所有权重之和大于或等于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

    • 中减去1
  • 通过减去前面输入的重量,将所有权重从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非常小时,计算的频率将非常接近理论值。

任务可以分为两个子任务:

  1. 生成具有给定分布(由权重指定)的随机数
  2. 生成唯一的随机数
  3. 生成具有给定分布的随机数

    1. 计算权重之和(sumOfWeights
    2. [1; sumOfWeights]
    3. 范围生成随机数
    4. 查找数组元素,其中数组开头的权重总和大于或等于生成的随机数
    5. 代码

      #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;
      }
      

      生成唯一的随机数

      众所周知的Durstenfeld-Fisher-Yates算法可用于生成唯一的随机数。请参阅this great explanation

      它需要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