Односвязный список в структурах данных

⚡ Умное резюме

Singly Linked List is a linear, unidirectional data structure where each node stores data and a single pointer to the next node, so traversal moves head-to-tail only and memory is allocated dynamically as new nodes get added.

  • 🧩 Структура узла: Each node holds one data field and one следующий pointer to the following node; the tail node’s следующий pointer is NULL.
  • 📦 List vs Array: Singly Linked Lists are preferred when the element count is unknown, random access is not required, and mid-list insertion is common.
  • Вставки: Nodes can be added at the head, at the tail, after a matched node, or before a matched node using next-pointer rewrites.
  • Удаления: Removing the head, tail, or a searched node updates neighbor pointers and frees the released memory to avoid leaks.
  • 🔁 Обход: Only forward traversal is supported because there is no previous pointer, so reverse walking a Singly Linked List is not possible.
  • 💻 C++ и Python Code: Complete implementations show insert, delete, search, and traverse routines with runnable output.
  • 📊 Сложность: Head insertion or deletion is O(1); search and other insertions and deletions are O(n); space complexity is O(n).

Односвязный список

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

Singly Linked List is a linear and unidirectional data structure where data is saved on the nodes, and each node is connected via a link to its next node. Each node contains a data field and a link to the next node. Singly Linked Lists can be traversed in only one direction, whereas a Двусвязный список can be traversed in both directions.

Here is the node structure of a Singly Linked List:

Структура узла в связанном списке

Структура узла в связанном списке

Why Use a Linked List Over an Array?

Several scenarios favor a Linked List over an массив:

  • Неизвестное количество элементов: When the required element count is not known at compile time, a Linked List allocates memory dynamically as elements get added.
  • Произвольный доступ: When random indexed access is not needed, a Linked List is a suitable choice.
  • Вставка посередине: Inserting in the middle of an array requires shifting elements. A Linked List allows insertion at any position by rewriting only a few pointers.

Operaции односвязного списка

A Singly Linked List is good for dynamically allocating memory. It supports the standard operations of the linked list, i.e., insertion, deletion, searching, updating, merging two lists, and traversing.

The following operations are discussed in this article:

  • Вставка в голову
  • Вставка в хвост
  • Вставка после узла
  • Вставка перед узлом
  • Удалить головной узел
  • Удалить хвостовой узел
  • Найти и удалить узел
  • Обход связанного списка

Here is an example of a linked list with four nodes.

Пример односвязного списка

Пример односвязного списка

Insertion at the Head of a Singly Linked List

This is a simple operation. It is generally known as pushing onto a Singly Linked List. A new node is created and placed at the head of the list.

To perform this operation, follow two important conditions:

  1. If the list is empty, the newly created node becomes the head node, and its следующий pointer is NULL.
  2. If the list is not empty, the new node becomes the head node, and its следующий pointer points to the previous head node.

Here is the pseudo-code for inserting a node at the head of a linked list:

function insertAtHead(head, value):
  newNode = Node(value)
  if head is NULL:
    head = newNode
    return head
  else:
    newNode.next = head
    return newNode

Вставка в голову

Вставка в голову

Insertion at the End of a Singly Linked List

Inserting a node at the end of a linked list is similar to inserting at the head. Traverse to the tail node, then point its следующий pointer to the new node. If the head is NULL, the new node becomes the head.

Шаг 1) Traverse until the следующий pointer of the current node becomes NULL.

Шаг 2) Создайте новый узел с указанным значением.

Шаг 3) Назначьте новый узел следующим узлом хвостового узла.

The pseudo-code for inserting at the tail of a singly list:

function insertAtEnd(head, value):
  newNode = Node(value)
  if head is NULL:
    head = newNode
    return head
  while head.next is not NULL:
    head = head.next
  head.next = newNode
  newNode.next = NULL

Вставка в хвост

Вставка в хвост

Insertion After a Node in a Singly Linked List

Inserting after a node has two parts: search for the target node and attach a new node after it. Traverse the list until a match is found, then splice the new node in.

