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。

谢谢

4

7 回答 7

14

您可以在 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) 是任何非加密哈希函数- 例如 MurmurHash。(加密功能也可以,但通常会更慢)。

(即使没有散列,这也可以工作,但是当值的范围相对较小时,它的健壮性会低得多)。

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

于 2013-06-30T21:08:08.283 回答
14

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

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 搜索有时比手动搜索要好。(例如,如果数据被排序并且编译器知道它,这可以是二进制搜索)。

于 2013-06-30T19:56:52.903 回答
6

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

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 ;});

}

这种方式取决于您的哈希函数的性能,我们可能会在展位向量的长度上得到线性复杂度(与排序的 n*logn 相比)。注意代码可能有一些错误,确实有时间检查它......

我在 ubuntu 13.10,vmware core i7 gen 3 上对这种比较两个向量的方式进行基准比较以进行基于排序的比较:

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

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

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

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

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

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

于 2013-07-01T01:04:44.627 回答
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;
}
于 2013-07-01T13:37:11.283 回答
0

如果您在相同的向量上大量使用此函数,则最好保留已排序的副本以进行比较。

从理论上讲,如果每个向量只比较一次,那么对向量进行排序并比较排序的向量可能会更好(排序是 O(n*log(n)),比较排序的向量 O(n),而你的函数是 O( n^2). 但是我认为,如果您不经常比较相同的向量,那么为排序向量分配内存所花费的时间将使任何理论收益相形见绌。

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

于 2013-06-30T20:14:09.283 回答
0

就像 stefan 说的那样,你需要排序以获得更好的复杂性。然后您可以使用 == 运算符(tnx 用于在评论中进行更正 - ste equal 也可以,但它更适合比较范围而不是整个容器)

如果那还不够快,那就麻烦微优化。

向量是否也保证大小相同?如果不把那个检查放在开头。

于 2013-06-30T22:06:02.740 回答
-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)操作执行,而不是排序b( O(NlogN)) 然后搜索两个向量 ( O(N))。

于 2016-04-19T09:40:17.517 回答