unordered_map<类型,布尔值>与设置<TYPE>值

unordered_map<TYPE, bool> vs. set<TYPE>

本文关键字:gt lt 设置 TYPE 类型 map unordered 布尔值      更新时间:2023-10-16

使用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(lgn)而不是O(1)。(对吗?)

我假设两者之间的权衡可能是内存使用和哈希函数的使用(与直接比较相比)。我使用的数据的实际类型只是一个unsigned int。我可以想象,这个问题的动态可能会因为一个更复杂的类型和不同的哈希函数而改变。

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


你的第一个算法:

step1:将L1的元素拷贝到unordered_map U中,时间复杂度:

  • 平均情况O(N) .

  • 最坏情况O(N^2) .

step2:遍历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)二次复杂度


你的第二个算法:

step1:将L1的元素复制到 sets中,时间复杂度:

  • 平均情况O(N) * O(log(N)) .

  • 最坏情况O(N) * O(log(N)) .

step2:遍历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在这个视频讲座中很好地解释了这种效果。
  3. 所有这些在实践中都是骗局。总是你必须分析你的代码,以确定哪种算法在实践中更快。Eric Brumer在这个视频讲座中很好地解释了这一点。

set<>和map<>通常使用树数据结构实现,因此插入和查找的运行时间为0 (lgn)。

unordered_set<>和unordered_map<>通常使用散列表结构实现,因此插入和查找的性能为0(1)。

有待确定-我不确定为什么set<>和map<>可以作为哈希表和双重链表的组合来实现。其中哈希表中的每个元素都封装了值和指向插入的前一个/下一个节点的指针。这是以后的问题了