分析我的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算法?如果没有,有什么建议可以提高效率吗?
答案 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),否则您将需要使用指向原始元素的指针而不是复制它们。
其次,有两种策略可供选择。
我必须承认我会倾向于第一个。封装,明确责任分离和所有这些。
无论如何,根据要求有多种方式。第一个问题是:
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
,你打算算法是通用的吗?答案 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的大小和您特定的库实现。