Шаг 1) Traverse until the value of the current node equals the search item.

Шаг 2) Set the new node’s следующий pointer to the current node’s следующий указатель.

Шаг 3) Point the current node’s следующий pointer to the new node.

Псевдокод:

function insertAfter(head, value, searchItem):
  newNode = Node(value)
  while head.value != searchItem:
    head = head.next
  newNode.next = head.next
  head.next = newNode

Вставка узла после узла в односвязном списке

Вставка узла после узла в односвязном списке

Insertion Before a Node in a Singly Linked List

This is similar to insertion after a node. Traverse until the next node matches the search value, then insert the new node before it.

Шаг 1) Перемещайтесь до тех пор, пока значение следующего узла не станет равным искомому элементу.

Шаг 2) Create a new node and set its следующий pointer to the current node’s следующий.

Шаг 3) Point the current node’s следующий to the new node.

function insertBefore(head, value, searchItem):
  newNode = Node(value)
  while head.next.value != searchItem:
    head = head.next
  newNode.next = head.next
  head.next = newNode

Вставка узла перед узлом в односвязном списке

Вставка узла перед узлом в односвязном списке

Delete the Head of the Singly Linked List

The head pointer is provided as the parameter. The head node is removed, and the next node becomes the new head. The memory of the deleted node must be freed to avoid memory leaks.

Шаг 1) Assign the next node of the head as the new head.

Шаг 2) Free the allocated memory of the previous head node.

Шаг 3) Верните новый головной узел.

function deleteHead(head):
  temp = head
  head = head.next
  free(temp)
  return head

Удаление главы связанного списка

Удаление главы связанного списка

Delete the Tail of the Singly Linked List

Deleting the tail node is similar to deleting the head node. The difference is that traversal to the end of the list is required. In a Singly Linked List, the node whose следующий pointer is NULL is the tail node.

Шаг 1) Traverse until just before the tail node. Save the current node.

Шаг 2) Free the memory of the next node (the tail).

Шаг 3) Set the next node of the current node to NULL.

function deleteTail(head):
  while head.next.next is not NULL:
    head = head.next
  free(head.next)
  head.next = NULL

Удаление хвоста односвязного списка

Удаление хвоста односвязного списка

Search and Delete a Node from a Singly Linked List

This function performs two tasks: search and delete. Traverse until the end of the list. If a matching node is found, remove it and relink the previous node’s следующий указатель.

Шаг 1) Traverse until the end of the list. Check whether the current node equals the search node.

Шаг 2) If a match is found, store a pointer to the current node.

Шаг 3) следующий of the previous node becomes the next node of the current node.

Шаг 4) Delete the current node and free its memory.

function searchAndDelete(head, searchItem):
  while head.next.next is not NULL and head.next.value != searchItem:
    head = head.next
  temp = head.next
  head.next = head.next.next
  free(temp)

Найдите и удалите узел из односвязного списка

Найдите и удалите узел из односвязного списка.

Traverse a Singly Linked List

A Singly Linked List only supports traversal from head to tail. There is no pointer to the previous node, so reverse traversal is not possible. Each node is visited in turn, printing its value until NULL is reached.

Шаг 1) Traverse each node until NULL is reached.

Шаг 2) Выведите значение текущего узла.

function traverse(head):
  while head is not NULL:
    print head.value
    head = head.next

Пример односвязного списка в C++

