Set и multiset в C++: что это такое и как с ними работать
Сегодня мы разберем еще два контейнера STL, которые очень похожи между собой — set и multiset.
- Что такое set и multiset
- Как создать set и multiset
- Итераторы на multiset и set
- Методы
- Простое приложение
- Плюсы и минусы
Что такое set и multiset
set — это контейнер, который автоматически сортирует добавляемые элементы в порядке возрастания. Но при добавлении одинаковых значений, set будет хранить только один его экземпляр. По другому его еще называют множеством.
multiset — это контейнер, который также будет содержать элементы в отсортированном порядке при добавлении, но он хранит повторяющееся элементы, по сравнению с множеством set. Часто его называют мультимножество.
Из-за того, что set построен на списках нельзя обратиться к определенному элементу по индексу, как в массиве или векторе:
1 |
st[3] = 19; // ошибка |
Для этого придется оперировать итераторами.
Как создать set и multiset
Для использование, с самого начала нужно подключить единственную библиотеку — <set>
.
1 |
#include <set> |
Далее используем данную конструкцию:
1 2 |
set < [тип] > <имя>; multiset < [тип] > <имя> |
[тип]
— это тип данных для нашего контейнера.[имя]
— название нашего контейнера.
1 2 |
set <int> st; // пример multiset <int> mst; |
Чтобы добавить новый элемент нужно использовать функцию insert()
:
1 |
st.insert(<значение>); |
Добавление происходит за log n.
n
— это размер контейнера.
Возможно понадобиться изменить сторону сортировки в обратную (по-убыванию) для этого можно сделать с помощью greater
, вот так:
1 2 |
set < [тип], greater [тип] > [имя]; set <long long, greater <long long> > st; // пример |
- В каждом из пунктов
[тип]
должен быть одинаковый.
Также можно добавлять значения при инициализации контейнеров:
1 |
set <int> st{1,2,3,4,5}; |
Но это можно делать только в C++ 11 и выше.
Итераторы для multiset и set
Вот как выглядит создание итератора:
1 2 |
set < [тип] > :: iterator it; multiset < [тип] > :: iterator it2; |
Чтобы итератор работал на определенный set или multiset, [тип]
должен совпадать с типом контейнера.
Для использование итераторов подключение посторонних библиотек будет лишним. Итераторы уже включены в библиотеку —
<iostream>
. А если будут нужны, то они находятся в библиотеке —<iterator>
.
При создании итератора для set и multiset нужно знать, что мы сможем только наблюдать за их значением. Это значит, что мы не сможем изменять значения существующих элементов.
Подробнее об итераторах и как улучшить жизнь применяя их, здесь.
Вот что можно делать с итератором на множество и мультимножество:
- Использование операции разыменования —
*
. - Сравнивать итераторы на равенство (
==
) и неравенство (!=
). - Увеличивать или уменьшать итератор, с помощью инкремента (
++
) и декремента (--
).
Но также у него есть свои ограничения:
- Нельзя изменять итератор используя операции сложения (
+
), умножение (*
), деления (/
). - Также нельзя сравнивать итераторы знаками больше (
>
) и меньше (<
).
Чтобы обратится к значению элемента, нужно воспользоваться операцией разыменование *
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <iostream> #include <set> using namespace std; int main() { set <int> st; for (int i = 0; i < 5; i++) { st.insert(i + 1); } set <int> :: iterator it = st.begin(); cout << *it; // 1 it++; cout << *it; // 2 return 0; } |
Также можно сравнивать итераторы на равенство (==
) и неравенство (!=
). Мы часто будем пользоваться данной возможностью. Например, чтобы вывести все элементы.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
#include <iostream> #include <ctime> #include <set> using namespace std; int main() { setlocale(LC_ALL, "Russian"); srand(time(NULL)); multiset <int> mst; cout << "Добавление случайных значений: " << endl; for (int i = 0; i < 10; i++) { int random = rand() % 10 + 1; mst.insert(random); cout << i + 1 << ") " << random << endl; } multiset <int> :: iterator it = mst.begin(); cout << "Отсортированный вариант: " << endl; for (int i = 1; it != mst.end(); i++, it++) { cout << *it << " "; } system("pause"); return 0; } |
- В строке 11: создали multiset —
mst
. - В строках 14 — 18: заполняем мультимножество случайными значениями.
- В строке 20: инициализируем итератор на
mst
. - В строках 22 — 25: выводим значения
mst
пользователю. - В строке 23: мы сравнивали итераторы на неравенство (
!=
), чтобы не выйти за диапазон значений контейнера и в последствии чтобы этот цикл не выполнялся бесконечно.
Вот пример данной программы:
Методы для set и multiset
Давайте рассмотрим функции, которые можно использовать, как с set, так и с multiset.
copy
Сначала подключите библиотеку — <iterator>
, она понадобится нам для использования — ostream_iterator
.
Эта функция используется для различных операций, о которых вы можете посмотреть здесь. Одна из таких операций вывод элементов контейнера. Снизу находится конструкция вызова данной функции:
1 |
copy ([начала], [конец], ostream_iterator< [тип] >(cout, [отступ])); |
[начала]
— итератор указывающий на первый элемент, который мы хотим вывести. Так мы можем начать выводить со второго или третьего элемента.[конец]
— итератор указывающий на ячейку, до которой будет производиться вывод.[тип]
— тип данных выводимых элементов (тип контейнера).<отступ>
— здесь можно указать, что выводить между элементами. Обычно указывают пробел(cout, " ")
.
Вот пример использования copy для set:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include <iostream> #include <iterator> // ostream_iterator #include <set> // set #include <ctime> // time using namespace std; int main() { setlocale(LC_ALL, "Russian"); set <int> st; cout << "Введите 5 чисел: " << endl; for (int i = 0; i < 5; i++) { cout << i + 1 << ") "; int dig; cin >> dig; st.insert(dig); } cout << "Содержимое set: "; copy(st.begin(), st.end(), ostream_iterator<int>(cout, " ")); system("pause"); return 0; } |
- В строках 14 — 18: считываем значения пользователя.
- В строке 22: выводим все элементы контейнера
st
.
Вот как будет выглядеть выполненная программа:
erase
С помощь нее вы сможете:
- Удалить какой-то конкретный элемент —
<имя>.erase([итератор])
. - Удалить все элементы с данным значением —
<название>.erase([ключ])
. Это отличная функция для мультимножества. А для set мы как раз удалим конкретный элемент, это удобнее чем:- Найти итератор на определенное значение через функции find или lower_bound
- А потом его удалить
- Либо удалить определенный диапазон значений.
1<имя>.erase([начала], [конец]);[начала]
— с какого элемента будет происходить удаление (включительно).[конец]
— до какого элемента будет продолжаться (не включительно).
Пример:
1 |
mst.erase(3); // удалим все элементы со значением 3 |
lower_bound
Часто приходится проверить есть ли элемент, который равен определенному ключу, либо больше его. Это функция находит элемент который больше или равен ключ (>= key
).
1 |
<имя>.lower_bound(key); |
key
— это наш ключ. Значение, с которым будут сравниваться элементы.
upper_bound
Этот метод идентичен функции lower_bound:
- У него такая же конструкция вызова
- При поиске также используется бинарный поиск
Но искать она будет элемент, который именно больше ключа — > key
.
find
Сравнительно часто может пригодиться нахождение итератора на элемент, либо проверить существует ли он вообще.
1 |
find([ключ]); |
Эта функция может возвратить:
- Местонахождение
[ключа]
— итератор. - Значение на конец контейнера ( оно будет равняться вызову
st.end()
), если ячейки с таким значением не существует.
Чтобы проверить есть ли данный элемент, можно воспользоваться данным кодом:
1 2 3 |
if ([контейнер].find([ключ]) == [контейнер].end()) { cout << "Такого значение не существует"; } |
Если условие верно, то такого элемента нет (он равен концу контейнера). Вот пример:
1 2 3 4 5 6 7 8 9 10 11 |
set <int> numbers; ... считывание значений для numbers ... for (int i = 1; i <= 30; i++) { dig = i; if (numbers.find(dig) == numbers.end()) { cout << "Такого значение " << dig << " не существует" << endl; } } |
Выше мы проверяем, есть ли значения меньшие 31
и большие 0
в контейнере.
- Если нет, то выводим это значение.
count
Возвращает количество элементов с заданным значением.
1 |
st.count([ключ]); |
Для обычного set эту функцию можно использовать, чтобы проверить существует ли заданное значение. А вот для multiset данная функция может пригодиться гораздо лучше из-за возможных повторяющих значений.
equal_range
Эта функция предназначена для multiset. Допустим вы добавили несколько одинаковых чисел, и вам требуется узнать: от куда этот диапазон начинается и до куда заканчивается.
1 |
<имя>.equal_range([ключ]); |
<имя>
— название нашего мультимножества.
Возвращаемым значением этой функции будет — pair.
- В первой ячейке будет находится значение итератора, который указывает на начала этого диапазона. Если мы вызовем
lower_bound([ключ])
(ключ при этом одинаковый), то значение итераторов будут одинаковые. Найдет число>= [ключ]
. - Во второй ячейке будет находится значение итератора, если бы мы вызвали
upper_bound([ключ])
с тем же ключом. Найдет число> [ключ]
.
Вот пример использования этой функции:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
multiset <int> chisla; multiset <int> :: iterator pair <multiset <int> :: iterator, multiset <int> :: iterator> it; ... инициализация ... // 1 1 1 2 2 3 4 8 9 10 end() it = chisla.equal_range(1); ^ ^ it = chisla.equal_range(4); ^ ^ chisla.insert(4); // 1 1 1 2 2 3 4 4 8 9 10 end() it = chisla.equal_range(4); ^ ^ it = chisla.equal_range(10); ^^ ^ |
Создание простого приложения
Давайте закрепим свои навыки на создании простой игры.
Условия просты. Нужно создать интерфейс приложения, который пользователь сможет использовать для регулирования папок. В итоге у нас должна получиться программа для настройки псевдо файловой системы.
Вот какой функционал она будет иметь:
- Добавление новых элементов.
- Удаление одного или сразу всех элементов со значением
value
. - Использование операции lower_bound для поиска папок.
- Оперирование операцией upper_bound для поиска папок.
Давайте начнем! Для начала хотелось бы чтобы пользователь уже имел какую-то файловую систему — так сказать «бэграунд».
1 2 3 4 5 6 7 8 9 10 |
cout << "Введите начальное количество файлов: "; int n; cin >> n; multiset <int> files; for (int i = 0; i < n; i++) { cout << i + 1 << ") Введите индекс папки для добавления: "; int a; cin >> a; files.insert(a); } |
- В строке 4: мы создали наше мультимножество.
- В строках 6 — 10: считываем начальные индексы папок.
Далее давайте реализуем каждую операцию. Начнем с добавление нового элемента.
1 2 3 4 5 6 7 8 9 10 11 |
if (operation == "add" || operation == "+") { cout << "Введите индекс новой папки: "; int value; cin >> value; files.insert(value); cout << "Новые значения индексов: "; copy(files.begin(), files.end(), ostream_iterator(cout, " ")); cout << endl; } |
Обратите внимание, что для каждой операции мы сделали длинную и короткую форму вызова.
Вторая операция будет удаление — это уже сложнее. Потому что у нас два вида выполнения данной функции: удаление одного элемента и удаление всех элементов с заданным ключом.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
if (operation == "erase" || operation == "-") { cout << "Укажите какой именно тип операции вам подходит: "; string temp; cin >> temp; if (temp == "one" || temp == "1") { cout << "Введите значение: "; int value; cin >> value; multiset <int> :: iterator it = files.find(value); if (it == files.end()) { cout << "Такого индекса не существует!" << endl; continue; } files.erase(it); } if (temp == "all" || temp == "*") { cout << "Введите значение: "; int value; cin >> value; multiset <int> :: iterator it = files.find(value); if (!files.count(value)) { cout << "Таких индексов не существует!" << endl; continue; } files.erase(value); } cout << "Новые значения индексов: "; copy(files.begin(), files.end(), ostream_iterator(cout, " ")); // выводим cout << endl; } |
- В строке 3: считываем вид операции.
- В строках 5 — 15: выполняется операции с удалением одной ячейки.
- В строках 18 — 29: выполняется операции с удалением всех значений.
- В строках 10 и 23: реализованы условия для проверки существования данных значений в мультимножестве.
- В строке 10 это реализовано с помощью функции
find()
. Использованияfind()
необходимо в данном случае, чтобы потом удалить значение данного итератора. - В строке 23 это реализовано с помощью
count()
, потому что для удаление нам не понадобится на их итератор —erase([значение])
.
- В строке 10 это реализовано с помощью функции
Третья и четвертая операция похожие и просты. Вот реализация операции lower_bound:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
if (operation == "lower_bound" || operation == ">=") { cout << "Введите значение для поиска: "; int value; cin >> value; multiset <int> :: iterator it; it = files.lower_bound(value); // получаем итератор if (it == files.end()) { // проверяем существует ли данный элемент cout << "Элемента >= " << value << " не существует!" << endl; continue; } cout << *it << endl; } |
И последняя операция upper_bound:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
f (operation == "upper_bound" || operation == ">") { cout << "Введите значение для поиска: "; int value; cin >> value; multiset <int> :: iterator it; it = files.upper_bound(value); // получаем местоположение if (it == files.end()) { // проверяем существование cout << "Элемента > " << value << " не существует!" << endl; continue; } cout << *it << endl; } |
А вот полностью все приложение:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
#include <iostream> #include <set> #include <iterator> using namespace std; int main() { setlocale(0, ""); cout << "Введите начальное количество файлов: "; int n; cin >> n; multiset <int> files; for (int i = 0; i < n; i++) { cout << i + 1 << ") Введите индекс папки для добавления: "; int a; cin >> a; files.insert(a); } cout << endl << "Укажите количество операций: "; q; cin >> q; for (int i = 0; i < q; i++) { cout << i + 1 << ") "; string operation; cin >> operation; if (operation == "add" || operation == "+") { cout << "Введите индекс новой папки: "; int value; cin >> value; files.insert(value); cout << "Новые значения индексов: "; copy(files.begin(), files.end(), ostream_iterator(cout, " ")); cout << endl; } if (operation == "erase" || operation == "-") { cout << "Укажите какой именно тип операции вам подходит: "; string temp; cin >> temp; if (temp == "one" || temp == "1") { cout << "Введите значение: "; int value; cin >> value; multiset <int> :: iterator it = files.find(value); if (it == files.end()) { cout << "Такого индекса не существует!" << endl; continue; } files.erase(it); } if (temp == "all" || temp == "*") { cout << "Введите значение: "; int value; cin >> value; multiset <int> :: iterator it = files.find(value); if (!files.count(value)) { cout << "Таких индексов не существует!" << endl; continue; } files.erase(value); } cout << "Новые значения индексов: "; copy(files.begin(), files.end(), ostream_iterator(cout, " ")); cout << endl; } if (operation == "lower_bound" || operation == ">=") { cout << "Введите значение для поиска: "; int value; cin >> value; multiset <int> :: iterator it; it = files.lower_bound(value); if (it == files.end()) { cout << "Элемента >= " << value << " не существует!" << endl; continue; } cout << *it << endl; } if (operation == "upper_bound" || operation == ">") { cout << "Введите значение для поиска: "; int value; cin >> value; multiset <int> :: iterator it; it = files.upper_bound(value); if (it == files.end()) { cout << "Элемента > " << value << " не существует!" << endl; continue; } cout << *it << endl; } } system("pause"); return 0; } |
Вот пример выполнения данной программы:
Плюсы и минусы использования set и multiset
Плюсы:
- Быстрая сортировка элементов.
Минусы:
- Нельзя обратится к конкретной ячейке по индексу
[]
.
Если у вас мало обращений к определенным ячейкам, то использовать однозначно нужно.
Если же вам придется очень часто обращаться к произвольным ячейкам — то использовать лучше vector или массив, если это возможно.
Упражнение
Чтобы закрепить свои знания предлагаем вам создать следующую программу.
Создайте set и сделайте так чтобы пользователь смог введя определенный символ использовать отдельную операцию:
- Добавление — при вводе push и значение нового элемента.
- Удаление — при вводе delete и значение удаляемого элемента.
Если у вас появились вопросы задавайте их в комментариях. Удачи!
Добавить комментарий