55
66![ img.png] ( ../img/doubly_linked_list.png )
77
8- На рисунке выше синяя стрелка отображает связь на следующий элемент, а зеленая - на предыдущий. `` HEAD `` -
9- указатель, хранящий ссылку на головной узел. Заметим, что доступ к хвосту списка осуществляется за счет `` HEAD `` и связи
10- на предыдущий элемент `` prev `` .
8+ На рисунке выше синяя стрелка отображает связь на следующий элемент, а зеленая - на предыдущий. Как и в односвязном
9+ списке, у структуры определены два указателя `` HEAD `` и `` TAIL `` на корневой `` head `` и конечный `` tail `` узлы
10+ соответственно .
1111
1212Каждый узел в двусвязном списке содержит не только значение, но и ссылку на следующий и предыдущий элемент. Программно
1313его можно представить так:
1414
1515```
16+ @dataclass
1617class DoublyListNode:
1718 """
1819 Узел двусвязного списка
1920 """
20- def __init__(self, value, next_node, prev_node):
21- self.value = value
22- self.prev_node = prev_node
23- self.next_node = next_node
21+ val: int
22+ prev: Optional['DoublyListNode'] = None
23+ next: Optional['DoublyListNode'] = None
2424```
2525
26- Как и односвязный список, двусвязный также имеет два указателя `` HEAD `` и `` TAIL `` на корневой `` head `` и
27- конечный `` tail `` узлы соответственно.
28-
2926## Линейный поиск и доступ к элементу по индексу по двусвязному списку
3027
3128Аналогично односвязному списку, в двусвязном мы также имеем:
@@ -38,44 +35,209 @@ class DoublyListNode:
3835
3936Рассмотрим три вида вставки: вставка элемента в середину, в "голову" и в "хвост".
4037
38+ ** Вставка в начало** изображена ниже. Алгоритм вставки следующий:
39+
40+ 1 . Создаем узел `` cur `` и устанавливаем `` next `` у него на `` head ``
41+ 2 . Если `` head `` уже существует и список не пустой, то у `` head `` устанавливаем `` prev `` на `` cur ``
42+ 3 . Если `` tail `` не инициализирован (т.е. добавляемый `` cur `` первый в списке), то устанавливаем `` tail `` как `` cur ``
43+ 4 . Переназначаем `` head `` на `` cur ``
44+
4145![ img.png] ( ../img/doubly_linked_list_1.png )
4246
43- Вставку элемента в середину можно разделить на два шага:
47+ ** Вставку элемента в середину** можно разделить на два шага:
4448
45491 . Связывания вставляемого элемента `` cur `` с узлами `` prev `` и `` next ``
46- 2 . Удаление старых связей `` prev `` -> `` next `` и дальнейшее их переназначение на `` cur ``
50+ 2 . Удаление старых связей для `` prev `` и `` next `` и дальнейшее их переназначение на `` cur ``
4751
48- Операция займет ** O(n)** вне зависимости от того какой узел следующий или предыдущий задается. В отличие от односвязного
49- списка мы располагаем двумя связями.
52+ ![ img.png] ( ../img/doubly_linked_list_2.png )
53+
54+ Операция займет ** O(1)** вне зависимости от того какой узел следующий или предыдущий задается. В отличие от односвязного
55+ списка мы располагаем двумя связями. Следует заметить, что если происходит вставка по индексу, то изначально следует
56+ получить ссылку на правый или левый узел, а это займет ** O(n)** .
5057
51- Вставка в голову и хвост аналогична тому, как это осуществляется в односвязном списке. Всегда ее сложность оценивается в
52- ** O(1)** .
58+ ** Вставка в хвост** аналогична вставке в начало. Ее сложность оценивается в ** O(1)** .
5359
5460Временная сложность операций:
5561
56- Вставка элемента в середину - ** O(1)** .
62+ Вставка элемента в середину - ** O(1)** . При условии, что задается узел для вставки, а не его индекс. Для вставки по
63+ индексу ** O(n)** .
5764
58- Вставка элемента в "голову" - ** O(1)**
65+ Вставка элемента в "голову" - ** O(1)** .
5966
6067Вставка элемента в "хвост" - ** O(1)** .
6168
6269## Операция удаления элемента
6370
64- Рассмотрим удаление из середины.
71+ Алгоритм ** удаления из головы ** следующий:
6572
66- ![ img.png] ( ../img/doubly_linked_list_2.png )
73+ 1 . Берем голову `` head `` и инициализируем `` cur `` значением связи `` next `` .
74+ 2 . Если `` cur `` существует, удаляем у него значение связи `` prev `` .
75+ 3 . Если `` cur `` не существует, в списке один элемент и мы его удаляем. Поэтому обнуляем `` tail `` .
76+ 4 . Устанавливаем `` head `` как `` cur ``
77+
78+ Рассмотрим ** удаление из середины** .
79+
80+ ![ img.png] ( ../img/doubly_linked_list_3.png )
6781
6882В отличие от односвязного списка, где эта операция занимает ** O(n)** из-за того, что нам требуется найти предыдущий
6983элемент для создания связи, двусвязный список имеет ссылку на `` prev `` . Если мы хотим удалить существующий узел `` cur `` ,
7084мы можем просто связать его предыдущий узел `` prev `` со следующим узлом `` next `` . Временная сложность ** O(1)** .
7185
72- Удаление из начала и конца списка является частным случаем удаление из середины, поэтому имеет временную сложность ** O(
73- 1)** .
86+ Удаление из конца списка аналогично удалению из головы, поэтому имеет временную сложность ** O(1)** .
7487
7588Временная сложность операций:
7689
77- Удаление элемента из "головы" - ** O(1)**
90+ Удаление элемента из "головы" - ** O(1)** .
91+
92+ Удаление элемента из середины - ** O(1)** . При условии, что задается узел для вставки, а не его индекс. Для удаления по
93+ индексу ** O(n)** .
94+
95+ Удаление элемента из "хвоста" - ** O(1)** .
96+
97+ ## Реализация двусвязного списка на языке python
7898
79- Удаление элемента из середины - ** O(1)**
99+ ```
100+ """
101+ DoublyLinkedList
102+ """
103+ from dataclasses import dataclass
104+ from typing import Optional
105+
106+
107+ @dataclass
108+ class DoublyListNode:
109+ """
110+ Узел двусвязного списка
111+ """
112+ val: int
113+ prev: Optional['DoublyListNode'] = None
114+ next: Optional['DoublyListNode'] = None
115+
116+
117+ class DoublyLinkedList:
118+ """
119+ Реализация двусвязного списка
120+ """
80121
81- Удаление элемента из "хвоста" - ** O(1)**
122+ def __init__(self):
123+ self.head = None
124+ self.tail = None
125+ self.len = 0
126+
127+ def get(self, index: int) -> Optional[DoublyListNode]:
128+ """ Получение узла по индексу """
129+ cur = self.head
130+ idx = 0
131+ while idx != index:
132+ if not cur:
133+ return None
134+
135+ cur = cur.next
136+ idx += 1
137+
138+ return cur
139+
140+ def add_at_head(self, val: int) -> DoublyListNode:
141+ """ Добавление в голову """
142+ cur = DoublyListNode(val)
143+ cur.next = self.head
144+ if self.head:
145+ self.head.prev = cur
146+ if not self.tail:
147+ self.tail = cur
148+
149+ self.head = cur
150+ self.len += 1
151+
152+ return cur
153+
154+ def add_at_tail(self, val: int) -> DoublyListNode:
155+ """ Добавление в хвост """
156+ cur = DoublyListNode(val)
157+ cur.prev = self.tail
158+ if self.tail:
159+ self.tail.next = cur
160+ if not self.head:
161+ self.head = cur
162+
163+ self.tail = cur
164+ self.len += 1
165+
166+ return cur
167+
168+ def add_at_index(self, index: int, val: int) -> Optional[DoublyListNode]:
169+ """ Добавление по индексу """
170+ if index > self.len:
171+ return None
172+
173+ right = self.get(index)
174+ if not right:
175+ return self.add_at_tail(val)
176+
177+ left = right.prev
178+ if not left:
179+ return self.add_at_head(val)
180+
181+ cur = DoublyListNode(val)
182+ cur.prev = left
183+ cur.next = right
184+ left.next = cur
185+ right.prev = cur
186+ self.len += 1
187+ return cur
188+
189+ def delete_at_index(self, index: int) -> None:
190+ """ Удаление по индексу """
191+ curr = self.get(index)
192+ if not curr:
193+ return None
194+
195+ left = curr.prev
196+ right = curr.next
197+
198+ if not left:
199+ return self.delete_at_head()
200+
201+ if not right:
202+ return self.delete_at_tail()
203+
204+ right.prev = left
205+ left.next = right
206+ self.len -= 1
207+
208+ return None
209+
210+ def delete_at_head(self) -> None:
211+ """ Удаление из головы """
212+ if not self.head:
213+ return None
214+
215+ cur = self.head.next
216+ if cur:
217+ cur.prev = None
218+ else:
219+ # Если в списке один элемент и мы его удаляем
220+ self.tail = None
221+
222+ self.head = cur
223+ self.len -= 1
224+
225+ return None
226+
227+ def delete_at_tail(self) -> None:
228+ """ Удаление с хвоста """
229+ if not self.tail:
230+ return None
231+
232+ cur = self.tail.prev
233+ if cur:
234+ cur.next = None
235+ else:
236+ # Если в списке один элемент и мы его удаляем
237+ self.head = None
238+
239+ self.tail = cur
240+ self.len -= 1
241+
242+ return None
243+ ```
0 commit comments