c++ 如何有效地比较两个std::vector是否相等,而忽略元素的顺序?

j2datikz  于 12个月前  发布在  其他
关注(0)|答案(7)|浏览(288)

我需要一个向量比较函数的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;
}

字符串
这个功能被大量使用,我正在考虑可能的方法来优化它。你能给予一些建议吗?

e0bqpujr

e0bqpujr1#

它只是意识到这段代码只做了一种“集合等价性”检查(现在我明白你真的这么说了,我是个多么糟糕的读者!)。

#include <algorithm> // for std::sort

template <class T>
bool compareVectors(vector<T> a, vector<T> b)
{
    // alternatively, assert(a.size() == b.size());
    // (if you expect the vectors to be the same length)
    if (a.size() != b.size())
    {   // this test saves a lot of time and means we don't need to sort
        return false; 
    }
    // or, in C++20, std::ranges::sort(a);
    std::sort(a.begin(), a.end());
    std::sort(b.begin(), b.end());
    return (a == b);
}

字符串
从技术上讲,这种方法的复杂度是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搜索有时比手动搜索更好。(例如,如果数据是排序的,编译器知道它,这可以是一个二分搜索)。

zqry0prt

zqry0prt2#

这样的话,时间复杂度为O(n),时间复杂度为O(n)。
计算:

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(加密函数也可以,但通常会慢一些)。
(This即使没有散列也可以工作,但是当值具有相对小的范围时,它的鲁棒性会差得多)。
一个128位的哈希函数对于许多实际应用来说已经足够了。

brccelvz

brccelvz3#

最受欢迎的解决方案涉及对两个输入向量进行排序。对向量进行排序并不是测试相等性的严格必要条件,如果输入向量是常数,则需要进行复制。
另一种方法是构建一个关联容器来计算每个vector中的元素。也可以在parr中减少两个vector。在非常大的vector的情况下,这可以给予很好的速度。

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 (std::size_t count = 0; count <  vec1.size(); ++count) {
        count_set[vec1[count]]++;
        count_set[vec2[count]]--;
    }

    // If everything balances out, we should have zero everywhere
    return std::all_of(count_set.begin(), count_set.end(), [](const auto& p) {
        return p.second == 0;
    });
}

字符串
这种方式取决于你的哈希函数的性能。我们可能会得到两个向量长度的线性复杂度(相对于排序的O(n log n))。
我在ubuntu 13.10,vmware core i7 gen 3上对这种比较两个向量到基于排序的比较的方式进行了基准测试:

Comparing 200 vectors of 500 elements by counting takes 0.184113 seconds
Comparing 200 vectors of 500 elements by sorting takes 0.276409 seconds
Comparing 200 vectors of 1000 elements by counting takes 0.359848 seconds
Comparing 200 vectors of 1000 elements by sorting takes 0.559436 seconds
Comparing 200 vectors of 5000 elements by counting takes 1.78584 seconds
Comparing 200 vectors of 5000 elements by sorting takes 2.97983 seconds

zte4gxcn

zte4gxcn4#

正如其他人建议的那样,预先对向量进行排序将提高性能。
作为一个额外的优化,你可以把向量堆出来进行比较(复杂度为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;
}

字符串

kuuvgm7e

kuuvgm7e5#

如果你在相同的向量上使用这个函数很多次,最好保留排序的副本以供比较。
在理论上,如果每个向量只比较一次,那么对向量进行排序和比较排序的向量可能会更好,(排序是O(n*log(n)),比较排序的向量O(n),而你的函数是O(n^2)。但是我认为如果你不经常比较相同的向量,那么为排序的向量分配内存所花费的时间将使任何理论收益相形见绌。
与所有优化一样,分析是确保的唯一方法,我会尝试一些std::sort/std::equal组合。

btxsgosb

btxsgosb6#

就像Stefan说的,你需要排序来获得更好的复杂性。然后你可以使用==运算符(tnx用于注解中的校正- ste equal也可以工作,但它更适合比较范围而不是整个容器)
如果这还不够快,那就麻烦微优化了。
向量是否保证大小相同?如果不是,请在开始时进行检查。

yv5phkfx

yv5phkfx7#

另一个可能的解决方案(只有当所有元素都是唯一的时才可行),这应该会在一定程度上改善@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))。

相关问题