Односвязные списки — это тип структуры данных, в которой значения хранятся в виде списка. В списке каждое значение считается узлом, и каждый узел связан со следующим значением в списке (или нулевым, если элемент является последним в списке) через указатель.

Первый элемент списка считается головным, а последний — хвостовым. Как и в случае с массивами, свойство длины определяется как количество элементов, содержащихся в списке.

Основные отличия от массивов следующие:

  • Списки не имеют индексов. Каждое значение «знает» только значения, с которыми оно связано через указатели.
  • Поскольку у списков нет индексов, мы не можем получить доступ к значениям случайным образом. Когда мы хотим получить доступ к значению, нам всегда приходится искать его, перебирая список, начиная с его головы или хвоста.
  • Преимущество отсутствия индексов в том, что вставка/удаление в любой части списка более эффективна, чем с массивами. Нам остается только перенаправить указатели «соседних» значений, а в массивах или более поздних значениях нужно переиндексировать.

Как и в любой структуре данных, в ней реализованы различные методы для работы с данными. Наиболее распространенные из них включают в себя: push, pop, unshift, shift, get, set, insert, remove и reverse.

Толкать()

  • Функция, которая принимает значение в качестве входных данных и создает с ним новый узел.
  • Если головы нет (пустой список), он назначит новый узел как голову и хвост.
  • Если список не пуст, он назначает новый узел хвостом и соединяет предыдущую голову с новым узлом с помощью указателя «следующий».
  • Он увеличивает свойство length на единицу и возвращает список с добавленным элементом.

Возможная реализация может быть следующей:

push(val){
        var newNode = new Node(val);
        if(!this.head){
            this.head = newNode;
            this.tail = this.head;
        } else {
            this.tail.next = newNode;
            this.tail = newNode;
        }
        this.length++;
        return this;
    }

Поп()

  • Функция, которая стирает последнее значение (хвост) списка.
  • Если список пуст, он возвращает undefined.
  • Он проходит по списку, пока не достигает хвоста и не стирает его.
  • Он изменяет указатель предыдущего значения, чтобы он указывал на ноль, и присваивает предыдущее значение хвосту.
  • Он уменьшает свойство длины на единицу и возвращает стертый элемент.

Возможная реализация может быть следующей:

pop(){
        if(!this.head) return undefined;
        var current = this.head;
        var newTail = current;
        while(current.next){
            newTail = current;
            current = current.next;
        }
        this.tail = newTail;
        this.tail.next = null;
        this.length--;
        if(this.length === 0){
            this.head = null;
            this.tail = null;
        }
        return current;
    }

отменить сдвиг ()

  • Функция, которая принимает значение и добавляет его в начало списка.
  • Если головы нет (пустой список), он назначит новый узел как голову и хвост.
  • Если список не пуст, новый узел назначается головным.

Возможная реализация может быть следующей:

unshift(val){
        var newNode = new Node(val);
        if(!this.head) {
            this.head = newNode;
            this.tail = this.head;
        } else {
            newNode.next = this.head;
            this.head = newNode;
        }
        this.length++;
        return this;
    }

Сдвиг()

  • Функция, которая стирает первое значение (заголовок) списка.
  • Если список пуст, он возвращает undefined.
  • Он присваивает свойству head следующее значение, уменьшает свойство length на единицу и возвращает стертое значение элемента.

Возможная реализация может быть следующей:

shift(){
        if(!this.head) return undefined;
        var currentHead = this.head;
        this.head = currentHead.next;
        this.length--;
        if(this.length === 0){
            this.tail = null;
        }
        return currentHead;
    }

получить()

  • Функция, которая принимает число (индекс или позицию) и извлекает узел с заданной позицией в списке.
  • Если индекс меньше нуля или больше или равен длине списка, он возвращает значение null.
  • В противном случае он перебирает список до тех пор, пока не будет достигнут заданный индекс и не вернет это значение.

Возможная реализация может быть следующей:

get(index){
        if(index < 0 || index >= this.length) return null;
        var counter = 0;
        var current = this.head;
        while(counter !== index){
            current = current.next;
            counter++;
        }
        return current;
    }

набор()

  • Функция, которая принимает число (индекс или позицию) и значение и изменяет значение узла с заданной позицией в списке.
  • Если значение не найдено, возвращается false. В противном случае, если операция завершена, она возвращает true.

Возможная реализация может быть следующей:

set(index, val){
        var foundNode = this.get(index);
        if(foundNode){
            foundNode.val = val;
            return true;
        }
        return false;
    }

вставлять()

  • Функция, которая принимает число (индекс или позицию) и значение и вставляет значение как новый узел с заданной позицией в списке.
  • Если значение меньше нуля или больше длины списка, вернуть undefined.
  • Если индекс совпадает с длиной, добавьте новый узел в список. Если индекс равен нулю, снимите новый узел.
  • В противном случае, используя метод get, мы получаем доступ к узлу, предшествующему позиции, в которую мы хотим ввести новый узел, и перенаправляем его указатель на новый узел. Направляем указатель нового узла на следующий по порядку узел.
  • Увеличьте длину списка и верните true.

