Курс по структурам данных: https://stepik.org/a/134212
На этом занятии мы рассмотрим уже готовую
реализацию двусвязного списка из стандартной библиотеки шаблонов (STL). Чтобы
воспользоваться этой коллекцией данных, вначале нужно подключить модуль list:
А, затем, в
функции main() определить
объект класса list, например, так:
int main()
{
using namespace std;
list<int> lst;
return 0;
}
Все, у нас в
программе имеется объект lst, который представляет собой двусвязный
список. Обратите внимание, в угловых скобках прописан тип int – тот тип
данных, который предполагается хранить в элементах двусвязного списка. Вместо
типа int можно прописать
любой другой, включая собственные определения классов и структур. То есть, мы
здесь имеем универсальную реализацию списка, который может работать с любыми
типами данных. Но для простоты пусть будет тип int.
Далее, если
прописать lst и поставить
точку, то интегрированная среда покажет набор методов, поддерживаемых данным
классом. На этом занятии мы рассмотрим наиболее значимые из них. Начнем со
способа инициализации списка.
При создании
двусвязного списка командой:
создается пустой
список (без элементов). Мы в этом можем легко убедиться, если вызовем метод size(), который
возвращает общее число элементов:
cout << lst.size() << endl;
Если же нам
нужно прописать в список некоторый набор начальных значений, то это можно
сделать так:
list<int> lst = {1, 5, 3, 2};
или так (без
разницы):
list<int> lst {1, 5, 3, 2};
В обоих случаях
мы получим список из четырех элементов со значениями 1, 5, 3, 2. Давайте
выведем их в консоль:
for (auto it = lst.cbegin(); it != lst.cend(); it++)
cout << *it << " ";
cout << endl;
Смотрите, чтобы
перебрать элементы связного списка используется механизм итераторов, который в
С++ нужно прописывать явно. Вначале с помощь метода cbegin() получаем константный
итератор, установленный в самое начало списка. Затем, делаем цикл до тех пор,
пока адрес итератора не равен конечному адресу. А оператор it++ позволяет
сместить итератор на следующий элемент списка. В самом цикле с помощью операции
разыменования указателя *it получаем значение текущего элемента
списка и выводим его в консоль. Фактически, с помощью итератора мы
последовательно переходим от одного элемента к другому, используя ссылки next и prev, которые есть у
каждого объекта двусвязного списка.
Обратите
внимание, у двусвязного списка list имеются методы:
- cbegin() и cend() – для работы
с константным итератором;
- begin() и end() – для работы
с обычным итератором.
Отличие между
этими методами заключается в том, что через константный итератор нельзя менять
значения списка (только читать), а через обычный – можно. Во всем остальном они
работают одинаково.
Далее, если нам
нужно просто получить значения первого или последнего элементов, то для этого
существуют методы front() и back():
cout << lst.front() << endl;
cout << lst.back() << endl;
Видим значения 1
и 2. Эти методы бывают весьма полезны, например, при реализации очередей, о
которых мы будем говорить на следующем занятии.
Следующий часто
используемый метод empty(), который возвращает булево значение true – если список
пуст, и false в противном
случае. Например, методы front() и back() будут
приводить к ошибке, если их вызвать для пустого списка. Поэтому правильнее было
бы обращаться к ним с проверкой:
if (!lst.empty()) {
cout << lst.front() << endl;
cout << lst.back() << endl;
}
Добавление элементов в список
Следующий набор
методов, используемый для добавления элементов в двусвязный список. Методы push_back() и push_front() используются
для добавления элементов в конец и начало списка. Например:
list<int> lst;
lst.push_back(1);
lst.push_front(4);
lst.push_front(-4);
lst.push_back(-1);
Увидим значения:
-4, 4, 1, -1.
Если нам
требуется вставить какое-либо значение в произвольную позицию, то для этого используется
метод insert(). Этот метод
использует итератор для указания позиции вставки. Например, если определить
итератор с помощью команды:
auto pos_it = lst.cbegin();
то получим
указатель на первую позицию, куда и будет вставлен элемент:
Если нужно
вставить во вторую позицию, то это можно сделать так:
auto pos_it = lst.cbegin();
pos_it++;
lst.insert(pos_it, 100);
или в более
краткой записи:
auto pos_it = lst.cbegin();
lst.insert(++pos_it, 100);
Обратите
внимание, что оператор ++ должен быть записан перед итератором pos_it. Если его
прописать после, то сначала будет выполнен метод insert() и только
потом изменится итератор на единицу.
Если нужно
вставить элемент в произвольную k-ю позицию, то сначала итератор
устанавливается в эту позицию, например, с помощью цикла:
auto pos_it = lst.cbegin();
int n = 3;
for (int k = 0; k < n; ++pos_it, ++k)
;
lst.insert(pos_it, 100);
а, затем,
выполняется команда insert(). Поэтому операция вставки, в общем
случае, имеет сложность O(n), где n – общее число
элементов в списке.
Также с помощью insert() можно
относительно просто вставить сразу несколько одинаковых значений, например,
так:
lst.insert(pos_it, 5, 3);
Выполняется
вставка пяти троек, начиная с позиции pos_it.
Удаление элементов из списка
Последняя группа
методов связана с удалением элементов из списка. Если нам нужно очистить список
(удалить все элементы), то это делается с помощью метода clear():
Для удаления
первого или последнего элемента – методы pop_front() и pop_back():
lst.pop_front();
lst.pop_back();
Наконец,
удаление произвольного элемента выполняется методом erase(). Здесь также,
как и в методе insert(), необходимо определить итератор на
удаляемый элемент, например, так:
auto pos_it = lst.cbegin();
lst.erase(++pos_it);
В результате
будет удален второй элемент. Либо можно задать границы удаляемых элементов
через итераторы:
auto start_it = lst.cbegin();
auto end_it = lst.cend();
lst.erase(++start_it, --end_it);
Заключение
Вот основные
операции при работе с двусвязными списками объекта list языка С++.
Конечно, мы рассмотрели не все доступные операции, но с ними можно подробно
ознакомиться, например, на ресурсе:
https://en.cppreference.com/w/cpp/container/list
А на данный
момент вы должны себе представлять, как работают следующие методы двухсвязного
списка класса list:
Метод
|
Описание
|
Сложность
|
size()
|
Возвращает
число элементов в списке
|
O(1)
|
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