【问题标题】:how to get median value from sorted map如何从排序的地图中获取中值
【发布时间】:2010-08-16 22:38:28
【问题描述】:

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

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

那么中位数是3。

有没有不迭代地图中一半以上项目的解决方案?

【问题讨论】:

  • 地图中有多少元素?您需要多久检索一次中位数?如果第一个或第二个答案是小数字,则只需使用std::advance( m.begin(), m.size()/2 )。按顺序迭代地图的成本是线性的,因此需要O(N) 操作。每次手术都相对便宜。

标签: c++ stl


【解决方案1】:

我认为你可以通过使用两个std::map 来解决问题。一个用于较小的一半项目(mapL),第二个用于另一半(mapU)。当您进行插入操作时。这将是任何一种情况:

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

如果地图的大小不同,并且您将元素插入到数量较少的地图中 您跳过移动部分的元素。 基本思想是保持地图平衡,因此最大尺寸差异为 1 个元素。 据我所知,STL 所有操作都应该在 O(ln(n)) 时间内工作。可以使用迭代器访问地图中的最小和最大元素。 当您有 n_th 位置查询时,只需检查地图大小并返回 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;
    }
}

【讨论】:

  • +1 这是一个非常聪明的解决方案!您可以轻松地在此基础上进行构建并将其封装在一个类中,该类可用作std::map 的替代品,但还有一个额外的median() 方法。好主意!
【解决方案2】:

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

【讨论】:

  • +1 给这个人一块饼干。要在恒定时间内运行,它必须支持随机访问迭代器或底层数据类型,这两者 std::map 都不提供。
  • 我喜欢饼干!谢谢梅林!
【解决方案3】:

试试:

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

如果 size() 是奇数则有效。当 size() 是偶数时,我会让你想办法。

【讨论】:

  • 这是获得中位数的最干净的方法。但是,您仍在迭代一半的元素,因为 std::map::iterator 是双向迭代器,无法进行随机访问。
  • 是的。但是当您的数据结构是映射时,选择不多。但是,如果数据结构被更改为任何其他容器类型,std::advance() 仍然可以工作并自动提供最有效的推进方法。
  • 这等于 iter = map.begin();而 ( i
  • 马丁,我完全同意!如果我不得不使用地图,你的解决方案是我会尝试的第一件事。我只需要迂腐指出解决方案并不完全满足 OP 的标准。
  • @Raymond:与什么相比很差。复杂度是 O(n),除非你使用向量,否则你不会变得更好。
【解决方案4】:

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

【讨论】:

  • 好主意。大多数时候,近似值是可以的。这意味着我应该重写地图,并保留指标?如果在每次插入操作中调整指标,那是低效的,因为会有map.size()比较。
  • 你不必重写map,你可以继承它。为什么效率低下?这是 instertion 的 O(1) 方法。
  • 你是对的,我可以继承它,并设置一个成员变量来保存指标。谢谢,这也是一个可选的解决方案。
  • STL 类并非设计为子类化。见stackoverflow.com/questions/1647298
【解决方案5】:

如果您可以切换数据结构,请将项目存储在std::vector 中并对其进行排序。这将能够在不迭代的情况下按位置访问中间项目。 (这可能令人惊讶,但由于局部性,排序后的vector 的性能通常优于map。对于按排序键的查找,您可以使用二进制搜索,它的性能与map 几乎相同。请参阅 Scott Meyer 的Effective STL。)

【讨论】:

  • 对不起,我无法切换到矢量,因为地图中的项目是动态插入的。
  • 你可以在向量上push_back,然后再排序。尝试计时 - 所有操作的总成本可能会更少。
  • 顺便说一句。有一个很好的函数nth_element 对 RandomAccessIterator 进行排序,直到第一个 n 元素被排序。你会发现中位数是nth_element(v.begin(),v.begin+v.size()/2,v.end());
  • @Raymond 您无法计算一组未排序值的中位数。该术语仅针对排序值定义。所以无论如何你都必须对地图进行排序......
  • 排序相关的原因是因为这个答案建议使用vector 而不是mapvector 可以进入排序状态(它有一个 sort 方法),但是一旦你修改它的内容,它很可能会变得未排序。因此,如果您使用vector,您将失去自动排序,但您可以获得 (a) 控制何时以及如何进行排序,(b) 恒定时间访问中间位置,(b) 更大的局部性:您的数据在一个连续的块,而不是分散在地址空间周围,这通常可以提高性能。
【解决方案6】:

如果您知道地图将被排序,则获取位于 floor(length / 2) 处的元素。如果你心情有点烦,试试 (length >> 1)。

【讨论】:

  • OP:“这是没有迭代地图中一半项目的解决方案吗?”
  • @Ido: 从 0 开始计数用于索引,这是 CS。 floor(len/2) = 2,2是第3个元素的索引。
  • 请不要用位移来代替算术。
【解决方案7】:

对于大地图,我无法快速从纯 STL 地图中获取中值。如果你的地图很小或者你很少需要中位数,我认为无论如何你都应该使用线性推进到 n/2 - 为了简单和标准。

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

自定义编写的容器也可以做你想做的事,这可能是最有效的,但要付出定制代码的代价。您可以尝试按照建议修改现有的 stl 地图实现。您还可以寻找现有的实现。

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

【讨论】:

  • COOL,这有助于私有地图的实现。谢谢。
【解决方案8】:

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

【讨论】:

    【解决方案9】:

    对于排序列表,这里是 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]);
        }
    

    【讨论】:

    • STL 的地图不提供随机访问。
    【解决方案10】:

    nth_element() 方法是为你准备的 :) 它实现了快速排序的分区部分,你不需要对向量(或数组)进行排序。 而且时间复杂度是 O(n)(而排序需要支付 O(nlogn))。

    【讨论】:

    • OP 询问的是std::map,而不是std::vector
    • 但是如果你仔细观察,实际上他的数据结构是一个向量。地图中的键不是值排序的。
    猜你喜欢
    • 2012-05-28
    • 1970-01-01
    • 1970-01-01
    • 2019-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多