如何从有序地图中获取中值

时间:2010-08-10 06:04:53

标签: c++ stl

我正在使用std :: map。有时我会做一个操作:找到所有项目的中值。例如 如果我添加

1 "s"
2 "sdf"
3 "sdfb"
4 "njw"
5 "loo"

然后中位数是3.

是否有一些解决方案没有迭代地图中超过一半的项目?

10 个答案:

答案 0 :(得分:9)

我认为答案是肯定的。您不能只是跳到开头的N / 2项目,因为std::map使用bidirectional iterators。您必须遍历地图中的一半项目。如果您有权访问通常用于std::map的基础红/黑树实现,则可以像Dani's answer中那样关闭。但是,您无权访问它,因为它被封装为实现细节。

答案 1 :(得分:6)

我认为您可以使用两个std::map来解决问题。一个用于较小的一半项目(mapL),另一个用于另一半(mapU)。当你有插入操作。无论是哪种情况:

  • 将项目添加到mapU并将最小元素移动到mapL
  • 将项目添加到mapL并将最大元素移动到mapU

如果地图具有不同的大小,并且您将元素插入到具有较小数量的地图中 你跳过移动部分的元素。 基本思路是保持地图平衡,因此最大尺寸差异为1个元素。 据我所知STL所有操作都应该在O(ln(n))时间内工作。可以使用迭代器来访问映射中最小和最大的元素。 当你有第n个位置查询时,只需检查地图大小并返回mapL中的最大元素或mapR中的最小元素。

以上使用方案仅适用于插入,但您也可以将其扩展为删除项目,但您必须跟踪哪个地图包含项目或尝试从两者中删除。

以下是我的代码示例:

#include <iostream>
#include <string>
#include <map>
using namespace std;

typedef pair<int,string> pis;
typedef map<int,string>::iterator itis;

map<int,string>Left;
map<int,string>Right;

itis get_last(map<int,string> &m){
    return (--m.end());
}

int add_element(int key, string val){
    if (Left.empty()){
        Left.insert(make_pair(key,val));
        return 1;
    }

    pis maxl = *get_last(Left);
    if (key <= maxl.first){
        Left.insert(make_pair(key,val));
        if (Left.size() > Right.size() + 1){
            itis to_rem = get_last(Left);
            pis cpy = *to_rem;
            Left.erase(to_rem);
            Right.insert(cpy);
        }
        return 1;
    } else {
        Right.insert(make_pair(key,val));
        if (Right.size() > Left.size()){
            itis to_rem = Right.begin();
            pis cpy = *to_rem;
            Right.erase(to_rem);
            Left.insert(*to_rem);
        }
        return 2;
    }   
}

pis get_mid(){
    int size = Left.size() + Right.size();
    if (Left.size() >= size / 2){
        return *(get_last(Left));
    }
    return *(Right.begin());
}

int main(){
    Left.clear();
    Right.clear();

    int key;
    string val;
    while (!cin.eof()){
        cin >> key >> val;
        add_element(key,val);
        pis mid = get_mid();
        cout << "mid " << mid.first << " " << mid.second << endl;
    }
}

答案 2 :(得分:4)

尝试:

typedef std::map<int,std::string>  Data;
Data           data;
Data::iterator median = std::advance(data.begin(), data.size() / 2); 

如果size()是奇数,则工作。当size()是偶数时,我会告诉你如何做到这一点。

答案 3 :(得分:2)

在自平衡二叉树(我认为std :: map是一个)中,一个很好的近似就是根 对于精确值,只需使用余额指示器对其进行缓存,每次添加到中位数以下的项目都会减少指标,并在上面添加项目时增加。当指标等于2 / -2时,将中位数向上/向下移动一步并重置指标。

答案 4 :(得分:2)

如果您可以切换数据结构,请将项目存储在std::vector中并对其进行排序。这将使得能够在不进行迭代的情况下在位置上访问中间项。 (这可能会令人惊讶,但由于地点的原因,排序的vector经常超出map。对于按排序键查找,您可以使用二进制搜索,它将具有与{{{1}相同的性能{1}}无论如何。请参阅Scott Meyer的Effective STL。)

答案 5 :(得分:1)

如果您知道地图将被排序,那么将元素放在地板上(长度/ 2)。如果你心情有点烦恼,请尝试(长度&gt;&gt; 1)。

答案 6 :(得分:1)

由于听起来像插入和查找是您的两种常见操作,而中位数很少,最简单的方法是使用地图和std::advance( m.begin(), m.size()/2 );,正如DavidRodríguez最初建议的那样。这是线性时间,但很容易理解,所以我只考虑另一种方法,如果分析显示中位数调用相对于你的应用程序正在进行的工作而言过于昂贵。

答案 7 :(得分:0)

对于排序列表,这里是java代码,但我认为,它很容易移植到c ++:

    if (input.length % 2 != 0) {
        return input[((input.length + 1) / 2 - 1)];
    } else {
        return 0.5d * (input[(input.length / 2 - 1)] + input[(input.length / 2 + 1) - 1]);
    }

答案 8 :(得分:0)

nth_element()方法适用于此:)它实现了快速排序的分区部分,您不需要对矢量(或数组)进行排序。 时间复杂度也是O(n)(而对于排序,你需要支付O(nlogn))。

答案 9 :(得分:0)

我知道无法从大型地图中快速获得纯STL地图的中位数。如果您的地图很小或者您需要中位数,那么无论如何我都应该使用线性推进到n / 2 - 为了简单和标准。

您可以使用地图构建一个提供中位数的新容器:Jethro建议使用两个地图,基于此可能更好的是单个地图和不断更新的中位数迭代器。这些方法的缺点是你必须重新实现每个修改操作,并且在jethro的情况下甚至是读取操作。

自定义编写的容器也可以用你所能做的,可能是最有效的,但是以自定义代码的价格。您可以尝试修改现有的stl地图实现。您还可以查找现有的实现。

有一种超高效的C实现,它提供了大多数地图功能以及名为Judy Arrays的随机访问。这些适用于整数,字符串和字节数组键。