仅使用几乎相等的标准(无排序)从容器中删除重复项的最有效方法是什么

What is the most efficient way of removing duplicates from a container only using almost equality criteria (no sort)

本文关键字:删除 方法 是什么 有效 排序 标准      更新时间:2023-10-16

当我无法定义operator<时,例如当我只能定义模糊比较函数时,我如何从未排序的容器(主要是向量)中删除重复项。

这个使用排序的答案不起作用,因为我无法定义用于排序数据的函数。

template <typename T>
void removeDuplicatesComparable(T& cont){
for(auto iter=cont.begin();iter!=cont.end();++iter){
cont.erase(std::remove(boost::next(iter),cont.end(),*iter),cont.end());
}
}

这是O(n²),在缓存命中方面应该非常本地化。有没有更快或者至少更整洁的解决方案?

编辑:关于为什么我不能使用集合。我做几何比较。一个例子可能是这样的,但我也有其他不同于多边形的实体。

bool match(SegPoly const& left,SegPoly const& right,double epsilon){
double const cLengthCompare = 0.1; //just an example
if(!isZero(left.getLength()- right.getLength(), cLengthCompare)) return false;
double const interArea =areaOfPolygon(left.intersected(right)); //this is a geometric intersection
if(!isZero(interArea-right.getArea(),epsilon)) return false;
else return true;
}

因此,对于这样的比较,我不知道如何制定排序或整洁的哈希函数。

首先,不要一次移除一个元素。

接下来,使用哈希表(或类似结构)来检测重复项。

如果不需要保持顺序,那么将所有元素复制到一个哈希集中(这会破坏重复项),然后使用哈希集中剩下的值重新创建向量。

如果您需要保留订单,那么:

  1. 将读和写迭代器设置到向量的开头
  2. 开始移动读取迭代器,根据哈希集或八叉树或其他可以快速查找附近元素的东西来检查元素
  3. 对于与hashset/octtree中的一个元素冲突的每个元素,只推进读取迭代器
  4. 对于不冲突的元素,从读迭代器移动到写迭代器,复制到hashset/octtree,然后两者都前进
  5. 当读迭代器到达末尾时,调用erase来截断写迭代器位置的向量

八叉树的关键优势在于,虽然它不会让你立即确定是否有足够接近的东西可以成为"重复",但它允许你只针对近邻进行测试,不包括大部分数据集。因此,根据空间分布,您的算法可能是O(N lg N),甚至是O(N lg lg N)

同样,如果你不在乎排序,你实际上可以把幸存者移到hashset/octtree中,最后把他们移回向量中(紧凑地)。

如果您不想重写代码以防止重复项从一开始就被放置在向量中,您可以这样做:

std::vector<Type> myVector;
// fill in the vector's data
std::unordered_set<Type> mySet(myVector.begin(), myVector.end());
myVector.assign(mySet.begin(), mySet.end());

其将是O(2*n)=O(n)。

std::set(或std::unordered_set,它使用哈希而不是比较)不允许重复,因此它将在初始化集合时消除它们。然后使用未重复的数据重新指定矢量。

由于您坚持不能创建哈希,另一种选择是创建一个临时向量:

std::vector<Type> vec1;
// fill vec1 with your data
std::vector<Type> vec2;
vec2.reserve(vec1.size()); // vec1.size() will be the maximum possible size for vec2
std::for_each(vec1.begin(), vec1.end(), [&](const Type& t)
{
bool is_unique = true;
for (std::vector<Type>::iterator it = vec2.begin(); it != vec2.end(); ++it)
{
if (!YourCustomEqualityFunction(s, t))
{
is_unique = false;
break;
}
}
if (is_unique)
{
vec2.push_back(t);
}
});
vec1.swap(vec2);

如果副本是一个问题,请切换到指针向量,这样可以减少内存的重新分配:

std::vector<std::shared_ptr<Type>> vec1;
// fill vec1 with your data
std::vector<std::shared_ptr<Type>> vec2;
vec2.reserve(vec1.size()); // vec1.size() will be the maximum possible size for vec2
std::for_each(vec1.begin(), vec1.end(), [&](const std::shared_ptr<Type>& t)
{
bool is_unique = true;
for (std::vector<Type>::iterator it = vec2.begin(); it != vec2.end(); ++it)
{
if (!YourCustomEqualityFunction(*s, *t))
{
is_unique = false;
break;
}
}
if (is_unique)
{
vec2.push_back(t);
}
});
vec1.swap(vec2);