Рубрики
Без рубрики

Нежное введение в структуры данных: насколько связаны списки

Michael Olorunnisola Нежное введение в структуры данных: насколько связаны списки WorkpinterSthave Вы когда-либо построили машину Rube Goldberg? Если нет, может быть, вы построили сложную линию домино? Хорошо, может быть, вы не были такими нетехниками, как я. Пусть будет так. Для тех из

Автор оригинала: FreeCodeCamp Community Member.

Михаилом Олоруннисола

Вы когда-нибудь построили машину Rube Goldberg? Если нет, может быть, вы построили сложную линию домино?

Хорошо, может быть, вы не были такими нетехниками, как я. Пусть будет так. Для тех из вас, кто имел удовольствие сделать любое из вышеперечисленного, вы уже схватили сущность современной структуры данных: связанные списки!

Насколько связаны списки

Самая простая форма связанных списков – A Одно связанный список – Это серия узлов, где каждый отдельный узел содержит как значение, так и указатель на следующий узел в списке.

Добавления ( Добавить ) Выращите список, добавляя элементы до конца списка.

Removals ( Удалить) всегда будет удалять из данной позиции в списке.

Поиск ( Содержит ) Поищите список для значения.

Пример использования случаев:

  • Хранение значений в хэш-таблице для предотвращения столкновений (подробнее об этом в нескольких постах)
  • Пересматривать удивительную гонку!

Давайте сохраним эту статью приятную и легкую, работая над инструментом, что сеть CBS может использовать для планирования их следующего удивительного гоночного телевидения.

Когда вы проходите через это, я хочу, чтобы вы продолжали спрашивать себя: «Как связаны списки любые отличные от массивов? Как они похожи?»

Давайте начнем.

Во-первых, вам нужно создать представление нашего связанного списка:

class LinkedList{  constructor(){    this._head = null;    this._tail = null;    this._length = 0;  }
  size(){    return this._length;  }}

Чтобы отслеживать начальную точку и конечную точку гонки, вы создаете свойства головы и хвоста.

Тогда, чтобы убедиться, что вы не делаете гонку слишком долго или слишком коротко в течение сезона, вы создаете свойство свойств и размера длины. Таким образом, вы всегда можете отслеживать точно, как долго гонка.

Теперь, когда у вас есть способ сохранить список гонок, вы должны создать способ добавить в этот список. Вопрос в том, что вы добавляете специально?

Помните, что связанный список – это серия узлов, где каждый узел имеет значение и указатель на следующий узел в списке. Зная это, вы понимаете, что узел – это просто объект со значением и следующим свойством.

Поскольку вы собираетесь создавать новый узел каждый раз, когда вы добавляете в список, вы решите создать конструктор, который облегчает создание нового узла для каждого значения, которое добавляется в ваш список.

class Node{  constructor(value){    this.value = value;    this.next = null;  }}

Наличие этого конструктора доступно, позволяет создавать ваш способ добавления.

