确定无序向量<t>是否具有所有唯一元素</t>

时间:2010-05-04 21:41:32

标签: c++ algorithm stl unique

分析我的cpu绑定代码表明我花了很长时间检查容器是否包含完全唯一的元素。假设我有一些未分类元素的大容器(定义了<=),我有两个关于如何做到这一点的想法:

第一个使用集合:

template <class T>
bool is_unique(vector<T> X) {
  set<T> Y(X.begin(), X.end());
  return X.size() == Y.size();
}

第二次循环元素:

template <class T>
bool is_unique2(vector<T> X) {
  typename vector<T>::iterator i,j;
  for(i=X.begin();i!=X.end();++i) {
    for(j=i+1;j!=X.end();++j) {
      if(*i == *j) return 0;
    }
  }
  return 1;
}

我已经尽力测试了它们,而且从阅读有关STL的文档中我可以收集到的,答案是(照例),这取决于。我认为在第一种情况下,如果所有元素都是唯一的,那么它非常快,但如果存在大的简并性,则操作似乎需要O(N ^ 2)时间。对于嵌套迭代器方法,相反的情况似乎是正确的,如果X[0]==X[1]它会快速点亮,但如果所有元素都是唯一的,则需要(可以理解)O(N ^ 2)时间。

有没有更好的方法来实现这一点,也许是为此目的而构建的STL算法?如果没有,有什么建议可以提高效率吗?

11 个答案:

答案 0 :(得分:27)

你的第一个例子应该是O(N log N),因为set每次插入需要log N时间。我认为不可能有更快的O.

第二个例子显然是O(N ^ 2)。系数和内存使用率很低,因此在某些情况下它可能更快(甚至最快)。

这取决于T是什么,但对于通用性能,我建议对指向对象的指针进行排序。

template< class T >
bool dereference_less( T const *l, T const *r )
 { return *l < *r; } 

template <class T>
bool is_unique(vector<T> const &x) {
    vector< T const * > vp;
    vp.reserve( x.size() );
    for ( size_t i = 0; i < x.size(); ++ i ) vp.push_back( &x[i] );
    sort( vp.begin(), vp.end(), ptr_fun( &dereference_less<T> ) ); // O(N log N)
    return adjacent_find( vp.begin(), vp.end(),
           not2( ptr_fun( &dereference_less<T> ) ) ) // "opposite functor"
        == vp.end(); // if no adjacent pair (vp_n,vp_n+1) has *vp_n < *vp_n+1
}

或STL风格,

template <class I>
bool is_unique(I first, I last) {
    typedef typename iterator_traits<I>::value_type T;
    …

如果你可以重新排序原始矢量,当然,

template <class T>
bool is_unique(vector<T> &x) {
    sort( x.begin(), x.end() ); // O(N log N)
    return adjacent_find( x.begin(), x.end() ) == x.end();
}

答案 1 :(得分:9)

如果要快速确定它是否只包含唯一元素,则必须对矢量进行排序。否则,您可以做的最好的是O(n ^ 2)运行时或O(n log n)运行时与O(n)空间。我认为最好编写一个假定输入已排序的函数。

template<class Fwd>
bool is_unique(In first, In last)
{
    return adjacent_find(first, last) == last;
}

然后让客户端对矢量进行排序,或者生成矢量的排序副本。这将为动态编程打开一扇门。也就是说,如果客户端在过去对矢量进行了排序,那么他们可以选择保留并引用该排序后的矢量,以便他们可以为O(n)运行时重复此操作。

答案 2 :(得分:6)

标准库有std::unique,但这需要你制作整个容器的副本(注意,在你的两个例子中你也复制了整个矢量,因为你不必要地通过了矢量值)。

template <typename T>
bool is_unique(std::vector<T> vec)
{
    std::sort(vec.begin(), vec.end());
    return std::unique(vec.begin(), vec.end()) == vec.end();
}

这是否比使用std::set更快,如你所知,依赖: - )。

答案 3 :(得分:6)

仅仅使用从一开始就提供这种“保证”的容器是不可行的吗?在插入时而不是在将来的某个时刻标记副本是否有用?当我想做这样的事情时,那就是我走的方向;只是使用set作为“主要”容器,如果我需要维护原始顺序,可能会构建一个并行向量,但当然这会对内存和CPU可用性做出一些假设......

答案 4 :(得分:6)

首先,你可以结合两者的优点:如果你已经发现了副本,就停止构建集合:

template <class T>
bool is_unique(const std::vector<T>& vec)
{
    std::set<T> test;
    for (typename std::vector<T>::const_iterator it = vec.begin(); it != vec.end(); ++it) {
        if (!test.insert(*it).second) {
            return false;
        }
    }
    return true;
}

顺便说一句,Potatoswatter说明在通用情况下您可能希望避免复制T,在这种情况下,您可能会使用std::set<const T*, dereference_less>代替。


