【问题标题】:Weighted median computation加权中值计算
【发布时间】:2012-04-05 09:04:14
【问题描述】:

我正在寻找有关 C++ 中加权中值算法和/或示例代码计算的良好学习材料。我的中位数的权重是 0 到 1 之间的值。你能给我推荐一些链接吗?

【问题讨论】:

  • 您是否有一对值[x, y] 并喜欢在y 是您的体重时取加权中位数?请详细说明您的问题。
  • 我曾尝试使用 Boost 库实现,但我想更深入地了解这个算法,因为我需要在我的案例中设计这个解决方案的特殊变体。
  • 我需要找到一个值,它可以最小化所谓的加权分类错误。所以我有一对值 [error, weights],其中值是自然数,但权重是 0 到 1 之间的分数。我读过我可以使用加权中值算法在线性时间内找到最小值...

标签: c++ algorithm


【解决方案1】:

加权中位数的定义如下:

如果xN 元素的排序数组,而w 是总权重为W 的权重数组,则加权中位数是最后一个x[i],使得@ 的总和987654327@ 和之前所有的权重都小于或等于S/2

在 C++ 中,可以这样表示(假设 xwW 定义如上)

double sum = 0;
int i;
for(i = 0; i < N; ++i)
{
    sum += w[i];
    if(sum > W/2)
        break;
}
double median = x[i-1];

编辑

看来我回答这个问题太仓促了,犯了一些错误。我从R documentation 中找到了对加权中位数的简洁描述,它是这样描述的:

对于n 元素x = c(x[1], x[2], ..., x[n]) 带有正数 权重w = c(w[1], w[2], ..., w[n]) 使得sum(w) = S, 加权中位数定义为元素x[k],其初始 所有元素的总权重x[i] &lt; x[k] 小于或等于S/2 并且所有元素的总权重x[i] &gt; x[k] 小于 或等于S/2

根据这个描述,我们有一个非常直接的算法实现。如果我们以k == 0开头,那么x[k]之前没有元素,所以元素x[i] &lt; x[k]的总权重会小于S/2。根据数据,元素x[i] &gt; x[k] 的总权重可能小于也可能不小于S/2。所以我们可以在数组中向前移动,直到第二个总和小于或等于S/2

#include <cstddef>
#include <numeric>
#include <iostream>

int main()
{
  std::size_t const N = 5;
  double x[N] = {0, 1, 2, 3, 4};
  double w[N] = {.1, .2, .3, .4, .5};

  double S = std::accumulate(w, w+N, 0.0); // the total weight

  int k = 0;
  double sum = S - w[0]; // sum is the total weight of all `x[i] > x[k]`

  while(sum > S/2)
  {
    ++k;
    sum -= w[k];
  }

  std::cout << x[k] << std::endl;
}

注意如果中位数是最后一个元素(medianIndex == N-1),那么sum == 0,所以条件sum &gt; S/2不成立。因此,k 永远不会越界(除非N == 0!)。另外,如果有两个元素满足条件,算法总是选择第一个。

【讨论】:

  • 可爱。我想严格来说它是> =?或者在平等的情况下,你是指那个和下面的意思吗?还是我有强迫症? ;o)
  • @andrewcooke:我的代码是正确的,我的描述有点错误。它已被修复。如果你真的想要强迫症,可以使用许多中位数。实际上,[x[i-1], x[i]) 范围内的任何值都是中位数。
  • 哦,我错过了i-1。那么如果 w[0] 为 0.9 会发生什么? i 是否在休息时增加(否则你有x[-1])?将 sum 初始化为 w[0] 并从 1 开始循环可能会更好?不,这似乎也不对。对不起,可能会混淆。反正我知道你的意思。
  • @KenWayneVanderLine。谢谢你。你确定你的算法是正确的吗?我知道对于所有权重等于 1/n 的 n 个元素,加权中位数应该是“通常的中位数”。所以对于 {1,2,3,4,5}, {1/5, 1/5,1/5,1/5,1/5} 它应该是“3”。你的算法给了我“2”。同样,对于具有相同权重的数字 {1,2,3,4,5,6} 的偶数值,它应该是 3 和 4(字面意思是 (3+4)/2),但是你的算法给了我一个“2”。所以也许不是 x[i-1] 而是 x[i]?如果我做错了什么,请纠正我。
  • @Viper:是的,我搞砸了,所以每次索引都会加一。我添加了新的、正确的实现。至于您关于平均 3 和 4 的观点,这只是一种选择。实际上,您可以使用 3 到 4(包括)之间的任何值作为中位数 - 我的代码总是会输出 3,但这应该很容易自定义。
【解决方案2】:

这是初始未排序向量的加权中位数的实现。它建立在@Ken Wayne VanderLinde 对中位数计算的非常好的回答以及this thread 中给出的索引排序器之上。

    template <typename VectorType>
    auto sort_indexes(VectorType const& v)
    {
        std::vector<int> idx(v.size());
        std::iota(std::begin(idx), std::end(idx), 0);

        std::sort(std::begin(idx), std::end(idx), [&v](int i1, int i2) {return v[i1] < v[i2];});

        return idx;
    }

    template<typename VectorType1, typename VectorType2>
    auto weightedMedian(VectorType1 const& x, VectorType2 const& weight)
    {
        double totalWeight = 0.0;
        for (int i = 0; i < static_cast<int>(x.size()); ++i)
        {
            totalWeight += weight[i];
        }

        auto ind = sort_indexes(x);

        int k = ind[0];
        double sum = totalWeight - weight[k];

        for (int i = 1; i < static_cast<int>(ind.size()); ++i)
        {
            k = ind[i];
            sum -= weight[k];

            if (sum <= 0.5 * totalWeight)
            {
                break;
            }
        }
        return x[k];
    }

它适用于任何支持operator[](int)size() 的向量类型(--因此不使用std::accumulate 等)。

【讨论】:

  • 它不适用于以下情况:elements=[1,2], weights=[100, 50]。意思是,如果结果应该是第一个元素,它将不起作用。你可以很容易地解决这个问题,从 0 开始循环,初始化总和到 totalWeight,只需在循环外声明 k 并验证向量不为空。
  • @Jonathan:感谢您的测试和建议的更正 -- 请随时将其编辑到代码中。
猜你喜欢
  • 1970-01-01
  • 2021-11-24
  • 2016-12-14
  • 2021-11-21
  • 2017-04-22
  • 1970-01-01
  • 1970-01-01
  • 2010-10-04
  • 1970-01-01
相关资源
最近更新 更多