class Node {  constructor(value) {    this.value = value;    this.next = null;  }}
class LinkedList {   constructor() {    this._head = null;    this._tail = null;    this._length = 0;  }    add(value) {    let node = new Node(value);         //we create our node    if(!this._head && !this._tail){     //If it's the first node      this._head = node;                //1st node is head & tail      this._tail = node;    }else{    this._tail.next = node;             //add node to the back    this._tail = this._tail.next;       //reset tail to last node    }    this._length++;  }    size() {    return this._length;  }}
const AmazingRace = new LinkedList();AmazingRace.add("Colombo, Sri Lanka");AmazingRace.add("Lagos, Nigeria");AmazingRace.add("Surat, India");AmazingRace.add("Suzhou, China");

Теперь, когда вы добавили этот метод, вы сможете добавить кучу местоположения в ваш удивительный список гонок. Вот как это будет выглядеть. Обратите внимание, что я добавил некоторое дополнительное пробел, чтобы облегчить понять.

{ _head:    { value: 'Colombo, Sri Lanka',     next: { value: 'Lagos, Nigeria',              next: { value: 'Surat, India',                     next: { value: 'Suzhou, China',                             next: null                            }                   }           }    },  _tail: { value: 'Suzhou, China', next: null },  _length: 4 }

Хорошо, теперь, когда вы создали этот список и способ добавить, вы понимаете, что вы хотите, чтобы некоторые помогили добавлять местоположения в этот список, потому что у вас есть децидофия (да, это ).

Вы решили поделиться этим со своим сотрудником, Кент, прося его добавить еще несколько мест. Единственная проблема в том, что когда вы даете ему его, вы не говорите ему, какие места вы уже добавили. К сожалению, вы тоже забыли после страдания амнезии, привлеченные от решения о тревоге.

Конечно, он мог просто запустить Console.log (Amazingrace) и прочитайте, что консольные выходы. Но Кент ленивый программист и нужен способ проверить, существует ли что-то, поэтому он может предотвратить дубликаты. С учетом этого вы построите Содержит Метод для проверки существующих значений.

class Node {  constructor(value) {    this.value = value;    this.next = null;  }}class LinkedList {   constructor() {    this._head = null;    this._tail = null;    this._length = 0;  }    add(value) {    let node = new Node(value);             if(!this._head && !this._tail){           this._head = node;                      this._tail = this._head;    }else{    this._tail.next = node;                 this._tail = this._tail.next;           }    this._length++;  }    contains(value){    let node = this._head;    while(node){      if(node.value === value){        return true;      }      node = node.next;    }    return false;  }    size() {    return this._length;  }  }
const AmazingRace = new LinkedList();AmazingRace.add("Colombo, Sri Lanka");AmazingRace.add("Lagos, Nigeria");AmazingRace.add("Surat, India");AmazingRace.add("Suzhou, China");
//Kent's check
AmazingRace.contains('Suzhou, China'); //trueAmazingRace.contains('Hanoi, Vietnam'); //falseAmazingRace.add('Hanoi, Vietnam');AmazingRace.contains('Seattle, Washington'); //falseAmazingRace.add('Seattle, Washington');AmazingRace.contains('North Pole'); // falseAmazingRace.add('North Pole');

Удивительно, теперь Кент имеет способ проверить значения, прежде чем добавлять их, чтобы избежать дубликатов.

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

Вы даже можете войти и изменить нативные методы на существующие структуры. Попробуйте ниже в reft:

Array.prototype.push = () => { return 'cat';}
let arr = [];arr.push('eggs'); // returns 'cat';

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

Поскольку наш класс связанного списка не является родным для JavaScript, у нас есть больше свободы в реализации, но есть все еще основные ожидания того, как структуры данных, такие как они должны функционировать. Связанные списки не придерживаются уникальных значений. Но у них есть методы, такие как Содержит Это позволяет нам предварительно проверить и поддерживать уникальность в нашем списке.

Кент возвращается к вам со списком пунктов назначения, но некоторые из них сомнительны. Например, северный полюс не может быть лучшим удивительным местом гонки.

Таким образом, вы решите построить метод для удаления узла. Важно помнить, что, как только вы удалите узел, вы ненимаете список, и придется повторно связать, что произошло до и после удаленного узла.

class Node {  constructor(value) {    this.value = value;    this.next = null;  }}class LinkedList {   constructor() {    this._head = null;    this._tail = null;    this._length = 0;  }    add(value) {    let node = new Node(value);             if(!this._head && !this._tail){           this._head = node;                      this._tail = this._head;    }else{    this._tail.next = node;                 this._tail = this._tail.next;           }    this._length++;  }    remove(value) {    if(this.contains(value)){          // see if our value exists      let current = this._head;           // begin at start of list      let previous = this._head;        while(current){                   // check each node          if(current.value === value){            if(this._head === current){   // if it's the head              this._head = this._head.next;  // reset the head              this._length--;              // update the length              return;                      // break out of the loop            }            if(this._tail === current){   // if it's the tail node              this._tail = previous;       // make sure to reset it            }            previous.next = current.next;  // unlink (see img below)            this._length--;            // update the length            return;                    // break out of           }          previous = current;          // look at the next node          current = current.next;      // ^^        }     }    }      contains(value){    let node = this._head;    while(node){      if(node.value === value){        return true;      }      node = node.next;    }    return false;  }    size() {    return this._length;  }  }
const AmazingRace = new LinkedList();AmazingRace.add("Colombo, Sri Lanka");AmazingRace.add("Lagos, Nigeria");AmazingRace.add("Surat, India");AmazingRace.add("Suzhou, China");AmazingRace.add('Hanoi, Vietnam');AmazingRace.add('Seattle, Washington');AmazingRace.add('North Pole');
//Kent's check
AmazingRace.remove('North Pole');

В этом есть много кода Удалить функционировать там. По сути, это сводится к следующему:

  1. Если значение существует в списке …
  2. ИТЕРТАПРАВЛЯЕТСЯ СЛЕДУЮЩИЙ СПИСОК, отслеживание предыдущего и текущего узла
  3. Затем, если есть матч →

4а. Если это голова

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

4b Если это хвост

  • Сбросьте хвост к предыдущему узлу в списке
  • Отсоединить узел, сбросив указатели, как показано ниже

4с Если это не совпадение → Продолжить итерацию

