如何使向量元素独特?(删除不相邻的重复项)

aJ.*_*aJ. 34 c++ stl vector unique

我有一个包含很少非相邻重复项的向量.

举个简单的例子,考虑一下:

2 1 6 1 4 6 2 1 1
Run Code Online (Sandbox Code Playgroud)

我试图vector通过删除不相邻的重复项并保持元素的顺序来使这个独特.

结果将是:

2 1 6 4 
Run Code Online (Sandbox Code Playgroud)

我尝试的解决方案是:

  1. 插入std :: set但这种方法的问题是它会扰乱元素的顺序.
  2. 使用std :: sort和std :: unique的组合.但同样的订单问题.
  3. 手动重复消除:

        Define a temporary vector TempVector.
        for (each element in a vector)
        {
            if (the element does not exists in TempVector)
            {
                add to TempVector;
            }
        }
        swap orginial vector with TempVector.
    
    Run Code Online (Sandbox Code Playgroud)

我的问题是:

是否有任何STL算法可以从向量中删除不相邻的重复项?它的复杂性是什么?

fa.*_*fa. 13

我想你会这样做:

我会在向量上使用两个迭代器:

第一个读取数据并将其插入临时集.

当读取数据不在集合中时,将其从第一个迭代器复制到第二个迭代器并递增它.

最后,您只将数据保留到第二个迭代器.

复杂度为O(n .log(n)),因为重复元素的查找使用集合而不是向量.

#include <vector>
#include <set>
#include <iostream>

int main(int argc, char* argv[])
{
    std::vector< int > k ;

    k.push_back( 2 );
    k.push_back( 1 );
    k.push_back( 6 );
    k.push_back( 1 );
    k.push_back( 4 );
    k.push_back( 6 );
    k.push_back( 2 );
    k.push_back( 1 );
    k.push_back( 1 );

{
    std::vector< int >::iterator r , w ;

    std::set< int > tmpset ;

    for( r = k.begin() , w = k.begin() ; r != k.end() ; ++r )
    {
        if( tmpset.insert( *r ).second )
        {
            *w++ = *r ;
        }
    }

    k.erase( w , k.end() );
}


    {
        std::vector< int >::iterator r ;

        for( r = k.begin() ; r != k.end() ; ++r )
        {
            std::cout << *r << std::endl ;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 使用`find`然后`insert`是低效的.如果插入了值,则tmpset.insert(*r).second`将为true;如果值已在集合中,则为false. (4认同)
  • 这是一个合理的空间/速度权衡.替代方案可以是unordered_set(散列)以跟踪之前看到的值,其将是O(N).但是为`r`和`w`拼出`read`和`write`会不会有害?这段代码非常简单,好的名称会使注释变得多余.另外,`r`应该是`const_iterator`; 它不会修改`k` (4认同)
  • 现在是时候我们被允许写`std :: vector <int> k({1,2,3});`... (3认同)

CB *_*ley 13

如果不使用临时性set,可能会(可能)性能损失:

template<class Iterator>
Iterator Unique(Iterator first, Iterator last)
{
    while (first != last)
    {
        Iterator next(first);
        last = std::remove(++next, last, *first);
        first = next;
    }

    return last;
}
Run Code Online (Sandbox Code Playgroud)

用作:

vec.erase( Unique( vec.begin(), vec.end() ), vec.end() );
Run Code Online (Sandbox Code Playgroud)

对于较小的数据集,实现的简单性和缺乏额外的分配可能会抵消使用额外的理论上更高的复杂性set.但是,使用代表性输入进行测量是唯一可以确定的方法.


And*_*ler 7

问题是"是否有任何STL算法......?它的复杂性是什么?" 实现这样的功能是有道理的std::unique:

template <class FwdIterator>
inline FwdIterator stable_unique(FwdIterator first, FwdIterator last)
{
    FwdIterator result = first;
    std::unordered_set<typename FwdIterator::value_type> seen;

    for (; first != last; ++first)
        if (seen.insert(*first).second)
            *result++ = *first;
    return result;
}
Run Code Online (Sandbox Code Playgroud)

所以这就是如何std::unique实现加上一个额外的集合.该unordered_set应是比常规更快set.删除的所有元素都与它们之前的元素相等(第一个元素被保留,因为我们无法统一到任何东西).迭代器返回指向范围内的新结尾[first,last).

编辑:最后一句意味着容器本身不被修改unique.这可能令人困惑.以下示例实际上将容器缩减为统一集.

1: std::vector<int> v(3, 5);
2: v.resize(std::distance(v.begin(), unique(v.begin(), v.end())));
3: assert(v.size() == 1);
Run Code Online (Sandbox Code Playgroud)

第1行创建一个向量{ 5, 5, 5 }.在第2行中,调用unique返回第二个元素的迭代器,第二个元素是第一个不唯一的元素.因此distance返回1并 resize修剪向量.


Ric*_*den 6

你可以使用以下方法删除fa的答案中的一些循环remove_copy_if:

class NotSeen : public std::unary_function <int, bool>
{
public:
  NotSeen (std::set<int> & seen) : m_seen (seen) { }

  bool operator ()(int i) const  {
    return (m_seen.insert (i).second);
  }

private:
  std::set<int> & m_seen;
};

void removeDups (std::vector<int> const & iv, std::vector<int> & ov) {
  std::set<int> seen;
  std::remove_copy_if (iv.begin ()
      , iv.end ()
      , std::back_inserter (ov)
      , NotSeen (seen));
}
Run Code Online (Sandbox Code Playgroud)

这对算法的复杂性没有影响(即写入它也是O(n log n)).您可以使用unordered_set对此进行改进,或者如果值的范围足够小,则可以简单地使用数组或位阵.