Курс по структурам данных: https://stepik.org/a/134212
Мы продолжаем знакомство с очередями. На
этом занятии разберем работу контейнера (класс) deque из стандартной
библиотеки шаблонов STL языка С++. Напомню, что этот контейтер реализует
функционал двухсторонней очереди, то есть, возможность добавления и удаления
элементов и в конец и в начало.
Однако в С++
очередь имеет особую реализацию в виде гибрида двусвязного списка и
динамических массивов:
Здесь значения
элементов 0, 1, 2, … совпадают с их индексами в этой структуре данных. То есть,
в нашем примере, первые три значения 0, 1, 2 хранятся в первом массиве (первого
элемента двусвязного списка), следующие три значения – во втором массиве (и
втором элементе списка) и так далее.
Для чего
понадобилось определять такую более сложную структуру для хранения данных в
очереди? Дело в том, что в пределах одного массива мы достаточно быстро можем
обратиться к произвольному элементу – за время O(1). Значит,
если нам нужно получить значение k-го элемента, то вначале определяем
элемент списка, в котором он находится:
j = k // size_array
(здесь оператор //
- целочисленное деление; size_array – максимальное
число элементов в массивах, массивы полагаем равной длины во всех объектах
двусвязного списка).
Затем в массиве j-го объекта
переходим к нужному элементу массива:
i = k % size_array
(здесь оператор %
- вычисление целого остатка от деления).
Все, в итоге,
нам нужно перебрать только первые j значений
двусвязного списка, вместо k, если бы использовался обычный список.
Это заметно ускоряет доступ к отдельным элементам очереди. И в этом главная
причина использования такой структуры данных в объекте deque языка С++.
Но есть и
недостатки в сравнении с обычными двусвязными списками. Операции добавления/удаления
граничных элементов работают несколько дольше, т.к. приходится смещать элементы
динамических массивов. Также несколько больше операций может занимать и вставка/удаление
промежуточных значений. Поэтому выбор, что использовать в качестве хранилища
данных двусторонней очереди, делает сам разработчик алгоритма, опираясь на
собственные соображения. Я здесь лишь констатирую факт, что в С++ применяется
гибридная схема двусвязного списка с динамическими массивами.
Использование класса deque в С++
Для использования
в программе класса deque вначале нужно подключить заголовок с
таким же названием:
После этого в
пространстве имен std появится класс deque:
int main()
{
std::deque<int> dq;
return 0;
}
Чтобы все время
не писать std:: подключим
пространство имен std в функции main():
Итак, команда
создает пустую
очередь (без элементов) с типом данных в элементах int.
Если нужно
инициализировать очередь некоторыми начальными значениями, то это можно сделать
так:
deque<int> dq = { 1, 2, 3 };
или так:
deque<int> dq { 1, 2, 3 };
Давайте теперь
отобразим элементы списка в консоли. Для этого я воспользуюсь циклом for следующим
образом:
for (unsigned int i = 0; i < dq.size(); ++i)
cout << dq[i] << " ";
cout << endl;
Здесь метод size() возвращает
общее число элементов в списке. А обращение к значению отдельного элемента
осуществляется по индексу, используя оператор квадратные скобки. Аналогичный
результат можно получить с помощью метода at():
for (unsigned int i = 0; i < dq.size(); ++i)
cout << dq.at(i) << " ";
Однако, обратите
внимание, доступ к отдельным элементам объекта deque требует O(n) операций, где n – число объектов
в двусвязном списке. Поэтому, лучше выводить последовательные значения,
используя механизм итераторов, следующим образом:
for (auto it = dq.cbegin(); it != dq.cend(); ++it)
cout << *it << " ";
Такая
конструкция будет работать быстрее и выигрыш становится тем больше, чем больше
элементов в списке.
С методами cbegin(), cend(), а также begin() и end() мы с вами уже
знакомы, когда рассматривали двусвязные списки. Поэтому повторяться здесь не
буду.
Далее, для
получения значения первого или последнего элементов используются методы front() и back():
cout << dq.front() << endl;
cout << dq.back() << endl;
Для добавления
новых элементов в начало или конец списка – методы push_front() и push_back():
dq.push_front(0);
dq.push_back(10);
Удаление первого
или последнего элемента осуществляется с помощью методов pop_front() и pop_back():
dq.pop_front();
dq.pop_back();
Обратите
внимание, эти методы не возвращают значений удаляемых элементов. За это
отвечают ранее рассмотренные методы front() и back().
Также объект deque поддерживает
операции вставки и удаления промежуточных элементов. Они реализуются методами insert() и erase() и работают по
тому же принципу, что и в двусвязных списках:
auto pos_it = dq.cbegin();
int n = 1;
for (int k = 0; k < n; ++pos_it, ++k);
dq.insert(pos_it, 100);
И метод erase():
auto pos_it = dq.cbegin();
dq.erase(++pos_it);
Я подробно на
них не останавливаюсь, т.к. мы уже их рассматривали в двусвязных списках.
И последний
метод, что мы рассмотрим на этом занятии – это clear() для очистки
(удаления) всех элементов очереди:
Заключение
Вот основные
операции при работе с элементами объекта deque языка С++. Конечно,
мы рассмотрели не все доступные операции, но с ними можно подробно
ознакомиться, например, на ресурсе:
https://en.cppreference.com/w/cpp/container/deque
А на данный
момент вы должны себе представлять, как работают следующие методы класса deque:
Метод
|
Описание
|
Сложность
|
size()
|
Возвращает
число элементов в очереди
|
O(1)
|
at() или []
|
Доступ
к произвольному элементу очереди
|
O(n)
|
begin(), cbegin()
|
Возвращает
итератор на первый элемент очереди
|
O(1)
|
end(), cend()
|
Возвращает
итератор на последний элемент очереди
|
O(1)
|
front()
|
Получение
значения первого элемента
|
O(1)
|
back()
|
Получение
значения последнего элемента
|
O(1)
|
push_back()
|
Вставка
нового элемента в конец очереди
|
O(1)
|
push_front()
|
Вставка
нового элемента в начало очереди
|
O(1)
|
insert()
|
Вставка
нового элемента в произвольную позицию
|
O(n)
|
clear()
|
Очистка очереди
|
-
|
pop_front()
|
Удаление
первого элемента из очереди
|
O(1)
|
pop_back()
|
Удаление
последнего элемента из очереди
|
O(1)
|
erase()
|
Удаление
произвольного элемента очереди
|
O(n)
|
Курс по структурам данных: https://stepik.org/a/134212