  • Сделайте следующий узел тока
  • Сделайте текущий узел предыдущим

Последнее следует отметить: вы, возможно, поняли, что вы на самом деле не удалили узел. Вы только что удалили ссылки на него. Ну, это нормально, потому что, как только все ссылки на объект удалены, сборщик мусора помогает нам удалить его из памяти. Вы можете прочитать в коллекции мусора здесь Отказ

С помощью метода удаления теперь реализован, вы можете запустить этот маленький кусок кода ниже, чтобы убедиться, что участники не замерзают до смерти или случайно беспокоили Санта, когда он готовятся к праздникам этого года.

AmazingRace.remove('North Pole');

Вы сделали это! Вы создали простую реализацию связанного списка. И вы можете вырастить список, добавляя элементы и сокращаю его, удаляя элементы – все на основании значения элемента.

Посмотрите, можете ли вы добавить, вы можете расширить связанный список, чтобы позволить вам вставлять значения в начале, конец или любую точку между ними.

У вас есть все, что вам нужно для реализации этих методов. Имена и аргументы для этих методов должны выглядеть немного подобно:

addHead(value) {
}
insertAfter(target, value){
}

Не стесняйтесь делиться своими реализациями в комментариях ниже?

Анализ сложности времени на методы очереди

Вот код снова:

class LinkedList {   constructor() {    this._head = null;    this._tail = null;    this._length = 0;  }    add(value) {    let node = new Node(value);             if(!this._head && !this._tail){           this._head = node;                      this._tail = this._head;    }else{    this._tail.next = node;                 this._tail = this._tail.next;           }    this._length++;  }    remove(value) {    if(this.contains(value)){                let current = this._head;              let previous = this._head;        while(current){                   if(current.value === value){            if(this._head === current){               this._head = this._head.next;              this._length--;                            return;                                  }            if(this._tail === current){               this._tail = previous;                }            previous.next = current.next;            this._length--;                        return;                              }          previous = current;                    current = current.next;              }     }    }     contains(value){    let node = this._head;    while(node){      if(node.value === value){        return true;      }      node = node.next;    }    return false;  }    size() {    return this._length;  }
// To Be Implemented
addHead(value) {
}
insertAfter(target, value){
}

Добавить это O (1): Поскольку вы всегда знаете последний элемент в списке благодаря свойству HAST, вам не нужно повторять список.

Удалить это O (n): В худшем случае вам придется повторять весь список, чтобы найти значение, которое нужно удалить. Большая часть, хотя это фактическое удаление узла – это o (1), потому что вы просто сбрасываете указатели.

Содержит это O (n): Вы должны итереть во всем списке, чтобы проверить, существует ли значение в вашем списке.

Adddead это O (1): Аналогично нашему способу добавления выше, мы всегда знаем позицию головы, поэтому итерация не требуется.

internatafter это O (n) : Подобно нашему способу удаления выше, вам придется повторять весь список, чтобы найти целевой узел, который должен быть вставлен ваше значение. Аналогично, фактическая вставка – это o (1), потому что вы просто сбрасываете указатели.

Связанный список против массива?

Почему вы используете связанный список вместо массивов? Массивы технически позволяют вам делать все связанные списки, такие как дополнения, вставки и удаления. Кроме того, все эти методы уже доступны для нас в JavaScript.

Ну, самая большая разница находится в вставках и удалении. Поскольку массивы индексируются, когда вы выполняете вставку или удаление в середине массива, вы должны сбросить положение всех следующих значений в их новые показатели.

Представьте себе вставку в начало или середине массива 100 000 значений долго! Вставки и удаления, как это чрезвычайно дороги. Из-за этого связанные списки часто предпочтительны для больших наборов данных, которые часто смещаются вокруг.

С другой стороны, массивы великолепны, когда речь идет о нахождении предметов (произвольный доступ), поскольку они проиндексированы. Если вы знаете позицию элемента, вы можете получить доступ к нему в O (1) время через Массив [Позиция] Отказ

Связанные списки всегда требуют, чтобы вы последовали последовательно переписывают связанные списки. Учитывая это, массивы обычно предпочтительны для небольших наборов данных, либо наборы данных, которые не смещаются так часто.

Время для быстрого повторения

Связанные списки:

  1. иметь свойство хвоста и головки для отслеживания концов списка
  2. Имейте добавить, addead, inslibafter и удаление метода для управления содержимым вашего списка
  3. иметь свойство длины, чтобы отслеживать, как долго ваш связанный список

Дальнейшее чтение

Существует также вдвойне связанный список и циркулярные структуры данных списка списка. Вы можете прочитать о них На Википедии Отказ

Кроме того, вот сплошное, быстрое Обзор Vivek Kumar.

Наконец, Ян Эллиот написал прогулка Это помогает вам реализовать все методы. Но посмотрите, можете ли вы реализовать Adddead () и internatafter () Для вашего связанного списка, прежде чем заглядывать на это?