Двобічно зв'язаний список

Немає перевірених версій цієї сторінки; ймовірно, її ще не перевіряли на відповідність правилам проекту.

Двобічно зв'язаний список — вид зв'язаного списку, у якому посилання в кожному вузлі вказують на попередній і на подальший вузол у списку.


Вузол двобічно зв'язаного списку складається з трьох полів: вказівника на попередній елемент prev, поля даних data та вказівника next на наступний елемент.
Файл:Рис.1 Двозв'язковий список у графічному виляді2.png
Рис.1. Кільцевий двобічно зв'язаний список

Якщо в списку після останнього елемента йде перший, то такий список називається кільцевим двобічно зв'язаним списком. Тобто, поле prev голови списку вказує на хвіст списку, а поле next хвоста списку вказує на голову списку.

По двобічно зв'язаному списку можна пересуватися в будь-якому напрямку — як від початку до кінця, так і навпаки. Для нього простіше проводити видалення і перестановку елементів, оскільки завжди відомі адреси тих елементів списку, вказівник яких спрямований на змінюваний елемент.

Переваги двобічно зв'язаного списку над однобічно зв'язаним списком

ред.
  1. Додавання нового вузла в певну позицію.
  2. Видалення i-го елемента з послідовності.
  3. Перегляд списку в обох напрямках.

Існують і недоліки: з'являється додаткова змінна — вказівник на попередній елемент (prev). У даній статті описаний принцип роботи двобічно зв'язного циклічного списку. Єдина відмінність двобічно зв'язного циклічного списку від двобічно зв'язного списку в тому, що останній елемент указує на перший, а перший — на останній. Дана відмінність дає змогу виключати перевірки на «перший» і «останній» елемент. Двобічно зв'язаний циклічний список (далі просто Двобічно зв'язаний список) візуально можна представити в наступному вигляді (рис.1):

Стандартні функції для двобічно зв'язного списку

ред.

Функція push (додати новий елемент в i-ту позицію списку). Вона виконує наступні дії:

  1. Створює новий елемент.
  2. Копіює значення інформаційного поля.
  3. Якщо даний елемент є єдиним:
    1. Обидва покажчики (next і prev) посилаються на цей елемент (рис. 2).
    2. Покажчик first вказує на єдиний елемент у списку.
  4. Інакше зсувається покажчик на i-й елемент і додається новий елемент перед i-м.

Додати нове значення у двобічно зв'язний список (push 4), новий елемент додаватиметься після першого. Після операції push список міститиме один елемент (рис. 2).

Файл:Рис.2 Додавання нового значення1.png
рис.2 Додавання нового значення

Додавши ще кілька значень (push 6 і push 7), кожен новий елемент буде додаватися після першого. Список міститиме три елементи (рис. 3) у такій послідовності:

Файл:Рис 3. Додавання нових значень1.png
Рис 3. Додавання нових значень

Функція pop виштовхує i-й елемент зі списку. Вона виконує наступні дії:

  1. Якщо список порожній, вихід із функції.
  2. Якщо в список містить єдиний елемент:
    1. Копіюється значення інформаційного поля.
    2. Видаляється елемент зі списку.
    3. Присвоюється заголовку пусто.
  3. Інакше — зсувається покажчик на i-й елемент;
    1. якщо заголовок вказує на i-й елемент (first == t), тоді переміщається заголовок на наступний елемент (First = t-> next)
    2. копіюється значення інформаційного поля;
    3. видаляється i-й елемент зі списку.
  4. Повертається значення інформаційного поля.

Виконавши функцію pop над лінійним списком (виштовхується 3-й елемент). Отримується наступний стан зв'язного списку (рис. 4).

Файл:Рис 4. Виштовхування елементу.1.png
рис 4. Виштовхування елементу.

Функція print_all пробігає по всіх елементах і виводить в консоль значення інформаційного поля. Перегляд елементів здійснюється зліва направо, але легко можна переписати функцію і змінити перегляд елементів справа наліво (замінити a = a-> next на a = a-> prev). Функція clean видаляє всі елементи в списку і привласнює заголовку пусто (first = null). Після цієї операції список повертається в початковий стан.

Приклад реалізації двобічно зв'язаного списку мовою C#

ред.
namespace WikiSamples.LinkedList;

public class LinkedList<T>
    where T : IEquatable<T>
{
    public class Node
    {
        public Node(T value)
        {
            Value = value;
        }

        public Node? Previous { get; set; }

        public T Value { get; init; }

        public Node? Next { get; set; }
    }

    private Node? _head = null;
    private Node? _tail = null;

    public void InsertBefore(Node existingNode, Node newNode)
    {
        var previousNode = existingNode.Previous;

        if (previousNode is not null)
        {
            previousNode.Next = newNode;
            newNode.Previous = previousNode;
        }

        existingNode.Previous = newNode;
        newNode.Next = existingNode;

        if (existingNode == _head)
        {
            _head = newNode;
        }
    }

    public void InsertAfter(Node existingNode, Node newNode)
    {
        var nextNode = existingNode.Next;

        if (nextNode is not null)
        {
            nextNode.Previous = newNode;
            newNode.Next = nextNode;
        }

        existingNode.Next = newNode;
        newNode.Previous = existingNode;

        if (existingNode == _tail)
        {
            _tail = newNode;
        }
    }

    public void InsertFirst(Node newNode)
    {
        if (_head is null)
        {
            _head = _tail = newNode;
        }
        else
        {
            InsertBefore(_head, newNode);
        }
    }

    public void InsertLast(Node newNode)
    {
        if (_tail is null)
        {
            _head = _tail = newNode;
        }
        else
        {
            InsertAfter(_tail, newNode);
        }
    }

    /// <summary>
    /// Finds the node in the list with time complexity of O(N).
    /// </summary>
    public Node? Find(T searchedValue)
    {
        var currentNode = _head;

        while (currentNode is not null)
        {
            if (searchedValue.Equals(currentNode.Value))
            {
                return currentNode;
            }

            currentNode = currentNode.Next;
        }

        return null;
    }

    public IEnumerable<Node> Iterate()
    {
        var currentNode = _head;

        while (currentNode is not null)
        {
            yield return currentNode;

            currentNode = currentNode.Next;
        }
    }

    /// <summary>
    /// Removes the node from the list with time complexity of O(1).
    /// </summary>
    public void Remove(Node node)
    {
        var previousNode = node.Previous;
        var nextNode = node.Next;

        if (previousNode is not null)
        {
            previousNode.Next = nextNode;
        }

        if (nextNode is not null)
        {
            nextNode.Previous = previousNode;
        }

        if (_head == node)
        {
            _head = nextNode;
        }

        if (_tail == node)
        {
            _tail = previousNode;
        }
    }
}

Приклад реалізації двобічно зв'язаного списку на С++

ред.

Джерела

ред.