Skip to content

Commit 40789bd

Browse files
agorinenkoAnton Gorinenko
andauthored
Реализация двусвязного списка (#5)
Co-authored-by: Anton Gorinenko <[email protected]>
1 parent bd6a6f8 commit 40789bd

File tree

5 files changed

+187
-25
lines changed

5 files changed

+187
-25
lines changed

img/doubly_linked_list.png

3.64 KB
Loading

img/doubly_linked_list_1.png

-7.94 KB
Loading

img/doubly_linked_list_2.png

2.77 KB
Loading

img/doubly_linked_list_3.png

36.3 KB
Loading

tutorial/doubly_linked_list.md

Lines changed: 187 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,24 @@
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
1617
class 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

4549
1. Связывания вставляемого элемента ``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

Comments
 (0)