【问题标题】:Are there versions of the C++ STL's associative data structures optimized for numerous partial copies?是否有针对大量部分副本优化的 C++ STL 关联数据结构的版本?
【发布时间】:2011-11-30 20:26:44
【问题描述】:

我有一棵大树,随着算法的进展而增长。每个节点都包含集合,我想它是作为平衡二叉搜索树实现的。每个节点的集合在创建该节点之后,在用于创建该节点的子节点之前,应保持固定。

不过,我担心复制每组的成本过高。相反,我希望每个新创建的节点集都利用父节点集的所有适当部分。简而言之,我很乐意复制 O(log n) 的集合,而不是 O(n)。

是否有任何 STL 关联数据结构的变体提供这种部分复制优化?也许在 Boost 中?这样的数据结构在 Haskell 或 OCaML 中实现当然是微不足道的,但在 C++ 中需要更多的努力。

【问题讨论】:

  • 套装的内容是什么?我没有完全理解你的问题是什么,似乎每个级别的集合都是父母集合的严格子集,是吗?
  • 二叉搜索树一般具有有意义的子树,除非您的子节点恰好对应于父节点集合的连续有序子集。无论如何,如果您有父节点和子节点,您已经在实现自己的树结构,那么为什么不将数据保持在正确的级别呢?
  • 也很高兴知道,如果一个节点包含父节点的子集,该子集是否连续?
  • 每个级别都是一个严格的超集。所有这些集合的存在只是为了终止外部树的分支。我目前有一个未排序的手动链表,它简单地回溯每个树分支,提供 O(n) 搜索重复项、O(1) 插入和 O(1) 每个级别的额外空间。我愿意全盘支付 O(log n),但不会更高。平均而言,一个集合插入应该修改树的 O(log n),使许多子树保持不变。正如 Useless 所指出的,一个最佳的实现经常会改变得更少,但偶尔会改变得更多。然而,平均情况应该是 O(log n)。
  • 你一直提到 O(log(n))。有这个价值的理由吗?每个节点是否仅包含其父集的 log(n)?

标签: c++ stl set binary-tree tree-balancing


【解决方案1】:

我知道建议一种不同的语言通常不会有成效,但 Haskell 的标准容器库正是这样做的。我记得看过一个视频(是 Simon Peyton Jones 吗?)谈论这个确切的问题,以及对于给定的程序员工作,Haskell 解决方案最终如何比 C++ 解决方案快得多。当然,这是针对一个特定问题,它有很多集合和很多共享元素。

对这个主题有相当多的研究。如果您正在寻找关键字,我建议搜索“功能数据结构”而不是“不可变数据结构”,因为大多数功能范式通常受益于不变性。 finger tree 等结构正是为了解决这个问题而开发的。

我知道没有实现这些数据结构的 C++ 库。没有什么可以阻止您阅读相关论文(或 Haskell 源代码,Data.Set 包括测试大约 1k 行)并自己实现它,但我知道这不是您想听到的。您还需要对共享节点进行某种引用计数,对于如此深的结构,这可能比简单的垃圾收集器具有更高的开销。

【讨论】:

  • 事实上,我选择 C++ 仅仅是因为我更擅长分析 C++ 代码。所以是的,告诉我 Haskell 中存在的经过优化的容器会有所帮助。
  • @JeffBurdges:手指树和半持久性数据结构应该对你有用。
【解决方案2】:

这在 C++ 中几乎是不可能的,因为不可变的概念 容器不存在。你可能知道你不会做任何改变, 并且某种共享表示会更可取,但是 编译器和库没有,也没有办法传达这个 给他们。

【讨论】:

    【解决方案3】:

    每个节点都包含集合,我认为它是平衡的 二叉搜索树。之后每个节点的集合应保持固定 节点的创建,在它用于创建该节点的子节点之前。

    这是一个非常独特的案例。我建议改用 std::vector 。 (真的没有!)创建节点的代码仍然可以使用set,并在最后一秒切换到vector。但是,vector 更小,并且只占用少量内存分配(如果您使用 reserve,则分配一个),使得算法快得多

    typedef unsigned int treekeytype;
    typedef std::vector<unsigned int> minortreetype;
    typedef std::pair<treekeytype, minortreetype> majornode
    typedef std::set<treekeytype, minortreetype> majortype;
    majortype majortree;
    
    void func(majortype::iterator perform) {
         std::set<unsigned int> results;
         results.assign(perform->second.begin(), perform->second.end());
         majortree[perform->first+1].assign(results.begin(), results.end()); //the only change is here
         majortype::iterator next = majortree.find(perform->first+1);
         func(next);
    }
    

    您甚至可以使用std::lower_boundstd::upper_bound 仍然获得O(log(n)) 次内存访问,因为它的排序仍然与集合相同,因此您不会失去任何效率。只要您不需要频繁插入/删除,这就是纯粹的收获。

    不过,我担心复制每一组的成本太高了。

    如果这种担心是因为每个集合都包含与其父节点大部分相同的节点,并且数据成本很高(复制或内存中的任何一个),只有少数节点发生了变化,则让子树包含std::shared_pointers数据本身。这意味着数据本身不会被复制,只会复制指针。

    我意识到这不是您提出问题的目的,但正如 JamesKanze 所说,我知道没有这样的容器。除了可能对 STL 的 rope 类进行奇怪和危险的使用之外。请注意,我说的是 STL,而不是标准 C++ 库。它们是不同的。

    【讨论】:

    • 恐怕问题出在设置大小本身,好吧,他们已经设置了指针。我想到了绳子,但我以前从未使用过。谢谢,我去看看。
    • 我真的只是推荐矢量的东西。如果您不需要频繁的插入/删除,向量(和队列)比树快 waaay
    猜你喜欢
    • 1970-01-01
    • 2016-09-19
    • 1970-01-01
    • 1970-01-01
    • 2011-01-03
    • 2016-05-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多