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

时间:2011-04-21 11:38:44

标签: c++ performance map binary-tree std

我理解为什么人们不能这样做(重新平衡和填充):

iterator i = m.find(33);

if (i != m.end())
  i->first = 22;

但到目前为止,改变密钥的唯一方法(我知道)是从树中删除节点,然后用不同的密钥插回值:

iterator i = m.find(33);

if (i != m.end())
{
  value = i->second;
  m.erase(i);
  m[22] = value;
}

由于更多原因,这似乎对我来说效率低下:

  1. 遍历树三次(+余额)而不是两次(+余额)
  2. 另一个不必要的值
  3. 副本
  4. 不必要的重新分配,然后重新分配树内的节点
  5. 我发现分配和释放是这三者中最差的。我错过了什么或有更有效的方法吗?

    更新: 我认为,从理论上讲,它应该是可能的,所以我不认为改变不同的数据结构是合理的。这是我想到的伪算法:

    1. 在树中查找我想要更改其密钥的节点。
    2. 如果从树上分离(不要解除分配)
    3. 重新平衡
    4. 更改分离节点内的密钥
    5. 将节点插回树
    6. 重新平衡

7 个答案:

答案 0 :(得分:31)

在C ++ 17中,新的map::extract功能可让您更改密钥 例如:

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" } }

答案 1 :(得分:30)

我在18个月前提出了关联容器的算法:

http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-closed.html#839

寻找标记的评论:[2009-09-19 Howard补充说:]。

当时,我们太过接近FDIS来考虑这种变化。但是我认为它非常有用(你显然同意),我想把它送到TR2。也许您可以通过查找并通知您的C ++ National Body代表,这是您希望看到的功能。

<强>更新

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

答案 2 :(得分:23)

您可以省略复制;

const int oldKey = 33;
const int newKey = 22;
const iterator it = m.find(oldKey);
if (it != m.end()) {
  // Swap value from oldKey to newKey, note that a default constructed value 
  // is created by operator[] if 'm' does not contain newKey.
  std::swap(m[newKey], it->second);
  // Erase old key-value from map
  m.erase(it);
}

答案 3 :(得分:6)

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

似乎如果你在配对的关键方面有那么大的波动性,可能会有更多不同的数据结构或结构。

答案 4 :(得分:5)

你不能。

正如您所注意到的那样,这是不可能的。组织地图以便您可以有效地更改与密钥关联的值,但不能相反。

你看看Boost.MultiIndex,特别是它的Emulating Standard Container sections。 Boost.MultiIndex容器具有高效的更新功能。

答案 5 :(得分:1)

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

正如你所说,当密钥发生变化时,可能会有很多重新平衡。这就是树的运作方式。也许22是树中的第一个节点,33是最后一个节点?我们知道什么?

如果避免分配很重要,也许您应该尝试使用矢量或双端队列?它们分配更大的块,因此它们节省了对分配器的调用次数,但可能会浪费内存。所有的容器都有它们的权衡,由你来决定哪种容器在每种情况下都具有你需要的主要优势(假设它很重要)。

对于喜欢冒险的人:
如果您确定更改密钥不会影响订单,并且您永远不会犯错,那么一点const_cast 会让您更改密钥。

答案 6 :(得分:0)

如果您知道新键对于地图位置有效(更改它不会更改顺序),并且您不想执行将项目添加和添加到地图的额外工作,则可以使用const_cast来更改密钥,例如下面的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;
}

如果您想要一个仅在有效时就地更改的解决方案,否则会更改地图结构:

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));
}