【问题标题】:How is the C++ multimap container implemented?C++多图容器是如何实现的?
【发布时间】:2011-06-06 21:44:15
【问题描述】:

例如,C++ 向量是使用动态数组实现的,其中每个元素使用连续的内存空间。

我知道 C++ 多重映射是一对多的关系,但内部结构是什么?

【问题讨论】:

    标签: c++ multimap


    【解决方案1】:

    C++ 标准没有定义标准容器应该如何实现,它只给出了某些约束,就像你对向量所说的那样。

    multimaps 具有一定的运行时复杂度(O(lg n) 用于有趣的操作)和其他保证,并且可以实现为red-black trees。这就是它们在 GNU 标准 C++ 库中的实现方式。

    【讨论】:

    • 还有 Dinkumware 的 STL,因此几乎所有商业 C++ 编译器的标准库,包括 VC。
    • 元素怎么样。假设密钥 A 有 B、C、D、E。这些元素是可搜索的还是作为树中的一个节点聚集在一起
    • @Chris:我没有考虑太多,但我认为 B、C、D 和 E 可以集中在一个节点上,也可以表示为单个节点。不过,我不知道您所说的“可搜索”是什么意思。您可以遍历它们,但由于它们具有相同的键,您不能直接选择其中一个(通过值的任何属性)。多图仅提供按键查找。
    • @Chris:我最近查看了 GCC 的实现,发现具有相同键的元素位于不同的节点中。不过,我认为其他实现可能没有任何理由不选择对它们进行集群。
    • @MikeSeymour:实现一个迭代器来取消引用键/值对的引用是很棘手的,除非每个节点都有自己的键。这也解释了lower_bound/upper_bound。
    【解决方案2】:

    很多时候,red-black tree。参见例如STL's Red-Black Trees Dobb 博士的。

    【讨论】:

    • +1,如果您“经常”强调[并解释]更多。这个问题显然有误会需要纠正!
    • @Tomalak:不确定我是否关注?我犹豫要不要说“总是”(如果这是你暗示的话),因为标准没有强制要求,而且我不知道所有 STL 实现!
    • 不不,确切地说。 OP 似乎没有意识到这不是标准规定的事情。我只是觉得你的回答可以使更多(就像马格努斯所做的那样)。 :) (而且,咳咳,咳嗽标准库!)
    • 还不错;试图从听起来像是托尔的秘密身份的人那里偷雷可能在很大程度上是毫无意义的:P
    【解决方案3】:

    除了“首选”答案之外,因为 SO 不允许我发表评论:

    给定一个值为 B、C、D 的键,如果每个元素都有自己的节点,迭代器的行为会更容易实现。 Find() 被定义为返回系列中的第一个结果,随后的迭代将带您穿过剩余的元素。 事实上 map 和 multimap 的区别在于 multimap 使用

    更正:C++11 标准明确指出,新的(键、映射)对将插入到具有相同键的任何现有值的末尾。这提出了一个我没有考虑过的问题:多重映射是否可以包含两个节点,其中键和映射的目标都相同。该标准似乎对此没有明确的立场,但值得注意的是映射类型不需要比较运算符。如果你写一个测试程序,你会发现一个multimap可以将X映射到1、2、1。即:“1”可以作为目标多次出现,两个实例不会合并。对于一些算法来说这是一个不足。

    来自 Dobbs 博士的 article 讨论了常用的底层 rb-tree 实现。需要注意的主要一点是,重新平衡操作实际上根本不关心键,这就是为什么您可以构建一个允许重复键的 rb-tree。

    【讨论】:

      【解决方案4】:

      multimap 就像它的简单版本一样,即 std::map,主要是使用红黑树构建的。 C++ 标准本身并没有指定实现。但在大多数情况下(我亲自检查过 SGI STL)都使用红黑树。红黑树是高度平衡的树,因此对它们的获取/读取操作始终保证为 O(log(n)) 时间。但是,如果您想知道键的值是如何存储的。每个key->pair 都保存为红黑树中的一个单独节点(即使同一个键可能会出现多次,就像下面的键'b' 一样)。密钥用于查找/搜索 rb-tree。找到键后,将返回其存储在节点中的值。

        std::multimap<char,int> mmp;
      
        mmp.insert(std::pair<char,int>('a',10));
        mmp.insert(std::pair<char,int>('b',20));
        mmp.insert(std::pair<char,int>('b',10));
        mmp.insert(std::pair<char,int>('b',15));
        mmp.insert(std::pair<char,int>('b',20));
        mmp.insert(std::pair<char,int>('c',25));
        mmp.insert(std::pair<char,int>('a',15));
        mmp.insert(std::pair<char,int>('a',7));
      
      
      for (std::multimap<char,int>::iterator it=mmp.begin(); it!=mmp.end(); ++it){
      
          std::cout << (*it).first << " => "  << (*it).second << " . Address of (*it).second = " << &((*it).second) << '\n';
      }
      

      输出:

      a => 10 . Address of (*it).second = 0x96cca24
      a => 15 . Address of (*it).second = 0x96ccae4
      a => 7 . Address of (*it).second = 0x96ccb04
      b => 20 . Address of (*it).second = 0x96cca44
      b => 10 . Address of (*it).second = 0x96cca64
      b => 15 . Address of (*it).second = 0x96cca84
      b => 20 . Address of (*it).second = 0x96ccaa4
      c => 25 . Address of (*it).second = 0x96ccac4
      

      最初我认为像 'b' 这样的单个键的值可能存储在 std::vector 中。

      template <class K, class V>
      struct Node {
        K key;
        std::vector<V> values; 
        struct Node* left;
        struct Node* right;
      }
      

      但后来我意识到这会违反 O(log(n)) 的保证获取时间。此外,打印出值的地址可以确认具有公共键的值是不连续的。

      它们的键是使用运算符

      所以如果我们先插入 (键 = 'b',值 = 20) 进而 (键 = 'b',值 = 10) 插入是使用 operator

      我使用的编译器是gcc-5.1 (C++14)。

      【讨论】:

      • 请阅读投票赞成的答案,实际上是正确。他们还碰巧提到(自从 4 年以来!)libstdc++(由 GNU)和其他一些实际上使用 RB-tree。
      • 即使我也这么说! RB树是红黑树。但是 OP 提出的更微妙的问题是相同键的值如何存储在 rb 树中,以及 map 和 multimap 的实现差异,即使两者都使用 RB-Tree。我试图解释它。请仔细阅读答案。谢谢。
      • 重点是RB-tree只是众多可能性中的一种。顺便说一句,即使使用了 RB-tree 并且有一个用于插入顺序的额外字段,您的结构也不正确,因为键值对保存在 std::pair 中。而且使用时间戳并不是一个好主意,序列号的开销更小,而且通常效果更好。
      猜你喜欢
      • 2023-03-27
      • 2011-04-18
      • 1970-01-01
      • 1970-01-01
      • 2017-04-09
      • 2018-11-06
      • 2014-09-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多