← Назад к вопросам

Можно ли использовать связанный список в Python?

1.3 Junior🔥 111 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Можно ли использовать связанный список в Python?

Связанный список (Linked List) — это классическая структура данных, которую можно использовать в Python, но не всегда нужно. Давайте разберемся, когда её использовать.

1. Короткий ответ

Да, можно, но в большинстве случаев Python список (list) лучше.

В Python встроенный список list оптимизирован настолько, что связанный список редко требуется. Однако знание структуры критично для собеседований и специфических задач.

2. Что такое связанный список?

Связанный список — это структура данных, где каждый узел содержит:

  • Данные (value)
  • Ссылку на следующий узел (next)
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

# Создание узлов
node1 = Node(10)
node2 = Node(20)
node3 = Node(30)

# Связывание
node1.next = node2
node2.next = node3

# Визуально:
# [10] -> [20] -> [30] -> None

3. Реализация простого связанного списка

class LinkedListNode:
    def __init__(self, value):
        self.value = value
        self.next = None

class LinkedList:
    def __init__(self):
        self.head = None
    
    def append(self, value):
        # Добавить элемент в конец
        if not self.head:
            self.head = LinkedListNode(value)
            return
        
        current = self.head
        while current.next:
            current = current.next
        current.next = LinkedListNode(value)
    
    def insert(self, position, value):
        # Вставить элемент на позицию
        if position == 0:
            new_node = LinkedListNode(value)
            new_node.next = self.head
            self.head = new_node
            return
        
        current = self.head
        for _ in range(position - 1):
            if not current:
                return
            current = current.next
        
        new_node = LinkedListNode(value)
        new_node.next = current.next
        current.next = new_node
    
    def delete(self, value):
        # Удалить элемент по значению
        if not self.head:
            return
        
        if self.head.value == value:
            self.head = self.head.next
            return
        
        current = self.head
        while current.next:
            if current.next.value == value:
                current.next = current.next.next
                return
            current = current.next
    
    def search(self, value):
        # Поиск элемента
        current = self.head
        while current:
            if current.value == value:
                return True
            current = current.next
        return False
    
    def display(self):
        # Вывести все элементы
        elements = []
        current = self.head
        while current:
            elements.append(str(current.value))
            current = current.next
        print(" -> ".join(elements) + " -> None")
    
    def __len__(self):
        # Длина списка
        count = 0
        current = self.head
        while current:
            count += 1
            current = current.next
        return count

# Использование
ll = LinkedList()
ll.append(10)
ll.append(20)
ll.append(30)
ll.insert(1, 15)
ll.display()  # 10 -> 15 -> 20 -> 30 -> None
print(f"Length: {len(ll)}")  # 4

4. Двусвязный список (Doubly Linked List)

class DoublyNode:
    def __init__(self, value):
        self.value = value
        self.next = None
        self.prev = None

class DoublyLinkedList:
    def __init__(self):
        self.head = None
    
    def append(self, value):
        # Добавить элемент в конец
        new_node = DoublyNode(value)
        
        if not self.head:
            self.head = new_node
            return
        
        current = self.head
        while current.next:
            current = current.next
        
        current.next = new_node
        new_node.prev = current
    
    def traverse_forward(self):
        # Движение вперед
        result = []
        current = self.head
        while current:
            result.append(current.value)
            current = current.next
        return result
    
    def traverse_backward(self):
        # Движение назад (нужно сохранить хвост)
        if not self.head:
            return []
        
        current = self.head
        while current.next:
            current = current.next
        
        result = []
        while current:
            result.append(current.value)
            current = current.prev
        return result

# Использование
dll = DoublyLinkedList()
dll.append(10)
dll.append(20)
dll.append(30)
print(dll.traverse_forward())  # [10, 20, 30]
print(dll.traverse_backward())  # [30, 20, 10]

5. Когда использовать связанный список?

✅ Когда использовать:

a) Частые вставки/удаления в начало

# Связанный список: O(1)
ll.insert(0, new_value)  # Просто изменить head

# Обычный список: O(n)
my_list.insert(0, new_value)  # Сдвинуть все элементы

b) Когда размер списка неизвестен заранее

# Связанный список: динамический рост без переаллокации
ll = LinkedList()
while user_input := input("Enter value: "):
    ll.append(user_input)  # Всегда O(1) после оптимизации

c) Кэширование и LRU Caches

