unordered_map <type,bool =“”>与set <type> </type> </type,>

时间:2014-06-12 21:31:10

标签: c++ set hashtable

使用散列表集合类型的实际权衡是什么,例如std :: unordered_map与std :: set?

对于我正在处理的事情(在C ++中),我有一个设置交集问题,即从一对大列表中识别重复项目。

我的第一个假设是迭代第一个列表并将每个列表插入std::unordered_map<T, bool>或(std :: hash_map),其中插入时的值参数始终为true。然后在hash_map中查找第二个列表中每个项目的查找。工作假设是每次插入都是O(1),每次查找也是O(1)。

然后我开始认为也许std :: set更合适。一些粗略的在线搜索显示std :: set的实现是红色/黑色true,插入和/或查找可能在运行时间O(lg n)而不是O(1)。 (这是正确的吗?)

我假设每个之间的权衡可能是内存使用和散列函数的使用(与直接比较)。我使用的数据的实际类型只是一个unsigned int。我可以想象这个问题的动态可能会根据具有不同散列函数的更复杂类型而改变。

2 个答案:

答案 0 :(得分:2)

假设您有两个分别具有L1L2个元素的列表(例如,NM)。而L1L2也有独特的元素。 (即每个L#(i) != L#(j) i != j。)


您的第一个算法:

第1步:将L1的元素复制到unordered_map U,具有时间复杂性:

  • 平均案例O(N)

  • 最差情况O(N^2)

第2步:遍历L2的元素,并检查每个元素是否存在U

  • 平均案例O(M) * O(1) = O(M)

  • 最差情况O(M) * O(N) = O(M*N)

<强>总体:

  • 平均个案O(N) + O(M)线性复杂性

  • 最差情况O(N^2) + O(M*N)二次复杂度


你的第二个算法:

第1步:将L1的元素复制到set S,具有时间复杂性:

  • 平均案例O(N) * O(log(N))

  • 最差情况O(N) * O(log(N))

第2步:遍历L2的元素,并检查每个元素是否存在S

  • 平均案例O(M) * O(log(N))

  • 最差情况O(M) * O(log(N))

<强>总体:

  • 平均个案O(M) * O(log(N)) + O(N) * O(log(N))线性对数复杂度

  • 最差情况O(M) * O(log(N)) + O(N) * O(log(N))线性对数复杂度


结果:

渐近第一算法在平均情况下获胜。在最糟糕的情况下,通过第二种算法输了。


评论:

  1. 使用unordered_set渐近的提议算法与第一算法的时间复杂度相同。实际上更好更快,因为你没有布尔值的冗余。
  2. 在实践中,由于高速缓冲存储器的存在,不仅仅是理论上的复杂性。似乎具有连续内存存储元素的数据结构比具有分段存储元素的其他数据结构获得更好的性能。 Herb Sutter很好地解释了这个效果video lecture
  3. 以上所有实践都是hocus pocus。 始终您必须对代码进行分析,以确定哪种算法在实践中更快。 Eric Brumer在video lecture中很好地解释了这一点。

答案 1 :(得分:0)

设置&LT;&GT;和地图&lt;&gt;通常使用树数据结构实现,因此插入和查找会产生O(lg n)运行时间。

unordered_set&LT;&GT;和unordered_map&lt;&gt;通常使用哈希表结构实现,从而获得插入和查找的O(1)性能。

待定 - 我不确定为什么设置&lt;&gt;和地图&lt;&gt;可以实现为哈希表和双向链表的组合。散列表中的每个元素都封装了值和指向插入的上一个/下一个节点的指针。那将是另一天的问题。