递归填充动态大小向量

时间:2011-06-15 07:42:58

标签: c++ recursion vector divide-and-conquer

也许让我在伪C ++中陈述我的情况 - 首先是代码:

std:vector<double> sample(someFunctor f, double lower, double upper) {
    double t = (lower + upper)/2;
    double newval = f(t);

    if (f(upper) - newval > epsilon)
        subsample1 = sample(f, t, upper);
    if (newval - f(lower) > epsilon)
        subsample2 = sample(f, lower, t);

    return concat(subsample2, newval, subsample1);
}

其中concat,好吧,汇总返回的向量。基本上,我正在以一种方式对函数进行采样,使得在两个保存的函数值之间只有很小的差异。

我对上述方式不满意,因为在每个递归步骤中似乎都有相当多的内存分配(分配两个子向量,然后连接那些和另一个元素)。这段代码必须在我的算法的一部分中运行,这对性能至关重要。 upper - lower相当小后,评估f不会花费大量时间。

所以我的问题:

  • 您是否看到了在所有递归调用中使用相同数据结构的巧妙方法,并且只填充该向量的当前部分? (请记住,所需的功能评估数量尚未预先知道)。对此的想法:

    • 使用列表而不是向量。但我觉得内存检修不足以存储双打。
    • 在向量中保留洞并保留另一个向量,说明填充了哪些条目。递归调用的结束会使条目移位,以便subsamplenewval之间没有空洞。但是现在我通过为第二个向量添加额外的工作来切换复制 - 可能是个坏主意。
  • 你是否看到了完全摆脱递归的方法?但是,为了正确起见,我必须使用上述分而治之的模式。函数f大量使用上限和下限,并获得了相当大的性能。

感谢您的想法。


根据Space_C0wb0y的请求,我试着改写一下我的问题。也许第一个解释不是很清楚。

我有一些功能(在数学意义上)我想在给定的间隔内采样(例如在某些点评估)。

假设间隔为[0,100]。我知道函数值在0和100处。可能是f(0)=0f(100) = 40。 现在我在间隔中点评估函数,即50.我的函数返回f(50)=10。 如f(0)-f(50) <= 10,我在区间[0,50]中不需要进一步的样本。但是,我需要进一步计算间隔[50,100]。因此,在下一个(递归)步骤中,我评估f(75)。现在递归地重复上述逻辑。

最后我想(两个)向量给我带有相应参数的函数值,如下所示:

parameter  = vector(0, 50, 56.25, 62.5, 75, 100)
value      = vector(0, 10, 17.21, 25    34,  40)

我正在寻找以递归方式构建这些向量的最佳(最高效)方法。

希望这能澄清事情。

3 个答案:

答案 0 :(得分:2)

由于空间不是您的主要关注点,因此我将继续使用递归。

<强> 1。按引用使用副本而不是按(返回)值复制。

<强> 2。不需要传递仿函数,因为它不变。

第3。如果lowhigh是整数,则可能会更快。这取决于要求。

    // Thanks to Space_C0wb0y, here we avoid using a global vector
    // by passing the vector as reference. It's efficient as there
    // is no copy overhead as well.        
    void sample(vector<double>& samples, double low, double high)
    {
       // You can use shift operator if they're integers.
       double mid = (low + high)/2;

       // Since they're double, you need prevent them from being too close.
       // Otherwise, you'll probably see stack overflow.
       // Consider this case:
       // f(x): x=1, 0<x<8;  x*x, x<=0 or x>=8
       // low = 1, high = 10, epsilon = 10
       if (high - low < 0.5)
       {
          samples.push_back(f(mid));
          return;
       }   

       // The order you write the recursive calls guarantees you
       // the sampling order is from left to right.
       if (f(mid) - f(low) > epsilon)
       {
          sample(samples, low, mid);
       }

       samples.push_back(f(mid));

       if (f(high) - f(mid) > epsilon)
       {
          sample(samples, mid, high);
       }   
    }

答案 1 :(得分:1)

我建议采用以下方法:

  1. 不要使用两个向量,而是使用一个带有对的向量或自定义struct来表示参数和值:

    struct eval_point {
        double parameter;
        double value;
    };
    
    std::vector<eval_point> evaluated_points;
    
  2. 更改算法以将评估结果写入输出迭代器:

    template<class F, class output_iterator_type>
    void sample(F someFunctor, double lower, double upper,
                output_iterator_type out) {
        double t = (lower + upper)/2;
        eval_point point = { t, f(t) };
    
        if (f(upper) - point.value > epsilon) {
            *out = point;
            ++out;
            sample(f, t, upper, out);
        }
        if (point.value - f(lower) > epsilon) {
            *out = point;
            ++out;
            subsample2 = sample(f, lower, t, out);
        }
    }
    

    以上是对伪代码的修改,显示了使用输出迭代器时的样子。它没有经过测试,所以我不确定它是否正确。原则上,你会这样称呼它:

    std::vector<eval_point> results;
    sample(someFunction, 0, 100, std::back_inserter<eval_point>(results));
    

    这样您就不必为每个递归调用创建新的向量。如果您可以猜测样本数量的合理下限,则可以预先分配,以便不需要重新分配。在这种情况下,你会这样称呼它:

    std::vector<eval_point> results(lower_bound_for_samples);
    sample(someFunction, 0, 100, results.begin());
    
  3. 然后,您必须添加一个额外的计数器来跟踪生成的样本数量。

答案 2 :(得分:0)

我不明白为什么拒绝列表解决方案。 最糟糕的情况是您的列表的大小是原始数据的3倍。 我认为这比你在每个函数调用上创建一个新向量时要少得多。 您应该尝试一下,因为它不需要那么多的改变,因为两者的界面几乎相同。