我有两个vector<MyType*>
名为A
and的对象B
。MyType 类有一个字段ID
,我想获取MyType*
其中 inA
但不在B
. 我正在开发一个图像分析应用程序,我希望能找到一个快速/优化的解决方案。
4 回答
无序方法通常具有二次复杂度,除非数据预先排序(通过您的 ID 字段),在这种情况下它将是线性的并且不需要通过 B 重复搜索。
struct CompareId
{
bool operator()(const MyType* a, const MyType* b) const
{
return a>ID < b->ID;
}
};
...
sort(A.begin(), A.end(), CompareId() );
sort(B.begin(), B.end(), CompareId() );
vector<MyType*> C;
set_difference(A.begin(), A.end(), B.begin(), B.end(), back_inserter(C) );
另一种解决方案是使用带有用于 StrictWeakOrdering 模板参数的 CompareId 的有序容器,例如 std::set。如果您需要应用大量集合操作,我认为这会更好。这有它自己的开销(作为一棵树),但如果你真的发现这是一个效率问题,你可以实现一个快速内存分配器来超快速地插入和删除元素(注意:只有在你分析并确定这是瓶颈)。
警告:进入有些复杂的领域。
您可以考虑另一种解决方案,如果适用,它可能会非常快,而且您永远不必担心对数据进行排序。基本上,让任何共享相同 ID 的 MyType 对象组存储一个共享计数器(例如:指向 unsigned int 的指针)。
这将需要创建一个 ID 到计数器的映射,并且需要在每次基于其 ID 创建 MyType 对象时从映射中获取计数器。由于您有具有重复 ID 的 MyType 对象,因此您不必像创建 MyType 对象那样频繁地插入到地图中(大多数可能只获取现有的计数器)。
除此之外,还有一个全局“遍历”计数器,每当它被获取时就会递增。
static unsigned int counter = 0;
unsigned int traversal_counter()
{
// make this atomic for multithreaded applications and
// needs to be modified to set all existing ID-associated
// counters to 0 on overflow (see below)
return ++counter;
}
现在让我们回到存储 MyType* 的 A 和 B 向量的位置。要获取 A 中不在 B 中的元素,我们首先调用 traversal_counter()。假设这是我们第一次调用它,这将给我们一个遍历值 1。
现在遍历 B 中的每个 MyType* 对象,并将每个对象的共享计数器设置为从 0 到遍历值 1。
现在遍历 A 中的每个 MyType* 对象。计数器值与当前遍历值 (1) 不匹配的对象是 A 中不包含在 B 中的元素。
当遍历计数器溢出时会发生什么?在这种情况下,我们遍历存储在 ID 映射中的所有计数器,并将它们与遍历计数器本身一起设置回零。如果它是 32 位无符号整数,则这只需要在大约 40 亿次遍历中发生一次。
这是您可以应用于给定问题的最快解决方案。它可以对未排序的数据执行任何线性复杂性的集合操作(并且始终,不仅仅是在像哈希表这样的最佳情况下),但它确实引入了一些复杂性,所以只有在你真的需要时才考虑它。
std::sort
根据 ID对两个向量 ( ) 进行排序,然后使用std::set_difference
. 例如,您将需要定义一个自定义比较器以传递给这两种算法
struct comp
{
bool operator()(MyType * lhs, MyType * rhs) const
{
return lhs->id < rhs->id;
}
};
首先看问题。您想要“A 中的所有内容而不是 B 中的所有内容”。这意味着您将不得不访问“A 中的所有内容”。您还必须访问 B 中的所有内容,以了解 B 中存在和不存在的内容。因此这表明应该有一个O(n) + O(m)
解决方案,或者冒昧地忽略 n 和 m, 之间的区别O(2n)
。
让我们考虑一下std::set_difference
方法。每个排序是O(n log n)
,并且 set_difference 是O(n)
。所以 sort-sort-set_difference 方法是O(n + 2n log n)
. 让我们称之为O(4n)
。
另一种方法是首先将 B 的元素放在一个集合(或映射)中。遍历B 以创建集合是O(n)
加上每个元素的插入O(log n)
,然后是遍历 AO(n) 的迭代,并查找 A 的每个元素 (log n),得到总数:O(2n log n)
。让我们称之为O(3n)
,它稍微好一点。
最后,使用 unordered_set(或 unordered_map),并假设我们得到O(1)
插入和O(1)
查找的平均情况,我们有一个方法是O(2n)
. 啊哈!
真正的胜利在于 unordered_set(或 map)可能是首先表示数据的最自然选择,即正确的设计会产生优化的实现。这并不总是发生,但发生时很好!
如果 B 先存在于 A,那么在填充 A 时,您可以在 C 向量中记账。