Односвязные списки
Материал из m6a
Односвязный список — это структура данных, представляющая собой последовательность элементов, называемых узлами (nodes), где каждый узел содержит данные и ссылку (pointer) на следующий узел в списке. В отличие от массивов, элементы односвязного списка не обязательно хранятся в памяти последовательно.
Содержание
- 1 Основные понятия
- 2 Структура узла
- 3 Свойства односвязных списков
- 4 Операции над односвязными списками
- 5 Реализация односвязного списка
- 6 Операции над односвязными списками (подробно)
- 7 Временная сложность операций над односвязными списками
- 8 Преимущества и недостатки использования односвязных списков
- 9 Применение
Основные понятия
- Узел (Node): Основной элемент односвязного списка, содержащий данные и ссылку на следующий узел.
- Данные (Data): Информация, хранящаяся в узле.
- Ссылка (Link/Pointer): Указатель на следующий узел в списке.
- Голова (Head): Первый узел в списке.
- Хвост (Tail): Последний узел в списке. У хвоста ссылка на следующий узел равна `NULL` или `None`.
Структура узла
Узел односвязного списка обычно состоит из двух частей:
- Данные (Data): Информация, хранящаяся в узле. Тип данных может быть любым.
- Ссылка на следующий узел (Next): Указатель на следующий узел в списке. Если узел является последним в списке, то ссылка равна `NULL` или `None`.
Пример структуры узла на Python:
```python class Node:
def __init__(self, data): self.data = data self.next = None
```
Свойства односвязных списков
- Динамический размер: Размер односвязного списка может изменяться во время выполнения программы, в отличие от статических массивов.
- Непоследовательное хранение в памяти: Узлы односвязного списка не обязательно хранятся в памяти последовательно. Это позволяет эффективно использовать память, но может замедлять доступ к элементам списка.
- Простота вставки и удаления элементов: Вставка и удаление элементов в односвязном списке может быть выполнена за константное время ($O(1)$), если известен указатель на узел, перед которым или после которого нужно вставить или удалить элемент.
- Последовательный доступ: Доступ к элементам односвязного списка осуществляется последовательно, начиная с головы списка. Для доступа к элементу с заданным индексом необходимо пройти по всем предыдущим элементам списка.
Операции над односвязными списками
- Создание списка (Creation): Создание нового односвязного списка.
- Вставка элемента (Insertion): Добавление нового узла в список.
- Удаление элемента (Deletion): Удаление узла из списка.
- Поиск элемента (Search): Поиск узла с заданным значением в списке.
- Обход списка (Traversal): Проход по всем узлам списка для выполнения каких-либо действий.
- Определение длины списка (Length): Вычисление количества узлов в списке.
- Разворот списка (Reverse): Изменение порядка узлов в списке на обратный.
Реализация односвязного списка
Пример реализации односвязного списка на Python:
```python class Node:
def __init__(self, data): self.data = data self.next = None
class LinkedList:
def __init__(self): self.head = None
def insert_at_beginning(self, data): new_node = Node(data) new_node.next = self.head self.head = new_node
def insert_at_end(self, data): new_node = Node(data) if self.head is None: self.head = new_node return last = self.head while (last.next): last = last.next last.next = new_node
def insert_after(self, prev_node, data): if prev_node is None: print("Previous node must be in the list") return
new_node = Node(data) new_node.next = prev_node.next prev_node.next = new_node
def delete_node(self, key): temp = self.head if (temp is not None): if (temp.data == key): self.head = temp.next temp = None return
while(temp is not None): if temp.data == key: break prev = temp temp = temp.next
if(temp == None): return
prev.next = temp.next temp = None
def search(self, x): current = self.head while (current != None): if current.data == x: return True current = current.next return False
def print_list(self): temp = self.head while (temp): print(temp.data, end=" ") temp = temp.next print()
```
Операции над односвязными списками (подробно)
- Создание списка:
- ```python
- linked_list = LinkedList() # Создание пустого односвязного списка
- ```
- Вставка элемента в начало списка:
- ```python
- linked_list.insert_at_beginning(10) # Вставка элемента 10 в начало списка
- ```
- Вставка элемента в конец списка:
- ```python
- linked_list.insert_at_end(20) # Вставка элемента 20 в конец списка
- ```
- Вставка элемента после заданного узла:
- ```python
- node = linked_list.head # Получение головы списка
- linked_list.insert_after(node, 15) # Вставка элемента 15 после головы списка
- ```
- Удаление элемента с заданным значением:
- ```python
- linked_list.delete_node(10) # Удаление элемента 10 из списка
- ```
- Поиск элемента с заданным значением:
- ```python
- found = linked_list.search(20) # Поиск элемента 20 в списке
- if found:
- print("Element 20 found in the list")
- else:
- print("Element 20 not found in the list")
- ```
- Обход списка и вывод элементов:
- ```python
- linked_list.print_list() # Вывод всех элементов списка
- ```
Временная сложность операций над односвязными списками
- Вставка в начало списка: $O(1)$
- Вставка в конец списка: $O(n)$ (требуется проход по всему списку для нахождения хвоста)
- Вставка после заданного узла: $O(1)$ (если известен указатель на заданный узел)
- Удаление элемента: $O(n)$ (в худшем случае требуется проход по всему списку для нахождения удаляемого элемента)
- Поиск элемента: $O(n)$ (в худшем случае требуется проход по всему списку)
- Обход списка: $O(n)$
- Определение длины списка: $O(n)$ (требуется проход по всему списку)
Преимущества и недостатки использования односвязных списков
Преимущества:
- Динамический размер: Размер списка может изменяться во время выполнения программы.
- Простота вставки и удаления элементов: Вставка и удаление элементов может быть выполнена за константное время, если известен указатель на узел, перед которым или после которого нужно вставить или удалить элемент.
- Эффективное использование памяти: Узлы списка хранятся в памяти независимо друг от друга, что позволяет эффективно использовать память.
Недостатки:
- Последовательный доступ: Доступ к элементам списка осуществляется последовательно, что может замедлять доступ к элементам с большим индексом.
- Дополнительные затраты памяти на хранение указателей: Для каждого элемента списка необходимо хранить указатель на следующий элемент, что увеличивает потребление памяти.
- Сложность реализации некоторых операций: Некоторые операции, такие как поиск элемента с заданным индексом или разворот списка, могут быть сложными в реализации.
Применение
Односвязные списки используются в широком спектре приложений, включая:
- Реализация других структур данных (например, стеков, очередей, хеш-таблиц).
- Управление памятью.
- Представление списков задач.
- Реализация динамических коллекций данных.
Понимание односвязных списков и умение их эффективно использовать является важным для разработки качественного и производительного программного обеспечения.