我创建了一个简单的bidirectional map类,它通过在内部存储两个std::map
示例来工作,这两个示例具有相反的键/值类型,并提供了一个用户友好的界面:
template<class T1, class T2> class Bimap
{
std::map<T1, T2> map1;
std::map<T2, T1> map2;
// ...
};
- 有没有一种更有效的方法来实现双向Map,而不需要两倍的内存?
- bimap通常是如何实现的?
- 编辑:*
- bimap元素应该是可变的还是不可变的?(改变
map1
中的一个元素应该会改变map2
中的键,但键是常量,这是不可能的-解决方案是什么?) - 元素的所有权也是另一个问题:当用户在双Map中插入一个键-值对时,双Map应该复制这个键-值对并存储它,然后内部的第二个Map(具有反转的键/值)不应该复制,而是指向原始的键-值对。2如何实现呢?
- 编辑2:*
I've posted a possible implementation I made on Code Review.
6条答案
按热度按时间zbsbpyhn1#
在bimap的所有简单实现中,双重存储数据都存在一定的问题,如果可以从外部将其分解为指针的bimap,那么你可以忽略这一点,并简单地保持形式
std::map<A*,B*>
的两个Map,就像Arkaitz Jimenez已经建议的(尽管与他的回答相反,你必须关心来自外部的存储,以避免A->A*
查找)。但是如果你无论如何都有指针,为什么不简单地将std::pair<A,B>
存储在原本要分别存储A
和B
的位置上呢?用
std::map<A,B*>
代替std::map<A*,B*>
是很好的,因为这将允许例如通过新创建的具有相同内容的字符串而不是指向创建对的原始字符串的指针来查找与字符串相关联的元素。但是习惯上是在每个条目中存储键的完整副本,并且仅依赖于散列来找到正确的桶。这样,即使在哈希冲突的情况下,返回的项也将是正确的项...如果你想速战速决的话,有这个
黑客解决方案:
创建两个Map
std::map<size_t, A> mapA
和std::map<size_t, B> mapB
。插入时,散列两个要插入的元素,以获得相应Map的键。类似地实现查找。
使用
multimap
而不是map
,并在其他Map中通过查找来验证每个元素(从mapA
中获取候选b
,散列b
并在mapB
中查找是否匹配所需的键,否则迭代到下一个候选B),这是一个有效的实现-但在我看来仍然是黑客式的...你可以通过使用比较条目的元素的副本(见上文)作为存储空间来得到一个更好的解决方案,尽管这有点难以理解,详细说明一下:
更好的解决方案:
创建两组对
std::set<pair<A, B*>>
和std::set<pair<B, A*>>
,并重载operator<
和operator==
以仅考虑对的第一个元素(或提供相应的比较类)。需要创建对集而不是Map(内部看起来很相似),因为我们需要保证A
和B
在内存中的位置不变,在插入pair<A, B>
时,我们将其拆分为两个元素,以符合上述集合。查找现在可以简单地通过一个简单的
std::set
查找和一个指针解引用来完成。这个更好的解决方案类似于boost使用的解决方案--尽管他们使用一些匿名指针作为指针对的第二个元素,因此必须使用
reinterpret_cast
s。注意,元素对中的
.second
部分必须是可变的(所以我不确定std::pair
是否可用),或者即使对于这个简单的插入也必须添加另一个抽象层(std::set<pair<B, A**>> mapA
),在这两种解决方案中,都需要临时元素来返回对元素的非常数引用。vhmi4jdf2#
将所有元素存储在一个向量中并具有
<T1*,T2*>
和<T2*,T1*>
的2个Map会更高效,这样就不会将所有内容复制两次。在我看来,你试图存储两个东西,元素本身和它们之间的关系,如果你的目标是标量类型,你可以让它保持原样,但如果你的目标是处理复杂类型,它更有意义的是将存储与关系分开,并在存储之外处理关系。
sr4lhrrt3#
Boost Bimap利用了Boost Mutant Idiom。
从链接的维基百科页面:
Boost变体使用reinterpret_cast,并在很大程度上依赖于两个具有相同数据成员(类型和顺序)的不同结构的内存布局是可互换的这一假设。尽管C++标准并不保证这一属性,但实际上所有编译器都满足这一属性。
在升压源中的实现当然是相当多毛。
q7solyqu4#
如果你为你的类型
std::set<std::pair<X,Y>>
创建了一组对,你就基本上实现了你的功能,并且关于可变性和恒定性的规则被预设了(好吧,也许这些设置不是你想要的,但是可以做一些调整)。用法示例
注意:所有这些都是完整实现的粗略代码草图,即使在完善和扩展代码之后,我也不是暗示这将是
boost::bimap
的替代品,而仅仅是一种自制的方法,可以通过值和键搜索关联容器。Live example
f45qwnt85#
使用中间数据结构和间接寻址的一个可能的实现是:
插入
假设必须插入(X,Y),其中X、Y分别是A和B的示例,则:
1.在
mapA
--- O(长尺寸)中插入(X,尺寸)1.将(Y,尺寸)插入
mapB
--- O(长度尺寸)1.那么
register
中的push_back
(IterX,IterY)--- O(1),这里IterX和IterY是mapA
和mapB
中对应元素的迭代器,分别由(1)和(2)获得。查找
查找类型A的元素X的图像:
1.在
mapA
中获取Map到X的整数。--- O(lg n)1.使用int来索引
register
并获得相应的IterY。--- O(1)1.一旦你有了IterY,你就可以通过
IterY->first
得到Y。--- O(1)所以这两个操作都是在O(lgn)中实现的。
空间:A和B的对象的所有副本只需要存储一次,但是有很多簿记的东西,但是当你有大的对象时,那也没有多大意义。
注意:这个实现依赖于Map的迭代器永远不会失效的事实。因此,
register
的内容总是有效的。可以在here中找到此实现的更详细版本
aor9mmx16#
这个怎么样?
这里,我们避免了一种类型(T1)的双重存储,另一种类型(T2)仍然是双重存储。