#include<iostream>
using namespace std;
struct Node{
  int data;
  struct Node *next;
};
void insertAtHead(Node* &head, int value){
  Node* newNode = new Node();
  newNode->data = value;
  newNode->next = NULL;
  if(head != NULL){
    newNode->next = head;
  }
  head = newNode;
  cout<<"Added "<<newNode->data<<" at the front"<<endl;
}
void insertEnd(Node* &head, int value){
  if(head == NULL){
    insertAtHead(head, value);
    return;
  }
  Node* newNode = new Node();
  newNode->data = value;
  newNode->next = NULL;
  Node *temp = head;
  while(temp->next != NULL){
    temp = temp->next;
  }
  temp->next = newNode;
  cout<<"Added "<<newNode->data<<" at the end"<<endl;
}
void searchAndDelete(Node **headPtr, int searchItem){
  Node *temp = NULL;
  if((*headPtr)->data == searchItem){
    temp = *headPtr;
    *headPtr = (*headPtr)->next;
    free(temp);
  } else {
    Node *currentNode = *headPtr;
    while(currentNode->next != NULL){
      if(currentNode->next->data == searchItem){
        temp = currentNode->next;
        currentNode->next = currentNode->next->next;
        free(temp);
        break;
      } else {
        currentNode = currentNode->next;
      }
    }
  }
  cout<<"Deleted Node\t"<<searchItem<<endl;
}
void insertAfter(Node* &headPtr, int searchItem, int value){
  Node* newNode = new Node();
  newNode->data = value;
  newNode->next = NULL;
  Node *head = headPtr;
  while(head->next != NULL && head->data != searchItem){
    head = head->next;
  }
  newNode->next = head->next;
  head->next = newNode;
  cout<<"Inserted "<<value<<" after node\t"<<searchItem<<endl;
}
void insertBefore(Node* &headPtr, int searchItem, int value){
  Node* newNode = new Node();
  newNode->data = value;
  newNode->next = NULL;
  Node *head = headPtr;
  while(head->next != NULL && head->next->data != searchItem){
    head = head->next;
  }
  newNode->next = head->next;
  head->next = newNode;
  cout<<"Inserted "<<value<<" before node\t"<<searchItem<<endl;
}
void traverse(Node *headPointer){
  Node* tempNode = headPointer;
  cout<<"Traversal from head:\t";
  while(tempNode != NULL){
    cout<<tempNode->data;
    if(tempNode->next)
      cout<<" --> ";
    tempNode = tempNode->next;
  }
  cout<<endl;
}
int main(){
  Node *head = NULL;
  insertAtHead(head, 5);
  insertAtHead(head, 6);
  insertAtHead(head, 7);
  insertEnd(head, 9);
  traverse(head);
  searchAndDelete(&head, 6);
  traverse(head);
  insertAfter(head, 7, 10);
  insertBefore(head, 9, 11);
  traverse(head);
}

Результат

Added 5 at the front
Added 6 at the front
Added 7 at the front
Added 9 at the end
Traversal from head:    7 --> 6 --> 5 --> 9
Deleted Node    6
Traversal from head:    7 --> 5 --> 9
Inserted 10 after node  7
Inserted 11 before node 9
Traversal from head:    7 --> 10 --> 5 --> 11 --> 9

Пример односвязного списка в Python

class Node:
  def __init__(self, data=None, next=None):
    self.data = data
    self.next = next
class SinglyLinkedList:
  def __init__(self):
    self.head = None
  def insertAtHead(self, value):
    newNode = Node(data=value)
    if self.head is not None:
      newNode.next = self.head
    self.head = newNode
    print(f'Added {newNode.data} at the front.')
  def insertAtEnd(self, value):
    if self.head is None:
      self.insertAtHead(value)
      return
    newNode = Node(value)
    temp = self.head
    while temp.next is not None:
      temp = temp.next
    temp.next = newNode
    print(f'Added {newNode.data} at the end.')
  def searchAndDelete(self, searchItem):
    if self.head is None:
      return
    if self.head.data == searchItem:
      self.head = self.head.next
      print(f'Deleted node\t{searchItem}')
      return
    currentNode = self.head
    while currentNode.next is not None:
      if currentNode.next.data == searchItem:
        currentNode.next = currentNode.next.next
        print(f'Deleted node\t{searchItem}')
        return
      currentNode = currentNode.next
  def insertAfter(self, searchItem, value):
    newNode = Node(data=value)
    temp = self.head
    while temp.next is not None and temp.data != searchItem:
      temp = temp.next
    newNode.next = temp.next
    temp.next = newNode
    print(f'Inserted {value} after node\t{searchItem}')
  def insertBefore(self, searchItem, value):
    newNode = Node(data=value)
    temp = self.head
    while temp.next is not None and temp.next.data != searchItem:
      temp = temp.next
    newNode.next = temp.next
    temp.next = newNode
    print(f'Inserted {value} before node\t{searchItem}')
  def traverse(self):
    temp = self.head
    print("Traversing from head:\t", end="")
    while temp:
      print("{}\t".format(temp.data), end="")
      temp = temp.next
    print()
