C ++快速百分位数计算

时间:2015-10-27 11:16:04

标签: c++ vector percentile

我试图编写一个百分位函数,它将2个向量作为输入,1个向量作为输出。输入向量之一(Distr)将是随机数的分布。另一个输入向量(测试)将是我想要从Distr计算百分位数的值向量。输出将是一个向量(与测试大小相同),它返回测试中每个值的百分位数。

以下是我想要的例子:

Input Distr = {3, 5, 8, 12}
Input Tests = {4, 9}
Output Percentile = {0.375, 0.8125}

以下是我在C ++中的实现:

vector<double> Percentile(vector<double> Distr, vector<double> Tests)
{
    double prevValue, nextValue;
    vector<double> result;
    unsigned distrSize = Distr.size();

    std::sort(Distr.begin(), Distr.end());

    for (vector<double>::iterator test = Tests.begin(); test != Tests.end(); test++)
    {

        if (*test <= Distr.front())
        {
            result.push_back((double) 1 / distrSize); // min percentile returned (not important)
        }
        else if (Distr.back() <= *test)
        {
            result.push_back(1); // max percentile returned (not important)
        }
        else
        {
            prevValue = Distr[0];
            for (unsigned sortedDistrIdx = 1; sortedDistrIdx < distrSize; sortedDistrIdx++)
            {
                nextValue = Distr[sortedDistrIdx];

                if (nextValue <= *test)
                {
                    prevValue = nextValue;
                }
                else
                {
                    // linear interpolation
                    result.push_back(((*test - prevValue) / (nextValue - prevValue) + sortedDistrIdx) / distrSize);
                    break;
                }
            }
        }
    }
    return result;
}

Distr和Tests的大小可以从2,000到30,000。

是否有任何现有的库可以计算百分位数如上所示(或类似)?如果不能,我怎样才能更快地完成上述代码?

4 个答案:

答案 0 :(得分:0)

对于每个测试元素,Distr的线性搜索将是主要的时间量,如果这两个元素都很大。

当Distr大得多时,进行二分搜索而不是线性搜索要快得多。 std中有一个二进制搜索算法。你不需要写一个。

当测试几乎和Distr一样大或更大时,执行索引排序测试然后按顺序排列两个排序列表一起存储结果,然后在下一次传递中输出存储的结果更快。

编辑:我看到Csaba Balint的回答更详细地说明了我的意思&#34;将两个排序列表排在一起&#34;。

编辑:讨论的基本方法是:
1)对两个列表进行排序,然后线性地一起处理,时间NlogN + MlogM
2)只对一个列表和二分搜索进行排序,时间(N + M)logM
3)只排序其他列表和分区,时间我还没弄清楚,但是在N和M类似的情况下,它必须比方法1或2更大,并且在N足够小的情况下必须小于方法1或2。

答案 1 :(得分:0)

您的问题有一个线性算法(两种尺寸的线性时间对数)。您需要对两个向量进行排序,然后使用两个迭代器(itDistritTest)。有三种可能性:

1。     * itDistr&lt; * itTest

除了增加itDistr之外,你没有任何其他内容。

2。     * itDistr&gt; = * itTest

当您找到* itTest是区间[ *(itDistr-1), *itDistr )的元素的测试用例时,会出现这种情况。所以你必须进行你使用的插值(线性),然后增加itTest

第三种可能性是其中任何一个到达其容器向量的末尾。您还必须定义在开头和结尾处发生的事情,这取决于您从数字系列中定义分布的方式。

  

是否存在可以计算百分位数的现有库,如上所示(或类似)?

可能,但很容易实现它,你可以很好地控制插值技术。

答案 2 :(得分:0)

此答案与input最初是随机的(未排序)且test.size()小于input.size()的情况相关,这是最常见的情况。

假设只有一个测试值。然后,您只需要根据此值input进行分区,并获取较低(较高)分区的上限(下限)以计算相应的百分位数。这比输入的完全排序要快得多(快速排序实现为分区的递归)。

如果test.size()>1,那么您首先排序test(理想情况下,test已经排序,您可以跳过此步骤),然后每次按递增顺序继续测试元素仅将上部分区与前一个分区分开。由于我们还跟踪上部分区的下限(以及下部分区的上限),我们可以检测连续测试元素之间是否没有输入数据,并避免分区。

这个算法应该接近最优,因为没有生成不必要的信息(就像完整的input那样)。

如果后续分区将输入大致分成两半,则算法将是最佳的。这可以通过不按test的递增顺序进行近似,而是通过随后的test减半,即从中值测试元素开始,然后是第一个&amp;第三等四等。

答案 3 :(得分:0)

我会做像

这样的事情
vector<double> Percentile(vector<double> Distr, vector<double> Tests)
{
    double prevValue, nextValue;
    vector<double> result;
    unsigned distrSize = Distr.size();

    std::sort(Distr.begin(), Distr.end());

    for (vector<double>::iterator test = Tests.begin(); test != Tests.end(); test++)
    {
        if (*test <= Distr.front())
        {
            result.push_back((double) 1 / distrSize); // min percentile returned (not important)
        }
        else if (Distr.back() <= *test)
        {
            result.push_back(1); // max percentile returned (not important)
        }
        else
        {
            auto it = lower_bound(Distr.begin(), Distr.end(), *test);
            prevValue = *(it - 1);
            nextValue = *(it + 1);
            // linear interpolation
            result.push_back(((*test - prevValue) / (nextValue - prevValue) + (it - Distr.begin())) / distrSize);
        }
    }
    return result;
}

请注意,我不是对每个测试的 Distr 进行线性搜索,而是利用 Distr 进行排序并进行二进制搜索的事实(使用 LOWER_BOUND )。