【问题标题】:How to compute the size of an intersection of two STL sets in C++如何在 C++ 中计算两个 STL 集的交集的大小
【发布时间】:2015-09-17 21:34:17
【问题描述】:

我有两个集合(std::set from <set>),我想知道交叉点的大小。我可以使用来自<algorithm> 的 std::set_intersection,但我必须为其提供一个输出迭代器以将交集复制到其他容器中。

一个简单的方法是

  set<int> s1{1,2,3,4,5};
  set<int> s2{4,5,6,7,8,9,0,1};

  vector<int> v;

  set_intersection(
      s1.begin(), s1.end(), s2.begin(), s2.end(),
      inserter(v, v.begin()));

之后 v.size() 给出交点的大小。但是,即使我们不对其进行任何操作,也必须存储交叉点。

为了避免这种情况,我尝试实现一个虚拟输出迭代器类,它只计数,但不赋值:

template<typename T>
class CountingOutputIterator {
 private:
  int* counter_;
  T dummy_;
 public:
  explicit CountingOutputIterator(int* counter) :counter_(counter) {}
  T& operator*() {return dummy_;}
  CountingOutputIterator& operator++() { // ++t
    (*counter_)++;
    return *this;
  }
  CountingOutputIterator operator++(int) { // t++
    CountingOutputIterator ret(*this);
    (*counter_)++;
    return ret;
  }
  bool operator==(const CountingOutputIterator& c) {
    return counter_ == c.counter_; // same pointer
  }
  bool operator!=(const CountingOutputIterator& c) {
    return !operator==(c);
  }
};

我们可以做的事情

  set<int> s1{1,2,3,4,5};
  set<int> s2{4,5,6,7,8,9,0,1};

  int counter = 0;
  CountingOutputIterator<int> counter_it(&counter);
  set_intersection(
      s1.begin(), s1.end(), s2.begin(), s2.end(), counter_it);

之后,计数器保存交叉点的大小。

然而,这是更多的代码。我的问题是:

1)是否有标准(库)方式或标准技巧来获取交叉口的大小而不存储整个交叉口? 2)不管是否存在,使用自定义虚拟迭代器的方法是一个好的方法吗?

【问题讨论】:

  • 仅仅识别常见元素的数量似乎过于复杂。为什么不直接使用循环?
  • 很奇怪,当你从不真正使用交叉路口时,知道大小有什么意义?你考虑清楚了吗? Read this.
  • 与自定义迭代器相比,创建一个具有 insert() 成员的自定义“容器”会更简单,并与 insert_iterator 一起使用。
  • @HansPassant 为什么你觉得这很奇怪?我能想到很多情况。本质上它是重叠的区域。
  • @JonathanWakely 谢谢,我会考虑的

标签: c++ algorithm stl


【解决方案1】:

编写一个循环遍历这两个集合以寻找匹配元素并不难,或者你可以这样做,这比自定义迭代器简单得多:

struct Counter
{
  struct value_type { template<typename T> value_type(const T&) { } };
  void push_back(const value_type&) { ++count; }
  size_t count = 0;
};

template<typename T1, typename T2>
size_t intersection_size(const T1& s1, const T2& s2)
{
  Counter c;
  set_intersection(s1.begin(), s1.end(), s2.begin(), s2.end(), std::back_inserter(c));
  return c.count;
}

【讨论】:

  • 为了我编译它 (g++ 4.8.4) 我必须将 Counter 作为结构体作为 T 的模板,并在其中嵌套 value_type 的 typedef:使用 value_type = T;
  • 啊,是的,好点子。我已经用一个替代修复更新了答案,这仍然意味着 Counter 不需要是模板:定义一个可以从任何东西构造的 value_type
【解决方案2】:

你可以这样做:

auto common = count_if(begin(s1), end(s1), [&](const auto& x){ return s2.find(x) != end(s2); });

它的效率不是最佳,但对于大多数用途来说应该足够快。

【讨论】:

  • 不应该与 s2.end() 进行比较吗?
  • @Cheersandhth.-Alf 是的,哎呀。固定。
  • 哪个更复杂——O(n log n)O(n)
  • 谢谢。但是,这具有更高的计算复杂度,即 $n\log n$ 与 $m + \log n$,其中 $n$ 是集合的大小,$m$ 是它们的交集。
  • 如果s2远大于s1,复杂度n1*log(n2)可以小于n1+n2。
【解决方案3】:

您可以稍微简化方法的使用:

struct counting_iterator
{
    size_t count;
    counting_iterator& operator++() { ++count; return *this; }

    struct black_hole { void operator=(T) {} };
    black_hole operator*() { return black_hole(); }

    // other iterator stuff may be needed
};

size_t count = set_intersection(
  s1.begin(), s1.end(), s2.begin(), s2.end(), counting_iterator()).count;

【讨论】:

  • 好点,谢谢。我是这样开始的,但后来我认为我无法访问传递给 set_intersection 的迭代器。但是当然只是简单地返回一个副本。
【解决方案4】:

编写一个执行此操作的函数并不难。 This 展示了set_intersection 是如何完成的[虽然实际的实现当然可能略有不同]

所以我们可以只使用该代码,并对其进行一些修改:

template <class InputIterator1, class InputIterator2>
  size_t set_intersection_size (InputIterator1 first1, InputIterator1 last1,
                                InputIterator2 first2, InputIterator2 last2)
{
  size_t result = 0;
  while (first1!=last1 && first2!=last2)
  {
    if (*first1<*first2) ++first1;
    else if (*first2<*first1) ++first2;
    else {
      result++;
      ++first1; ++first2;
    }
  }
  return result;
}

尽管根据我的经验,当您想知道有多少人在交叉路口时,您通常迟早也想知道哪些元素。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-26
    • 2011-09-10
    • 2018-03-20
    • 1970-01-01
    • 2020-07-14
    • 2020-12-04
    相关资源
    最近更新 更多