【问题标题】:std::map with efficient nth element access具有高效第 n 个元素访问的 std::map
【发布时间】:2011-01-14 00:42:20
【问题描述】:

我有一组数据需要存储在有序映射中(即通过键有效地插入、删除和定位项目),但我还需要能够找到 nth 元素而不遍历整个地图(其中有时可能有数万个项目)。

我知道一种方法:使用红/黑树,但也要在每个节点的一条腿上保留子项的总数。它使插入和删除稍微慢了一点(因为您必须像这样做一样更新路径上每个节点上的计数),但是您可以找到任何 nnth 元素> 与找到钥匙的时间大致相同。

我想知道是否存在我可以使用的这种东西的现有 C++ 实现。如果没有我可以自己写,但我真的不想。


编辑:我对它的用例做了一些澄清。我有点误解了:在按键查找项目后,他们需要能够有效地找出找到的项目的索引,才能正确显示滚动条。

一个合法的需求,我上面描述的数据结构仍然适用,所以我仍在寻找答案。不过好像还没有人想出一个,我要自己开始编码了。

【问题讨论】:

  • 听起来你选错了容器。地图按键索引,不是按任意“从一开始的位置”。
  • @Tomalak:听起来他们正在挑选合适的容器,但在 stdlib 或其他地方找不到他们的需求。
  • @FredNurk:我同意。但是,根据我的阅读,我相信他们的要求表明了“设计气味”。毕竟,他们还没有找到任何符合他们“要求”的东西是有充分理由的。
  • 这是一个类似的问题。 stackoverflow.com/questions/2290429/rank-tree-in-c 所以我们知道怎么做(在 Cormen、Leiserson、Rivest 的“增强数据结构”一章中),但 C++ stl 树似乎不允许您向树节点添加字段。也许是 boost 的“侵入式”库?我从来没有用过。我想我最终也会自己写这棵树。
  • @Tomalak:所以需要标准库中不存在的东西是一种“设计味道”?

标签: c++ data-structures


【解决方案1】:

这是我对其他问题的回答,考虑到类似的问题。

associative / random access container

我想这也可能适用于您的问题。


这样的数据结构我找了很久。

最近,我发现了一个很有前途的库,它具有您正在寻找的所有功能。

在 O(log n) 中查看随机访问的 cntree::set。

这里是链接。 http://dl.dropbox.com/u/8437476/works/countertree/index.html

虽然它似乎正在开发中,但我认为它非常有用。

【讨论】:

  • 这听起来正是我所需要的。可惜晚了一年半。 :-) 我最终写了自己的,确实效果很好。我将把它标记为已接受的答案,因为它是唯一真正回答问题的答案,无论是否迟到。
  • 第二个链接失效了。
【解决方案2】:

如果您使用修改后的 Trie,其中非终端节点跟踪其下方有多少终端节点,您可以进行快速有序查找。

