C++ 在std::map中更改元素键的最快方法是什么

C++ 在std::map中更改元素键的最快方法是什么,c++,performance,dictionary,binary-tree,std,C++,Performance,Dictionary,Binary Tree,Std,我理解人们不能这么做的原因(再平衡等等): 但到目前为止,更改键的唯一方法(我知道)是将节点一起从树中删除,然后使用不同的键插入值: iterator i = m.find(33); if (i != m.end()) { value = i->second; m.erase(i); m[22] = value; } 对我来说,这似乎效率很低,原因更多: 遍历树三次(+平衡),而不是两次(+平衡) 值的另一个不必要副本 不必要的取消分配,然后在树内重新分配节点 我发现

我理解人们不能这么做的原因(再平衡等等):

但到目前为止,更改键的唯一方法(我知道)是将节点一起从树中删除,然后使用不同的键插入值:

iterator i = m.find(33);

if (i != m.end())
{
  value = i->second;
  m.erase(i);
  m[22] = value;
}
对我来说,这似乎效率很低,原因更多:

  • 遍历树三次(+平衡),而不是两次(+平衡)

  • 值的另一个不必要副本

  • 不必要的取消分配,然后在树内重新分配节点

  • 我发现分配和解除分配是这三种情况中最糟糕的。我是否遗漏了什么,或者有没有更有效的方法

    我认为,在理论上,这应该是可能的,所以我不认为为不同的数据结构进行更改是合理的。以下是我想到的伪算法:

  • 在树中查找要更改其密钥的节点

  • 从树中分离if(不要取消分配)

  • 再平衡

  • 更改分离节点内的关键点

  • 将节点插入回树中

  • 再平衡


  • 您可以省略值的复制

    你不能

    正如你所注意到的,这是不可能的。映射是有组织的,因此可以有效地更改与键关联的值,但不能更改相反的值


    您可以看看Boost.MultiIndex,尤其是它的。Boost.MultiIndex容器具有高效更新功能。

    STL映射中的键必须是不可变的


    如果配对的关键端有那么多的波动性,可能一个或多个不同的数据结构更有意义。

    您应该将分配留给分配器。:-)

    正如你所说,当关键因素发生变化时,可能会有很多再平衡。树就是这样工作的。也许22是树中的第一个节点,33是最后一个节点?我们知道什么

    如果避免分配是很重要的,也许你应该试试向量或deque?它们以较大的块进行分配,因此可以节省对分配器的调用次数,但可能会浪费内存。所有容器都有它们的权衡,由您决定哪一个容器在每种情况下具有您所需要的主要优势(假设这很重要)

    对于喜欢冒险的人:

    如果您确实知道更改密钥不会影响顺序,并且您从未犯过错误,那么一点const_cast无论如何都会让您更改密钥。

    大约18个月前,我在这里提出了关联容器的算法:

    请查看标记为[2009-09-19]的评论,霍华德补充道:

    当时我们离FDI太近了,不能考虑这种变化。然而,我认为它非常有用(你显然同意),我想把它加入到TR2中。也许你可以通过发现并通知C++国家机构的代表来帮助你发现这是你想看到的一个特性。 更新

    这还不确定,但我认为我们很有可能在C++17中看到此功能:-)

    在C++17中,新函数允许您更改密钥。
    例如:

    std::map m{{10,“土豆”}、{1,“香蕉”};
    自动节点句柄=m.extract(10);
    nodeHandler.key()=2;
    m、 插入(标准::移动(节点句柄));//{{1,“香蕉”},{2,“土豆”}
    
    如果您知道新密钥对地图位置有效(更改它不会更改顺序),并且您不想在地图中删除和添加项目的额外工作,您可以使用
    常量转换来更改密钥,如下面的
    unsafeUpdateMapKeyInPlace

    template <typename K, typename V, typename It>
    bool isMapPositionValidForKey (const std::map<K, V>& m, It it, K key)
    {
        if (it != m.begin() && std::prev (it)->first >= key)
            return false;
        ++it;
        return it == m.end() || it->first > key;
    }
    
    // Only for use when the key update doesn't change the map ordering
    // (it is still greater than the previous key and lower than the next key).
    template <typename K, typename V>
    void unsafeUpdateMapKeyInPlace (const std::map<K, V>& m, typename std::map<K, V>::iterator& it, K newKey)
    {
        assert (isMapPositionValidForKey (m, it, newKey));
        const_cast<K&> (it->first) = newKey;
    }
    
    模板
    bool ismappositionvalidworkey(const std::map&m、It、K键)
    {
    if(it!=m.begin()&&std::prev(it)->first>=key)
    返回false;
    ++它;
    返回它==m.end()| it->first>键;
    }
    //仅在密钥更新未更改映射顺序时使用
    //(仍大于上一个键,低于下一个键)。
    模板
    void unsafeUpdateMapKeyInPlace(const std::map&m,typename std::map::iterator&it,K newKey)
    {
    断言(IsMappositionValidWorkey(m、it、newKey));
    const_cast(it->first)=newKey;
    }
    
    如果希望解决方案仅在有效时就地更改,否则会更改地图结构,请执行以下操作:

    template <typename K, typename V>
    void updateMapKey (const std::map<K, V>& m, typename std::map<K, V>::iterator& it, K newKey)
    {
        if (isMapPositionValidForKey (m, it, newKey))
        {
            unsafeUpdateMapKeyInPlace (m, it, newKey);
            return;
        }
        auto next = std::next (it);
        auto node = m.extract (it);
        node.key() = newKey;
        m.insert (next, std::move (node));
    }
    
    模板
    void updateMapKey(const std::map&m,typename std::map::iterator&it,K newKey)
    {
    if(IsMappositionValidWorkey(m、it、newKey))
    {
    未更新的MapKeyInplace(m、it、newKey);
    返回;
    }
    自动下一步=标准::下一步(it);
    自动节点=m.extract(it);
    node.key()=newKey;
    m、 插入(下一步,std::move(节点));
    }
    
    是的,效率很低。如果不适合使用,请使用不同的数据结构case@sehe,我不认为这是数据结构的问题,如果我要创建自己的,我会得到相同的红黑树,唯一的区别是它有一个方法,可以重用节点,而不是分配和重新分配。@Chowlett,谢谢,我会记住这一点。“1.遍历树三次(+平衡)而不是两次(+平衡)”-它是两次而不是一次。。。
    end()
    @TonyDelroy不需要遍历。我相信操作符[]是第三个。感谢您的输入,但问题并不在于使用哪种数据结构。相反,问题是(如果我原来的答案是否定的):如果理论上似乎没有理由不应该有API,那么为什么没有API来有效地执行它呢。伪算法简单:找到节点;把它从树上取下来;再平衡;更改分离节点中的键;插回;再平衡@彼得-也许重新键入不是地图上的基本操作?我认为它还可以避免密钥必须是可分配的,这样就可以使用一些额外的类型作为密钥。@Viktor,嗯,第二个例子
    std::map<int, std::string> m{ {10, "potato"}, {1, "banana"} };
    auto nodeHandler = m.extract(10);
    nodeHandler.key() = 2;
    m.insert(std::move(nodeHandler)); // { { 1, "banana" }, { 2, "potato" } }
    
    template <typename K, typename V, typename It>
    bool isMapPositionValidForKey (const std::map<K, V>& m, It it, K key)
    {
        if (it != m.begin() && std::prev (it)->first >= key)
            return false;
        ++it;
        return it == m.end() || it->first > key;
    }
    
    // Only for use when the key update doesn't change the map ordering
    // (it is still greater than the previous key and lower than the next key).
    template <typename K, typename V>
    void unsafeUpdateMapKeyInPlace (const std::map<K, V>& m, typename std::map<K, V>::iterator& it, K newKey)
    {
        assert (isMapPositionValidForKey (m, it, newKey));
        const_cast<K&> (it->first) = newKey;
    }
    
    template <typename K, typename V>
    void updateMapKey (const std::map<K, V>& m, typename std::map<K, V>::iterator& it, K newKey)
    {
        if (isMapPositionValidForKey (m, it, newKey))
        {
            unsafeUpdateMapKeyInPlace (m, it, newKey);
            return;
        }
        auto next = std::next (it);
        auto node = m.extract (it);
        node.key() = newKey;
        m.insert (next, std::move (node));
    }