def delete_by_value(self, data):
# Случай 1: Список пустой
if not self.head:
return
# Случай 2: Удаляем голову
if self.head.data == data:
self.head = self.head.next # Просто сдвигаем голову
return
# Случай 3: Удаляем элемент из середины/конца
current_node = self.head
# Ищем узел ПЕРЕД тем, который нужно удалить
while current_node.next:
if current_node.next.data == data:
# "Перепрыгиваем" через удаляемый узел
current_node.next = current_node.next.next
return
current_node = current_node.next
Наглядно этот процесс выглядит так:
Исходный список:
head → [10|•]→[20|•]→[30|•]→[40|×]
Удаляем 20:
Шаг 1: Находим узел ПЕРЕД удаляемым
head → [10|•]→[20|•]→[30|•]→[40|×]
↑ ↑
current current.next (удаляем это)
Шаг 2: "Перепрыгиваем" через 20
current.next = current.next.next
head → [10|•]─────→[30|•]→[40|×]
\ /
[20|•] (потерян, будет удален сборщиком мусора)
Пример использования:
ll = LinkedList()
ll.append(10)
ll.append(20)
ll.append(30)
ll.append(40)
print(ll.display()) # [10, 20, 30, 40]
ll.delete_by_value(20)
print(ll.display()) # [10, 30, 40]
ll.delete_by_value(10) # Удаляем голову
print(ll.display()) # [30, 40]
Более сложной и гибкой вариацией является двусвязный список. В нем каждый узел содержит уже две ссылки: не только на следующий, но и на предыдущий узел. Это позволяет обходить список в обоих направлениях.
class DoubleNode:
def __init__(self, data):
self.data = data
self.next = None # Ссылка вперед
self.prev = None # Ссылка назад
Визуальная разница очевидна: односвязный список позволяет движение только вперед, в то время как двусвязный обеспечивает двустороннюю навигацию.
Двусвязный список:
None ← head tail → None
[×|10|•]↔️[•|20|•]↔️[•|30|×]
↑ ↑ ↑ ↑ ↑ ↑
prev next prev next prev next
Реализация двусвязного списка включает поддержку ссылки не только на голову, но и на хвост.
Пример 2
Пример показывает его новые возможности:
dll = DoublyLinkedList()
dll.append(10)
dll.append(20)
dll.append(30)
print(dll.display_forward()) # [10, 20, 30]
print(dll.display_backward()) # [30, 20, 10]
Преимущества двусвязного списка включают возможность двустороннего обхода, более простое удаление узла, так как ссылка на предыдущий элемент уже известна, и добавление в конец за O(1) благодаря наличию ссылки на хвост. Однако за эти удобства приходится платить дополнительной памятью на хранение второй ссылки в каждом узле и усложнением логики управления связями.
Таким образом, связный список — это гибкая и динамическая структура данных, идеально подходящая для сценариев, где часто требуется изменение порядка элементов, особенно в начале списка, или когда окончательный размер коллекции заранее неизвестен. Массивы же остаются лучшим выбором, когда критически важен быстрый произвольный доступ к элементам по их индексу.
#algorithm