Возможная реализация может быть следующей:

if(index < 0 || index > this.length) return false;
        if(index === this.length) return !!this.push(val);
        if(index === 0) return !!this.unshift(val);
        var newNode = new Node(val);
        var prev = this.get(index - 1);
        var temp = prev.next;
        prev.next = newNode;
        newNode.next = temp;
        this.length++;
        return true;

Удалить()

  • Функция, которая принимает число (индекс или позицию), удаляет узел с заданной позицией в списке.
  • Если значение меньше нуля или больше длины списка, вернуть undefined.
  • Если индекс совпадает с длиной, извлеките последний элемент. Если индекс равен нулю, сдвиньте первый элемент.
  • В противном случае, используя метод get, мы получаем доступ к узлу, предшествующему позиции, которую мы хотим удалить, и перенаправляем его указатель на узел, следующий за тем, который мы хотим удалить.
  • Уменьшите длину списка и верните значение стертого узла.

Возможная реализация может быть следующей:

if(index < 0 || index >= this.length) return undefined;
        if(index === 0) return this.shift();
        if(index === this.length - 1) return this.pop();
        var previousNode = this.get(index - 1);
        var removed = previousNode.next;
        previousNode.next = removed.next;
        this.length--;
        return removed;

обеспечить регресс()

  • Функция, которая меняет порядок списка на противоположный.
  • Меняет голову и хвост.
  • Создает переменные с именами next и prev.
  • Создает переменную с именем node и инициализирует ее свойством head.
  • Затем выполните цикл по списку и: 1) Установите следующую переменную в качестве следующего свойства на любом узле цикла. 2) Установите следующее свойство на узле таким же, как и предыдущее. 3) Установите prev в качестве значения переменной узла. 4) Установите переменную узла в качестве значения следующей переменной.
  • После завершения цикла верните список.

Возможная реализация может быть следующей:

var node = this.head;
      this.head = this.tail;
      this.tail = node;
      var next;
      var prev = null;
      for(var i = 0; i < this.length; i++){
        next = node.next;
        node.next = prev;
        prev = node;
        node = next;
      }
      return this;

Полная реализация односвязного списка может быть следующей:

class Node{
    constructor(val){
        this.val = val;
        this.next = null;
    }
}
class SinglyLinkedList{
    constructor(){
        this.head = null;
        this.tail = null;
        this.length = 0;
    }
    push(val){
        var newNode = new Node(val);
        if(!this.head){
            this.head = newNode;
            this.tail = this.head;
        } else {
            this.tail.next = newNode;
            this.tail = newNode;
        }
        this.length++;
        return this;
    }
    pop(){
        if(!this.head) return undefined;
        var current = this.head;
        var newTail = current;
        while(current.next){
            newTail = current;
            current = current.next;
        }
        this.tail = newTail;
        this.tail.next = null;
        this.length--;
        if(this.length === 0){
            this.head = null;
            this.tail = null;
        }
        return current;
    }
    shift(){
        if(!this.head) return undefined;
        var currentHead = this.head;
        this.head = currentHead.next;
        this.length--;
        if(this.length === 0){
            this.tail = null;
        }
        return currentHead;
    }
    unshift(val){
        var newNode = new Node(val);
        if(!this.head) {
            this.head = newNode;
            this.tail = this.head;
        }
        newNode.next = this.head;
        this.head = newNode;
        this.length++;
        return this;
    }
    get(index){
        if(index < 0 || index >= this.length) return null;
        var counter = 0;
        var current = this.head;
        while(counter !== index){
            current = current.next;
            counter++;
        }
        return current;
    }
    set(index, val){
        var foundNode = this.get(index);
        if(foundNode){
            foundNode.val = val;
            return true;
        }
        return false;
    }
    insert(index, val){
        if(index < 0 || index > this.length) return false;
        if(index === this.length) return !!this.push(val);
        if(index === 0) return !!this.unshift(val);
        var newNode = new Node(val);
        var prev = this.get(index - 1);
        var temp = prev.next;
        prev.next = newNode;
        newNode.next = temp;
        this.length++;
        return true;
    }
    remove(index){
        if(index < 0 || index >= this.length) return undefined;
        if(index === 0) return this.shift();
        if(index === this.length - 1) return this.pop();
        var previousNode = this.get(index - 1);
        var removed = previousNode.next;
        previousNode.next = removed.next;
        this.length--;
        return removed;
    }
    reverse(){
      var node = this.head;
      this.head = this.tail;
      this.tail = node;
      var next;
      var prev = null;
      for(var i = 0; i < this.length; i++){
        next = node.next;
        node.next = prev;
        prev = node;
        node = next;
      }
      return this;
    }
    print(){
        var arr = [];
        var current = this.head
        while(current){
            arr.push(current.val)
            current = current.next
        }
        console.log(arr);
    }
}

Сложность

Функции односвязных списков имеют следующие сложности:

  • Вставка — О(1)
  • Удаление — O(n)
  • Поиск по)
  • Доступ — O(n)