【讨论】:

    【解决方案3】:

    我从未使用过boost::multi_index_container<>,但听起来它可能有能力做你想做的事(虽然我不太确定 - 乍一看它是一个相当复杂的库)。

    它具有随机访问密钥类型,但我不确定您如何更新随机索引,以使插入元素的索引与其他索引的顺序保持同步。 另外,请注意tutorial on using a random index 中的以下内容:

    这种增加的灵活性是有代价的:在索引末尾以外的位置插入和删除具有线性复杂性,而这些操作对于有序索引来说是恒定时间。这种情况让人想起 std::list 和 std::vector 在复杂性行为上的差异:然而,在随机访问索引的情况下,插入和删除永远不会导致任何元素复制,因此这些操作的实际性能是可以接受的,尽管在序列索引方面存在理论上的劣势。

    我不清楚这是否会成为你的交易杀手,即使你可以设法以你想要的方式同步插入元素的随机索引。

    【讨论】:

    • 谢谢。这是我首先考虑的事情之一,但从文档(包括您引用的部分)来看,它看起来像是在内部使用了一个实际的向量,这会破坏插入/删除性能。
    • 随机访问索引按插入顺序显示元素,而不是按比较排序
    【解决方案4】:

    聚会迟到(在寻找相关内容时遇到这个问题)-但是排序向量不适合这里的用例吗? 插入时间更糟 - 除非您在排序之前在一批中完成大部分/所有插入。 在那之后查找时间实际上可以超过 std::map - 并且获取索引是微不足道的。

    【讨论】:

    • 插入时间是一个交易破坏者。它需要定期插入东西,并且只能在有限的程度上进行批处理。
    • 我喜欢这个答案,因为它强调了一个重要的点,在提交更棘手的解决方案之前一定要考虑!
    【解决方案5】:

    一种选择是开发一个基于 std::vector 的容器,但也具有地图接口。它将存储一个单独的哈希表或二叉树,使用元素的键来访问它们,但实际值将是指向向量使用的内部数组的指针。

    对于某些人来说,这样的怪物可能看起来毫无意义、容易出错或带有设计味道,但这样的数据结构确实有它的位置。我在零售系统中的硬件驱动程序代码中看到了这一点,其中一个容器的两个用户需要以不同的方式访问它。当“因为它在那里”使用时,它是一件坏事,但如果使用得当,它是救命稻草。

    【讨论】:

    • 这似乎不像我描述的 map-with-child-counts 设计那样省时,至少在这种情况下是这样。
    【解决方案6】:

    愚蠢的想法:同时将密钥保存在indexed skip list 中,并进行 O(log n) 插入和随机访问。见http://cglab.ca/~morin/teaching/5408/refs/p90b.pdf

    这并不比使用order statistic trees 更有效,并且需要更多空间和簿记。

    另见:Rank Tree in C++

    【讨论】:

      【解决方案7】:

      查看boost::container::flat_map[1],这是一个基于类矢量容器的地图[2]

      使用底层的随机访问迭代器,您可以获得 O(1) 查找; nth[3] 成员函数可帮助您获取第 n 个元素的迭代器。


      [1]Boost.Container
      [2]Documentation on flat_map
      [3]boost::container::flat_map::nth p>

      【讨论】:

        【解决方案8】:

        如果使用 g++,您还可以使用基于策略的数据结构

        示例:

        #include <ext/pb_ds/assoc_container.hpp>
        #include <ext/pb_ds/tree_policy.hpp>
        #include <functional> // for less
        
        using namespace __gnu_pbds;
        using namespace std;
        
        typedef tree<int, null_type, less<int>, rb_tree_tag, tree_order_statistics_node_update> pbds;
        
        int main() {
            pbds a;
            a.insert(4);
            a.insert(5);
            a.insert(1);
            
            // value at index 1
            cout << a.find_by_order(1)->second << endl; // prints 4
            
            // index of number 5
            cout << p.order_of_key(5) << endl;
        
            return 0;
        }
        

        order_of_key 和 find_by_order 函数都在对数时间内工作。

        来源:Geeks from Geeks g++ 编译器还支持一些不属于 C++ 标准库的数据结构。这种结构称为基于策略的数据结构。这些数据结构旨在实现高性能、灵活性、语义安全以及与 std 中相应容器的一致性。

        【讨论】:

          【解决方案9】:

          尝试使用有序的 std::list 并使用 std::binary_search 进行搜索。可以使用 std::list 实现有序列表,并使用 std::lower_bound 插入节点。网上和 SO 上有很多这样的例子。

          【讨论】:

          • 我可能错了,但是这些算法不会在前向、非随机访问迭代器(如列表迭代器)上降低到 O(n) 性能吗?请参阅cplusplus.com/reference/algorithm/binary_search 的复杂性部分
          • -1 列表对于二进制搜索来说很慢,因为迭代器不是随机访问。它必须取消引用到项目“n”的每个节点,这样你会失去很多速度,因为它会不断地在内存中跳跃并破坏缓存。
          • 很抱歉对社区的粗鲁欢迎,但这是堆栈溢出的方式。如果有什么不好的,它会被否决,无论意图如何。代码质量在这里是至高无上的,任何更少都会对社区有害。请不要将此视为批评,而是学习新事物的机会。再次发帖,我们只能从错误中吸取教训。我把我糟糕的答案/问题作为骄傲的标志!我不是万无一失的,这意味着我总是有一些需要改进的地方(如果不是这样,我想我会换一个完全不同的职业)。
          【解决方案10】:

          红黑树支持的MS VC STL映射。

          我认为不可能在同一个数据结构中进行有效的搜索(按键)和有效的随机访问。

          如果有效的随机访问真的很重要,最好将数据存储在类似矢量的随机访问容器中。排序和关键字搜索可以通过附加索引来完成。 RDBMS 就是这样做的。

          或者,如果插入/删除更重要,则似乎可以避免管理诸如随机访问的键数组(或行号索引)之类的东西。

          【讨论】:

          • 据我所知,这所有都很重要。是的,这是可能的——我可以确切地知道该怎么做,我只是真的不想花时间自己写。
          猜你喜欢
          • 2020-09-06
          • 1970-01-01
          • 2019-06-30
          • 1970-01-01
          • 2010-12-21
          • 1970-01-01
          • 2019-09-09
          • 2013-12-08
          • 1970-01-01
          相关资源
          最近更新 更多