std::map插入还是std::map查找?

107

假设有一个地图,您想要保留现有条目。20%的情况下,您要插入的条目是新数据。使用std :: map :: find然后使用返回的迭代器进行std :: map :: insert是否有优势?还是尝试插入,然后根据迭代器指示记录是否已插入而采取行动更快?

假设您想要在保留现有条目的情况下向地图中插入新数据。在这种情况下,使用std :: map :: find来查找该数据是否已经存在于地图中可以减少不必要的插入尝试并节省时间。如果数据不存在,则需要使用std :: map :: insert将其插入地图中。但是,如果数据已经存在,则不需要进行插入操作。
因此,使用std :: map :: find和std :: map :: insert组合的方法可能比仅使用std :: map :: insert方法更快。

4
我被纠正了,并且本来打算使用std::map::find,但改用了std::map::lower_bound。 - Superpolock
8个回答

160
答案是你两者都不需要做。相反,你应该按照Effective STL中的第24条建议进行操作,作者是Scott Meyers
typedef map<int, int> MapType;    // Your map type may vary, just change the typedef

MapType mymap;
// Add elements to map here
int k = 4;   // assume we're searching for keys equal to 4
int v = 0;   // assume we want the value 0 associated with the key of 4

MapType::iterator lb = mymap.lower_bound(k);

if(lb != mymap.end() && !(mymap.key_comp()(k, lb->first)))
{
    // key already exists
    // update lb->second if you care to
}
else
{
    // the key does not exist in the map
    // add it to the map
    mymap.insert(lb, MapType::value_type(k, v));    // Use lb as a hint to insert,
                                                    // so it can avoid another lookup
}

2
这确实是find函数的工作原理,诀窍在于它结合了find所需的搜索和插入操作。当然,使用插入操作然后查看第二个返回值也可以达到同样的效果。 - puetzk
13
@Richard:如果关键字不存在,find()将返回end(),lower_bound返回应该插入的位置(可以用作插入提示)。@puetzek:如果已经存在键,则“just insert”会覆盖参考值吗?不确定原帖作者是否希望这样。 - peterchen
4
有人知道是否有类似于unordered_map的东西吗? - Giovanni Funchal
1
他们不使用提示,它只是为了兼容性而存在。请参见此处 - default
3
@peterchen 如果已经存在,则map::insert不会覆盖现有的值,详见http://www.cplusplus.com/reference/map/map/insert/。 - Chris Drew
显示剩余5条评论

13
这个问题的答案也取决于在地图中存储的值类型的创建成本有多昂贵:

这个问题的答案还取决于在地图中存储的值类型的创建成本有多高:

typedef std::map <int, int> MapOfInts;
typedef std::pair <MapOfInts::iterator, bool> IResult;

void foo (MapOfInts & m, int k, int v) {
  IResult ir = m.insert (std::make_pair (k, v));
  if (ir.second) {
    // insertion took place (ie. new entry)
  }
  else if ( replaceEntry ( ir.first->first ) ) {
    ir.first->second = v;
  }
}

对于像int这样的值类型,上述方法比查找和插入的组合更有效(在没有编译器优化的情况下)。如上所述,这是因为对映射的搜索只发生一次。

然而,调用插入需要你已经构造了新的“value”:

class LargeDataType { /* ... */ };
typedef std::map <int, LargeDataType> MapOfLargeDataType;
typedef std::pair <MapOfLargeDataType::iterator, bool> IResult;

void foo (MapOfLargeDataType & m, int k) {

  // This call is more expensive than a find through the map:
  LargeDataType const & v = VeryExpensiveCall ( /* ... */ );

  IResult ir = m.insert (std::make_pair (k, v));
  if (ir.second) {
    // insertion took place (ie. new entry)
  }
  else if ( replaceEntry ( ir.first->first ) ) {
    ir.first->second = v;
  }
}
为了调用“insert”,我们需要为值类型进行昂贵的构造调用,而根据您在问题中所说的,您有20%的时间不会使用此新值。 在上述情况下,如果更改映射值类型不是一个选项,则首先执行“find”以检查是否需要构造元素更有效率。 或者,可以更改映射的值类型,使用您喜欢的智能指针类型存储数据的句柄。调用insert使用空指针(非常便宜的构造方式),仅在必要时构造新的数据类型。

8

在速度方面,这两者几乎没有什么区别,find将返回一个迭代器,而insert也会查找map以确定条目是否已存在。

所以,这取决于个人喜好。我通常尝试插入然后再根据需要更新,但有些人不喜欢处理返回的pair。


5

我认为,如果你进行查找然后插入的话,当你没有找到键并且在之后执行插入操作时,额外的成本会增加。这有点像按字母顺序查找书籍,没找到书,然后再次查找书籍以确定应将其插入到哪里。这归结于如何处理密钥以及它们是否经常更改。现在有一些灵活性,如果你找不到它,你可以记录、抛出异常或者采取任何你想要的操作...


我们可以使用map::lower_bound代替map::find,但是更加繁琐。 - Justme0

1

如果您关心效率,您可能需要查看hash_map<>

通常,map<>实现为二叉树。根据您的需求,使用hash_map可能更有效。


本人很想这么做。但是C++标准库中没有哈希映射(hash_map),而且PHB不允许使用标准库之外的代码。 - Superpolock
1
std::tr1::unordered_map 是建议加入下一个标准的哈希图,应该在大多数当前的STL实现中可用。 - beldaz

0

我似乎没有足够的积分来发表评论,但是被选中的答案对我来说似乎有点冗长——考虑到插入操作会返回迭代器,为什么要去搜索 lower_bound 呢?直接使用返回的迭代器不就可以了吗?很奇怪。


2
因为(在C++11之前)使用insert意味着你仍然必须创建一个std::map :: value_type对象,所以被接受的答案甚至避免了这个。 - KillianDS

-1

关于效率的任何回答都取决于您的STL的确切实现方式。唯一确定的方法是两种方式都进行基准测试。我猜差别不太可能显著,因此请根据您喜欢的风格做出决定。


2
这并不完全正确。STL与大多数其他库不同之处在于,它为其大多数操作提供了明确的大O要求。无论函数使用什么实现来实现O(log n)行为,2 * O(log n)和1 * O(log n)之间都有保证的差异。无论这种差异在您的平台上是否显着,这是一个不同的问题。但是这种差异总是存在的。 - srm
@srm 定义大O要求仍然不会告诉您操作绝对需要多长时间。您所说的保证差异不存在。 - Mark Ransom
@MarkRansom:话虽如此,如果insert没有使用从lower_bound调用提供的提示来避免第二个O(log n)搜索,那么这是一个相当糟糕的质量缺陷。没有理由让findlower_boundinsert使用根本不同的算法,因此通过避免第二次查找来将两个O(log n)查找减少为一个应该会提供有意义的好处。 - ShadowRanger

-2

map[key] - 让STL自行排序,这是最有效地传达你的意图。

是啊,说得没错。

如果你进行查找然后插入,那么在出现未命中时,你要执行2 x O(log N)操作,因为查找只让你知道是否需要插入而不是插入的位置(使用lower_bound可能会有所帮助)。我会选择直接插入,然后检查结果。


不,如果该条目存在,则返回对现有条目的引用。 - Kris Kumler
2
正如Kris K所说,使用map[key]=value将覆盖现有条目,而不是像问题中要求的那样“保留”它。您不能使用map[key]测试存在性,因为如果键不存在,它将返回一个默认构造的对象,并将其创建为键的条目。 - netjeff
重点是测试地图是否已经填充,只有在没有时才添加/覆盖。使用map[key]假定值始终已经存在。 - srm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接