← Назад к вопросам
Что такое двунаправленный список?
1.0 Junior🔥 171 комментариев
#STL контейнеры и алгоритмы#Структуры данных и алгоритмы
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Двунаправленный список (Doubly Linked List)
Двунаправленный список — это структура данных, где каждый узел содержит данные и два указателя: на предыдущий узел и на следующий узел. Это позволяет перемещаться по списку в обоих направлениях.
Структура узла
template <typename T>
struct Node {
T data;
Node* next; // Указатель на следующий узел
Node* prev; // Указатель на предыдущий узел
};
// Внешне:
// prev <- Node -> next
Схема двунаправленного списка
null <- [Data1] <-> [Data2] <-> [Data3] -> null
(prev) (next/prev) (next)
Реализация базовых операций
template <typename T>
class DoublyLinkedList {
private:
struct Node {
T data;
Node* next;
Node* prev;
Node(const T& value)
: data(value), next(nullptr), prev(nullptr) {}
};
Node* head;
Node* tail;
size_t size;
public:
DoublyLinkedList() : head(nullptr), tail(nullptr), size(0) {}
// Добавление в конец
void push_back(const T& value) {
Node* new_node = new Node(value);
if (tail == nullptr) {
head = tail = new_node;
} else {
tail->next = new_node;
new_node->prev = tail;
tail = new_node;
}
size++;
}
// Добавление в начало
void push_front(const T& value) {
Node* new_node = new Node(value);
if (head == nullptr) {
head = tail = new_node;
} else {
new_node->next = head;
head->prev = new_node;
head = new_node;
}
size++;
}
// Удаление с конца
void pop_back() {
if (tail == nullptr) return;
Node* temp = tail;
if (tail->prev) {
tail = tail->prev;
tail->next = nullptr;
} else {
head = tail = nullptr;
}
delete temp;
size--;
}
// Удаление с начала
void pop_front() {
if (head == nullptr) return;
Node* temp = head;
if (head->next) {
head = head->next;
head->prev = nullptr;
} else {
head = tail = nullptr;
}
delete temp;
size--;
}
};
Преимущества двунаправленного списка
- Двусторонняя навигация — можно идти вперёд и назад
- Вставка/удаление за O(1) — если уже знаешь позицию (нужны оба указателя)
- Реверс без переворота — можно просто менять направление движения
// Итерация вперёд
for (Node* curr = head; curr; curr = curr->next) {
process(curr->data);
}
// Итерация назад
for (Node* curr = tail; curr; curr = curr->prev) {
process(curr->data);
}
Недостатки двунаправленного списка
- Больше памяти — два указателя на узел вместо одного
- Сложнее поддерживать — нужно синхронизировать оба указателя
- Медленный поиск — O(n) для поиска элемента
- Нет случайного доступа — нельзя взять i-й элемент за O(1)
// Плохо: O(n)
T get_at(int index) {
Node* curr = (index < size / 2) ? head : tail;
int i = (index < size / 2) ? 0 : size - 1;
while (i != index) {
curr = (index < size / 2) ? curr->next : curr->prev;
i += (index < size / 2) ? 1 : -1;
}
return curr->data;
}
Сравнение: однонаправленный vs двунаправленный
| Операция | Однонаправленный | Двунаправленный |
|---|---|---|
| Вставка в начало | O(1) | O(1) |
| Удаление из начала | O(1) | O(1) |
| Удаление в конец (без tail) | O(n) | O(1) |
| Итерация назад | O(n) каждый раз | O(1) |
| Память на узел | 1 указатель | 2 указателя |
Где используется
- std::list в C++ STL — двунаправленный список
- Deque операции — нужна быстрая работа с обоих концов
- LRU Cache — нужна навигация в обоих направлениях
- Undo/Redo — нужно ходить по истории туда-сюда
#include <list>
int main() {
std::list<int> list = {1, 2, 3, 4, 5};
// Итерация вперёд
for (auto it = list.begin(); it != list.end(); ++it) {
std::cout << *it << " "; // 1 2 3 4 5
}
// Итерация назад
for (auto it = list.rbegin(); it != list.rend(); ++it) {
std::cout << *it << " "; // 5 4 3 2 1
}
}
Удаление элемента по итератору
auto it = list.begin();
++it; // На второй элемент
list.erase(it); // O(1) удаление благодаря двум указателям
Вывод: Двунаправленный список предоставляет баланс между гибкостью навигации и простотой реализации, идеален для сценариев, где нужна работа с обоих концов структуры и частые вставки/удаления в известных позициях.