В то время как объекты односвязного списка хранят ссылки только на следующие элементы, в двусвязном списке присутствуют обе связи на следующий и предыдущий элемент.
На рисунке выше синяя стрелка отображает связь на следующий элемент, а зеленая - на предыдущий. Как и в односвязном
списке, у структуры определены два указателя HEAD
и TAIL
на корневой head
и конечный tail
узлы
соответственно.
Каждый узел в двусвязном списке содержит не только значение, но и ссылку на следующий и предыдущий элемент. Программно его можно представить так:
@dataclass
class DoublyListNode:
"""
Узел двусвязного списка
"""
val: int
prev: Optional['DoublyListNode'] = None
next: Optional['DoublyListNode'] = None
Аналогично односвязному списку, в двусвязном мы также имеем:
Рассмотрим три вида вставки: вставка элемента в середину, в “голову” и в “хвост”.
Вставка в начало изображена ниже. Алгоритм вставки следующий:
cur
и устанавливаем next
у него на head
head
уже существует и список не пустой, то у head
устанавливаем prev
на cur
tail
не инициализирован (т.е. добавляемый cur
первый в списке), то устанавливаем tail
как cur
head
на cur
Вставку элемента в середину можно разделить на два шага:
cur
с узлами prev
и next
prev
и next
и дальнейшее их переназначение на cur
Операция займет O(1) вне зависимости от того какой узел следующий или предыдущий задается. В отличие от односвязного списка мы располагаем двумя связями. Следует заметить, что если происходит вставка по индексу, то изначально следует получить ссылку на правый или левый узел, а это займет O(n).
Вставка в хвост аналогична вставке в начало. Ее сложность оценивается в O(1).
Временная сложность операций:
Вставка элемента в середину - O(1). При условии, что задается узел для вставки, а не его индекс. Для вставки по индексу O(n).
Вставка элемента в “голову” - O(1).
Вставка элемента в “хвост” - O(1).
Алгоритм удаления из головы следующий:
head
и инициализируем cur
значением связи next
.cur
существует, удаляем у него значение связи prev
.cur
не существует, в списке один элемент и мы его удаляем. Поэтому обнуляем tail
.head
как cur
Рассмотрим удаление из середины.
В отличие от односвязного списка, где эта операция занимает O(n) из-за того, что нам требуется найти предыдущий
элемент для создания связи, двусвязный список имеет ссылку на prev
. Если мы хотим удалить существующий узел cur
,
мы можем просто связать его предыдущий узел prev
со следующим узлом next
. Временная сложность O(1).
Удаление из конца списка аналогично удалению из головы, поэтому имеет временную сложность O(1).
Временная сложность операций:
Удаление элемента из “головы” - O(1).
Удаление элемента из середины - O(1). При условии, что задается узел для вставки, а не его индекс. Для удаления по индексу O(n).
Удаление элемента из “хвоста” - O(1).
"""
DoublyLinkedList
"""
from dataclasses import dataclass
from typing import Optional
@dataclass
class DoublyListNode:
"""
Узел двусвязного списка
"""
val: int
prev: Optional['DoublyListNode'] = None
next: Optional['DoublyListNode'] = None
class DoublyLinkedList:
"""
Реализация двусвязного списка
"""
def __init__(self):
self.head = None
self.tail = None
self.len = 0
def get(self, index: int) -> Optional[DoublyListNode]:
""" Получение узла по индексу """
cur = self.head
idx = 0
while idx != index:
if not cur:
return None
cur = cur.next
idx += 1
return cur
def add_at_head(self, val: int) -> DoublyListNode:
""" Добавление в голову """
cur = DoublyListNode(val)
cur.next = self.head
if self.head:
self.head.prev = cur
if not self.tail:
self.tail = cur
self.head = cur
self.len += 1
return cur
def add_at_tail(self, val: int) -> DoublyListNode:
""" Добавление в хвост """
cur = DoublyListNode(val)
cur.prev = self.tail
if self.tail:
self.tail.next = cur
if not self.head:
self.head = cur
self.tail = cur
self.len += 1
return cur
def add_at_index(self, index: int, val: int) -> Optional[DoublyListNode]:
""" Добавление по индексу """
if index > self.len:
return None
right = self.get(index)
if not right:
return self.add_at_tail(val)
left = right.prev
if not left:
return self.add_at_head(val)
cur = DoublyListNode(val)
cur.prev = left
cur.next = right
left.next = cur
right.prev = cur
self.len += 1
return cur
def delete_at_index(self, index: int) -> None:
""" Удаление по индексу """
curr = self.get(index)
if not curr:
return None
left = curr.prev
right = curr.next
if not left:
return self.delete_at_head()
if not right:
return self.delete_at_tail()
right.prev = left
left.next = right
self.len -= 1
return None
def delete_at_head(self) -> None:
""" Удаление из головы """
if not self.head:
return None
cur = self.head.next
if cur:
cur.prev = None
else:
# Если в списке один элемент и мы его удаляем
self.tail = None
self.head = cur
self.len -= 1
return None
def delete_at_tail(self) -> None:
""" Удаление с хвоста """
if not self.tail:
return None
cur = self.tail.prev
if cur:
cur.next = None
else:
# Если в списке один элемент и мы его удаляем
self.head = None
self.tail = cur
self.len -= 1
return None