singlyLinkedList = SinglyLinkedList()
singlyLinkedList.insertAtHead(5)
singlyLinkedList.insertAtHead(6)
singlyLinkedList.insertAtHead(7)
singlyLinkedList.insertAtEnd(9)
singlyLinkedList.traverse()
singlyLinkedList.searchAndDelete(6)
singlyLinkedList.traverse()
singlyLinkedList.insertAfter(7, 10)
singlyLinkedList.insertBefore(9, 11)
singlyLinkedList.traverse()

Результат

Added 5 at the front.
Added 6 at the front.
Added 7 at the front.
Added 9 at the end.
Traversing from head:   7       6       5       9
Deleted node    6
Traversing from head:   7       5       9
Inserted 10 after node  7
Inserted 11 before node 9
Traversing from head:   7       10      5       11      9

Сложность односвязного списка

There are two kinds of complexity: time complexity and space complexity. The worst and average case time complexity are the same for a Singly Linked List.

лучших-case time complexity:

  • Insertion at the head can be done in O(1). No traversal inside the list is required.
  • Search and delete can be done in O(1) if the target element is at the head node.

Average-case time complexity:

  • Insertion inside a linked list takes O(n), where n is the total number of elements.
  • Search and delete can take O(n) too, because the target element can reside anywhere up to the tail node.

Space complexity of Singly Linked List

A Singly Linked List dynamically allocates memory. To store n elements, it allocates n memory units. So the space complexity is O(n).

Applications of Singly Linked List

Singly Linked Lists appear in many places where forward-only traversal and dynamic memory are useful:

  • Stacks and queues: Underlying storage for LIFO stacks and FIFO queues built from nodes.
  • Hash table chaining: Collisions are resolved by chaining entries into a Singly Linked List per bucket.
  • Adjacency lists: Sparse graphs use a Singly Linked List of neighbors for each vertex.
  • Таблицы символов: Compilers and interpreters chain identifiers into a Singly Linked List per scope.
  • Memory allocators: Free-list allocators track free blocks as a Singly Linked List.

Часто задаваемые вопросы (FAQ)

Singly Linked Lists chain training samples, mini-batches, and free memory blocks inside AI frameworks, enabling dynamic queues for streaming inputs and lock-free data pipelines that scale with model demand.

Yes. GitHub Copilot and GPT can produce a full Singly Linked List in C, C++, Java, Python или JavaScript, including insertion, deletion, reversal, cycle detection, and unit tests.

A Singly Linked List has one next pointer and traverses forward only. A Doubly Linked List has both next and prev pointers and traverses both directions but uses more memory per node.

Common uses include stack and queue implementations, hash-table chaining, adjacency lists for sparse graphs, symbol tables in compilers, free-list allocators, and undo history in lightweight editors.

Insertion or deletion at the head is O(1). Insertion at the tail, search, insertion at a position, and deletion of a specific node all cost O(n) because traversal is required from the head.

Linked Lists grow and shrink at runtime, insert or delete in O(1) once the position is known, and never need contiguous memory. Arrays offer O(1) random access and better cache locality.

Walk the list with three pointers, prev, curr, and next. On each step, save curr.next, point curr.next to prev, and shift prev and curr forward. Return prev as the new head.

Floyd’s tortoise-and-hare algorithm uses two pointers moving at different speeds. If they ever meet, the list contains a cycle. Otherwise, the fast pointer reaches NULL and no cycle exists.

Подведем итог этой публикации следующим образом: