如何有效地比较矢量与C ++?

时间:2013-06-30 19:38:49

标签: c++ c++11

我需要C ++中微优化的建议,用于矢量比较函数, 它比较了两个向量的相等性和元素的顺序无关紧要。

template <class T>
static bool compareVectors(const vector<T> &a, const vector<T> &b)
{
  int n = a.size();
  std::vector<bool> free(n, true);
  for (int i = 0; i < n; i++) {
    bool matchFound = false;
    for (int j = 0; j < n; j++) {
      if (free[j] && a[i] == b[j]) {
        matchFound = true;
        free[j] = false;
        break;
      }
    }
    if (!matchFound) return false;
  }
  return true;
}

这个功能被大量使用,我正在考虑优化它的可能方法。 你能给我一些建议吗?顺便说一句,我使用的是C ++ 11。

由于

7 个答案:

答案 0 :(得分:13)

它只是意识到这个代码只做了一种“设置等效”检查(现在我看到你确实说过,我是一个糟糕的读者!)。这可以更简单地实现

template <class T>
static bool compareVectors(vector<T> a, vector<T> b)
{
    std::sort(a.begin(), a.end());
    std::sort(b.begin(), b.end());
    return (a == b);
}

您需要添加标题algorithm

如果你的向量总是大小相同,你可能想在方法的开头添加一个断言:

assert(a.size() == b.size());

如果您错误地执行了不等长度的操作,这对于调试程序会很方便。

否则,如果矢量长度不等,则矢量不能相同,所以只需添加

if ( a.size() != b.size() )
{
   return false;
}
在排序说明之前

。这将为您节省大量时间。

这在技术上的复杂性是O(n*log(n)),因为它主要依赖于(通常)复杂性的排序。这比您的O(n^2)方法更好,但由于需要的副本可能会更糟。如果可以对原始矢量进行排序,则无关紧要。


如果您想坚持自己的方法,但要进行调整,我的想法就是这样:

您可以使用std::find

template <class T>
static bool compareVectors(const vector<T> &a, const vector<T> &b)
{
  const size_t n = a.size(); // make it const and unsigned!
  std::vector<bool> free(n, true);
  for ( size_t i = 0; i < n; ++i )
  {
      bool matchFound = false;
      auto start = b.cbegin();
      while ( true )
      {
          const auto position = std::find(start, b.cend(), a[i]);
          if ( position == b.cend() )
          {
              break; // nothing found
          }
          const auto index = position - b.cbegin();
          if ( free[index] )
          {
             // free pair found
             free[index] = false;
             matchFound = true;
             break;
          }
          else
          {
             start = position + 1; // search in the rest
          }
      }
      if ( !matchFound )
      {
         return false;
      }
   }
   return true;
}

另一种可能性是更换结构以存储自由位置。您可以尝试std::bitset或仅将已使用的索引存储在向量中,并检查该索引向量中是否存在匹配。如果此函数的结果通常是相同的(因此大部分都是真的或大多数是假的),您可以优化数据结构以反映这一点。例如。如果结果通常是假的,我会使用已使用索引的列表,因为可能只需要存储少量索引。

此方法与您的方法具有相同的复杂性。使用std :: find搜索事物有时比手动搜索更好。 (例如,如果数据已排序且编译器知道它,则可以是二进制搜索)。

答案 1 :(得分:13)

你可以概率比较O(n)中的两个未排序的向量(u,v):

计算:

U= xor(h(u[0]), h(u[1]), ..., h(u[n-1]))
V= xor(h(v[0]), h(v[1]), ..., h(v[n-1]))

如果U == V,那么矢量可能相等。

h(x)是任何non-cryptographic hash function - 例如MurmurHash。 (加密功能也会起作用,但通常会慢一些。)

(即使没有散列,这也可以工作,但是当值的范围相对较小时,它会更不稳定。)

128位散列函数对于许多实际应用来说已经足够了。

答案 2 :(得分:6)

我注意到大多数提出的解决方案涉及输入向量的排序。我认为排序数组计算更多的是评估两个向量相等所必需的(如果输入向量是常数,则是副本需要制作)。 另一种方法是构建一个关联容器来计算每个向量中的元素...也可以在parrallel中减少两个向量。在非常大的向量的情况下,可以提供一个很好的加速。 / p>

template <typename T>  bool compareVector(const std::vector<T> &  vec1, const std::vector<T> & vec2) {
    if (vec1.size() != vec2.size())
        return false ;

    //Here we assuame that T is hashable ...
    auto count_set =  std::unordered_map<T,int>();

    //We count the element in each vector...
    for (unsigned int count = 0 ; count <  vec1.size();++count)
    {
        count_set[vec1[count]]++;
        count_set[vec2[count]]--;
    } ;

    // If everything balance out we should have zero everywhere
    return std::all_of(count_set.begin(),count_set.end(),[](const std::pair<T,int> p) { return p.second == 0 ;});

}

这种方式取决于你的hashsing函数的性能,我们可能会得到booth vector长度的线性复杂度(vs n * logn with the sorting)。 NB代码可能有一些bug,确实有时间检查它......

基于这种比较两种向量与基于排序的比较的方式我得到了ubuntu 13.10,vmware core i7 gen 3:

通过计数比较200个500个元素的矢量需要0.184113秒

通过排序比较200个500个元素的向量需要0.276409秒

通过计数比较1000个元素的200个向量需要0.359848秒

通过排序比较200个1000个元素的向量需要0.559436秒

通过计数比较200个元素的200个向量需要1.78584秒

通过排序比较200个5000个元素的矢量需要2.97983秒

答案 3 :(得分:1)

正如其他人所说,预先对矢量进行排序会提高性能。

作为一个额外的优化,您可以从矢量中进行堆积比较(使用复杂度O(n)而不是使用O(n * log(n))进行排序。

之后,您可以从两个堆中弹出元素(复杂度O(log(n))),直到出现不匹配为止。

这样做的好处是,如果它们不相等,您只能堆积而不是对它们进行排序。

以下是代码示例。要知道什么是最快的,您必须使用一些示例数据来衡量您的用例。

#include <algorithm>

typedef std::vector<int> myvector;

bool compare(myvector& l, myvector& r)
{
   bool possibly_equal=l.size()==r.size();
   if(possibly_equal)
     {
       std::make_heap(l.begin(),l.end());
       std::make_heap(r.begin(),r.end());
       for(int i=l.size();i!=0;--i)
         {
           possibly_equal=l.front()==r.front();
           if(!possibly_equal)
             break;
           std::pop_heap(l.begin(),l.begin()+i);
           std::pop_heap(r.begin(),r.begin()+i);
         }
     }
  return possibly_equal;
}

答案 4 :(得分:0)

如果在相同的矢量上大量使用此功能,最好保留已排序的副本以供比较。

理论上,如果每一个被比较一次,排序矢量并比较排序的矢量甚至可能更好(排序是O(n * log(n)),比较排序的矢量O(n),而你的函数是O(n ^ 2)。 但是,如果你不经常比较相同的向量,我认为为分类向量分配内存所花费的时间会使任何理论上的收益相形见绌。

与所有优化一样,分析是确保的唯一方法,我会尝试一些std::sort / std::equal组合。

答案 5 :(得分:0)

像斯特凡说的那样,你需要排序以获得更好的复杂性。 然后你可以使用 ==运算符(tnx用于注释中的更正 - ste等也可以工作,但它更适合比较范围而不是整个容器)

如果这还不够快,那么只需要进行微优化。

还保证矢量大小相同吗? 如果没有在开始时进行检查。

答案 6 :(得分:-1)

另一种可能的解决方案(只有在所有元素都是唯一的情况下才可行),这应该会在某种程度上改善@stefan的解决方案(虽然复杂性将保留在O(NlogN)中)是这样的:

template <class T>
static bool compareVectors(vector<T> a, const vector<T> & b)
{
    // You should probably check this outside as it can 
    // avoid you the copy of a
    if (a.size() != b.size()) return false;

    std::sort(a.begin(), a.end());
    for (const auto & v : b)
        if ( !std::binary_search(a.begin(), a.end(), v) ) return false;
    return true;
}

这应该更快,因为它直接作为O(NlogN)操作执行搜索,而不是排序bO(NlogN)),然后搜索两个向量(O(N))。