from collections import OrderedDict

# LRU Cache использует двусвязный список + dict
class LRUCache:
    def __init__(self, capacity):
        self.cache = {}
        self.capacity = capacity
        # Можно реализовать с DoublyLinkedList для O(1) операций

d) Реализация Stack и Queue

# Stack (LIFO) с связанным списком
class Stack:
    def __init__(self):
        self.head = None
    
    def push(self, value):
        new_node = LinkedListNode(value)
        new_node.next = self.head
        self.head = new_node
    
    def pop(self):
        if not self.head:
            return None
        value = self.head.value
        self.head = self.head.next
        return value

# Queue (FIFO) с связанным списком
class Queue:
    def __init__(self):
        self.head = None
        self.tail = None
    
    def enqueue(self, value):
        new_node = LinkedListNode(value)
        if not self.head:
            self.head = new_node
        else:
            self.tail.next = new_node
        self.tail = new_node
    
    def dequeue(self):
        if not self.head:
            return None
        value = self.head.value
        self.head = self.head.next
        return value

❌ Когда НЕ использовать:

Обычный Python список лучше когда:

# 1. Нужен случайный доступ по индексу
my_list[5]  # O(1) для list, O(n) для linked list

# 2. Нужна производительность
# list оптимизирован на C
# LinkedList = Python код (медленнее)

# 3. Нужна краткость кода
lst = [1, 2, 3]  # vs. создание LinkedList

# 4. Мало вставок/удалений в начало
# Для этого есть deque из collections
from collections import deque
d = deque([1, 2, 3])
d.appendleft(0)  # O(1)

6. Сравнение производительности

import time
from collections import deque

# Операция: добавление 10000 элементов в начало

# 1. Обычный список (ПЛОХО)
lst = []
start = time.time()
for i in range(10000):
    lst.insert(0, i)  # O(n) каждый раз!
print(f"list.insert(0): {time.time() - start:.4f}s")  # ~10 сек

# 2. Связанный список (ХОРОШО)
ll = LinkedList()
start = time.time()
for i in range(10000):
    node = LinkedListNode(i)
    node.next = ll.head
    ll.head = node  # O(1)
print(f"LinkedList.prepend: {time.time() - start:.4f}s")  # ~0.01 сек

# 3. deque (ЛУЧШЕ)
d = deque()
start = time.time()
for i in range(10000):
    d.appendleft(i)  # O(1)
print(f"deque.appendleft: {time.time() - start:.4f}s")  # ~0.001 сек

7. Оптимальный выбор в Python

# Нужна работа с началом? → используй deque
from collections import deque
d = deque([1, 2, 3])
d.appendleft(0)   # O(1)
d.popleft()       # O(1)

# Нужна работа с концом? → используй list
lst = [1, 2, 3]
lst.append(4)     # O(1)
lst.pop()         # O(1)

# Нужен стек? → используй list (последний элемент)
stack = [1, 2, 3]
stack.append(4)   # Push: O(1)
stack.pop()       # Pop: O(1)

# Нужна очередь? → используй deque
queue = deque([1, 2, 3])
queue.append(4)   # Enqueue: O(1)
queue.popleft()   # Dequeue: O(1)

# Нужен связанный список? → Редко в production!
# Используй только для учебы или специфических случаев

8. Практический пример: Cycle Detection

def has_cycle(head):
    # Определить есть ли цикл в связанном списке
    # Используем два указателя: медленный и быстрый
    
    slow = head
    fast = head
    
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        
        if slow == fast:
            return True  # Цикл найден
    
    return False

# Тест
node1 = LinkedListNode(1)
node2 = LinkedListNode(2)
node3 = LinkedListNode(3)
node1.next = node2
node2.next = node3
node3.next = node1  # Цикл!

print(has_cycle(node1))  # True

Итоговая рекомендация

Можно ли использовать связанный список в Python?

  • Да, можно и нужно знать как реализовать
  • Но в production почти никогда не используйте
  • Вместо этого используйте:
    • list для случайного доступа и работы с концом
    • deque для работы с началом и концом
    • Встроенные структуры из collections

Связанный список в Python нужен для:

  • Понимания структур данных на собеседованиях
  • Реализации специфичных алгоритмов
  • Обучения и освоения Computer Science
  • Редких случаев, когда нет встроенной альтернативы
Можно ли использовать связанный список в Python? | PrepBro