Для работы с коллекциями на C++ имеется немало алгоритмов, среди которых особенно выделяется set_intersection.

Умение эффективно находить общие элементы двух множеств пригодится в запросах к базе данных, расчетах для анализа данных или сложных задачах программирования. Рассмотрим нюансы set_intersection и способы совершенствования кода и проектов на C++.

set_intersection

set_intersection  —  это алгоритм из библиотеки стандартных шаблонов C++, которым вычисляется пересечение двух отсортированных диапазонов. Все элементы, которые имеются в обоих входных диапазонах, им выявляются и помещаются в целевой диапазон.

set_intersection(InputIt1 first1, InputIt1 last1,
InputIt2 first2, InputIt2 last2,
OutputIt d_first);

С помощью first1 и last1 определяется первый входной диапазон, с помощью first2 и last2  —  второй. d_first является началом целевого диапазона.

Принцип работы set_intersection

Предполагается, что оба входных диапазона отсортированы в порядке возрастания. Алгоритмом set_intersection сравниваются элементы обоих диапазонов при одновременном перемещении по ним.

Совпадающие элементы копируются в выходной диапазон. Вот простой пример:

#include <algorithm>
#include <iostream>
#include <vector>

int main() {
std::vector<int> v1 = {1, 2, 3, 4, 5};
std::vector<int> v2 = {3, 4, 5, 6, 7};
std::vector<int> result;

std::set_intersection(v1.begin(), v1.end(),
v2.begin(), v2.end(),
std::back_inserter(result));

for (int n : result) {
std::cout << n << ' ';
}
std::cout << '\n';

return 0;
}

Этим кодом выводится: 3 4 5.

Эффективность и сложность

Ключевое преимущество set_intersection  —  эффективность. Линейная временна́я сложность алгоритма равна O(n + m), где n и m  —  размеры входных диапазонов. То есть большие наборы данных им обрабатываются быстро, поэтому алгоритм пригодится там, где важна производительность.

Реальное применение: рекомендации в друзья

Вот как при помощи set_intersection в соцсети предлагаются общие друзья:

#include <algorithm>
#include <iostream>
#include <string>
#include <vector>

struct User {
std::string name;
std::vector<std::string> friends;
};

std::vector<std::string> find_mutual_friends(const User& user1, const User& user2) {
std::vector<std::string> mutual_friends;
std::set_intersection(user1.friends.begin(), user1.friends.end(),
user2.friends.begin(), user2.friends.end(),
std::back_inserter(mutual_friends));
return mutual_friends;
}

int main() {
User alice {"Alice", {"Bob", "Charlie", "David"}};
User bob {"Bob", {"Alice", "Charlie", "Eve"}};

std::vector<std::string> mutual = find_mutual_friends(alice, bob);

std::cout << "Mutual friends of Alice and Bob: ";
for (const auto& friend_name : mutual) {
std::cout << friend_name << " ";
}
std::cout << '\n';

return 0;
}

Этим кодом выводится: Mutual friends of Alice and Bob: Charlie, то есть общие друзья Алисы и Боба: Чарли.

set_intersection с пользовательскими объектами

set_intersection не ограничивается встроенными типами. Он применяется с пользовательскими объектами, если определить способ их сравнения. Как пример возьмем пользовательскую структуру Book:

#include <algorithm>
#include <iostream>
#include <string>
#include <vector>

struct Book {
std::string title;
std::string author;

bool operator<(const Book& other) const {
return title < other.title;
}
};

int main() {
std::vector<Book> library1 = {
{"1984", "George Orwell"},
{"To Kill a Mockingbird", "Harper Lee"},
{"Pride and Prejudice", "Jane Austen"}
};

std::vector<Book> library2 = {
{"1984", "George Orwell"},
{"The Great Gatsby", "F. Scott Fitzgerald"},
{"Pride and Prejudice", "Jane Austen"}
};

std::vector<Book> common_books;

std::set_intersection(library1.begin(), library1.end(),
library2.begin(), library2.end(),
std::back_inserter(common_books));

std::cout << "Books in both libraries:\n";
for (const auto& book : common_books) {
std::cout << book.title << " by " << book.author << '\n';
}

return 0;
}

Этим кодом выводится:

```
Books in both libraries:
1984 by George Orwell
Pride and Prejudice by Jane Austen
```

Работа с дублями

По умолчанию в set_intersection включаются все дубли обоих диапазонов. Чтобы удалить дубли, set_intersection используется вместе с std::unique:

#include <algorithm>
#include <iostream>
#include <vector>

int main() {
std::vector<int> v1 = {1, 2, 2, 3, 3, 4, 5};
std::vector<int> v2 = {2, 2, 3, 4, 4, 5, 6};
std::vector<int> result;

std::set_intersection(v1.begin(), v1.end(),
v2.begin(), v2.end(),
std::back_inserter(result));

auto last = std::unique(result.begin(), result.end());
result.erase(last, result.end());

for (int n : result) {
std::cout << n << ' ';
}
std::cout << '\n';

return 0;
}

Этим кодом выводится: 2 3 4 5.

set_intersection и множества

set_intersection хорош для любых отсортированных диапазонов, но особенно для контейнеров std::set  —  здесь элементы автоматически сохраняются в отсортированном порядке:

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

int main() {
std::set<int> set1 = {1, 2, 3, 4, 5};
std::set<int> set2 = {3, 4, 5, 6, 7};
std::vector<int> result;

std::set_intersection(set1.begin(), set1.end(),
set2.begin(), set2.end(),
std::back_inserter(result));

for (int n : result) {
std::cout << n << ' ';
}
std::cout << '\n';

return 0;
}

Этим кодом выводится: 3 4 5.

Рекомендации по производительности

Чтобы задействовать весь потенциал set_intersection, не забывайте о:

  1. Размерах входных диапазонов: производительность алгоритма определяется размерами обоих входных диапазонов. Если один диапазон значительно меньше другого, используйте его как первый диапазон  —  для потенциального сокращения сравнений.
  2. Выходной контейнер: использовать std::back_inserter с вектором удобно, но это чревато многократными перераспределениями. Если максимально возможный размер пересечения известен, резервируйте место заранее:
std::vector<int> result;
result.reserve(std::min(v1.size(), v2.size()));
std::set_intersection(v1.begin(), v1.end(),
v2.begin(), v2.end(),
std::back_inserter(result));

3. Функция сравнения: для пользовательских объектов убедитесь, что функция сравнения эффективна, в процессе пересечения она будет вызываться многократно.

Заключение

set_intersection  —  это ценное дополнение к инструментарию любого программиста C++. Благодаря эффективному нахождению общих элементов отсортированных диапазонов set_intersection применяется в различных сценариях  —  от обработки данных до проектирования алгоритмов.

Эффективно используя set_intersection, вы сможете писать более эффективный и выразительный код, четко и точно решая сложные задачи.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи ryan: Set Intersection in C++: A Practical Guide

Предыдущая статьяСвязывание файла JavaScript с HTML: полное руководство
Следующая статья10 инструментов ИИ для SaaS-стартапов 2025