递归填充动态大小向量

Fill dynamic size vector recursively

本文关键字:向量 动态 填充 递归      更新时间:2023-10-16

也许让我先用伪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只是,嗯,对返回的向量进行concat。基本上,我对函数进行采样的方式是,在两个保存的函数值之间只有很小的差异。

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

所以我的问题是:

  • 你看到在所有递归调用中使用相同数据结构并只填充该向量的当前部分的聪明方法了吗?(请记住,所需的功能评估数量并不预先知道)。对此的思考:

    • 使用列表而不是矢量。但我觉得,仅仅存储替身是不够的
    • 保留向量中的孔,并保留另一个向量,说明哪些条目已填充。A递归调用结束时,移位条目,使subsamplenewval之间没有孔。但现在我通过对第二个向量进行额外的工作来切换复制——这可能是个坏主意
  • 你看到一种完全摆脱递归的方法了吗?然而,为了正确起见,我使用上述分而治之的模式是很重要的。函数f充分利用了上界和下界,并因此获得了相当大的性能。

谢谢你的想法。


根据Space_C0wb0y的请求,让我试着重新表述我的问题。也许第一个解释不是很清楚。

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

假设该间隔为[0100]。我知道0和100处的函数值。可能是f(0)=0f(100) = 40。现在,我在区间中点处评估函数,即50。比方说,我的函数返回f(50)=10。作为f(0)-f(50) <= 10,我不需要在区间[0,50]中进一步采样。然而,我需要对区间[5100]进行进一步的计算。因此,在下一个(递归)步骤中,我评估f(75)。现在递归地重复上面的逻辑。

最后,我想(两个)向量给我函数值,对应的参数如下:

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

我正在寻找最好的(也是最具性能的)方法来递归地构建这些向量。

希望这能澄清问题。

由于空间不是您主要关心的问题,所以我将继续使用递归。

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. 不要使用两个矢量,而是使用一个成对的矢量或自定义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倍。我认为这远远低于在每个函数调用上创建一个新向量时的情况。您应该尝试一下,因为它不需要太多更改,因为两者的接口几乎相同。