如果它不是通用的,你当然可以做得更好。例如,如果你有一个已知范围的整数向量,你可以在一个数组(或甚至是bitset)中标记一个元素是否存在。

答案 5 :(得分:2)

您可以使用std::unique,但需要先对范围进行排序:

template <class T>
bool is_unique(vector<T> X) {
  std::sort(X.begin(), X.end());
  return std::unique(X.begin(), X.end()) == X.end();
}

std::unique修改序列并将迭代器返回到唯一集的末尾,所以如果它仍然是向量的末尾,那么它必须是唯一的。

这在nlog(n)中运行;与您的设置示例相同。我认为理论上你不能保证更快地完成它,尽管使用C ++ 0x std::unordered_set而不是std::set会在预期的线性时间内完成 - 但这要求你的元素可以像以及定义operator ==,这可能不那么容易。

此外,如果您没有在示例中修改向量,则可以通过const引用传递它来提高性能,因此您不需要对其进行不必要的复制。

答案 6 :(得分:2)

如果我可以加2美分。

首先,正如@Potatoswatter所述,除非您的元素复制便宜(内置/小POD),否则您将需要使用指向原始元素的指针而不是复制它们。

其次,有两种策略可供选择。

  1. 只需确保首先插入没有重复内容。当然,这意味着控制插入,这通常通过创建专用类(使用向量作为属性)来实现。
  2. 每当需要该属性时,请检查重复项
  3. 我必须承认我会倾向于第一个。封装,明确责任分离和所有这些。

    无论如何,根据要求有多种方式。第一个问题是:

    • 我们是否必须按照特定顺序放置vector中的元素,还是可以“混乱”它们?

    如果我们可以搞砸他们,我建议保持vector排序:Loki::AssocVector应该让你开始。 如果没有,那么我们需要在结构上保留一个索引来确保这个属性...等一下:Boost.MultiIndex救援?

    第三:正如你所说的那样,简单的线性搜索加倍得到平均O(N 2 )复杂度,这是不好的。

    如果已定义<,则排序很明显,其O(N log N)复杂度。 使T Hashable成为可能也是值得的,因为std::tr1::hash_set可以产生更好的时间(我知道,你需要一个RandomAccessIterator,但如果T是Hashable,那么它很容易拥有T* Hashable to;))

    但最终真正的问题是我们的建议是必要的通用,因为我们缺乏数据。

    • 什么是T,你打算算法是通用的吗?
    • 元素的数量是多少? 10,100,10,000,1.000.000?因为渐近的复杂性在处理几百个时就是一种模糊的行为....
    • 当然:你能确保插入时的独特性吗?你能修改矢量本身吗?

答案 7 :(得分:1)

好吧,你的第一个应该只采用N log(N),所以这显然是这个应用程序更糟糕的情况。

但是,如果您在向集合中添加内容时进行检查,则应该能够获得更好的最佳案例:

template <class T>
bool is_unique3(vector<T> X) {
  set<T> Y;
  typename vector<T>::const_iterator i;
  for(i=X.begin(); i!=X.end(); ++i) {
    if (Y.find(*i) != Y.end()) {
      return false;
    }
    Y.insert(*i);
  }
  return true;
}

这应该是O(1)最佳情况,O(N log(N))最坏情况,平均情况取决于输入的分布。

答案 8 :(得分:1)

如果您在向量中存储的类型T很大并且复制它的代价很高,请考虑为向量元素创建指针或迭代器的向量。根据指向的元素对其进行排序,然后检查唯一性。

你也可以使用std :: set。模板看起来像这样

template <class Key,class Traits=less<Key>,class Allocator=allocator<Key> > class set

我认为您可以提供适当的Traits参数并插入原始指针以提高速度,或者为指针实现一个简单的包装类,其中&lt;操作

不要使用构造函数插入集合。使用插入方法。该方法(重载之一)具有签名

pair <iterator, bool> insert(const value_type& _Val);

通过检查结果(第二个成员)您通常可以比插入所有元素更快地检测到重复。

答案 9 :(得分:1)

在(非常)特殊情况下,用已知的,不太大的最大值N对离散值进行排序 您应该能够启动存储桶排序,只需检查每个存储桶中的值的数量是否低于2.

bool is_unique(const vector<int>& X, int N)
{
  vector<int> buckets(N,0);
  typename vector<int>::const_iterator i;
  for(i = X.begin(); i != X.end(); ++i)
    if(++buckets[*i] > 1)
      return false;
  return true;
}

这种复杂性将是O(n)。

答案 10 :(得分:0)

使用当前的C ++标准容器,您可以在第一个示例中找到一个很好的解决方案。但是如果你可以使用哈希容器,你可能会做得更好,因为对于标准集,哈希集将是n O(1)而不是n O(log n)。当然,一切都取决于n的大小和您特定的库实现。