написать впереди
В этой статье мы сначала обсудим, что такое связанный список и что такое связанный список в JavaScript, затем мы будем использовать язык JavaScript для реализации дизайна различных типов связанных списков, и, наконец, мы зададим несколько общих вопросов и ответим на них из различных Короче говоря, цель состоит в том, чтобы полностью получить связанный список
Получив концепцию, мы можем выбрать классификацию связанных списков и завершить их в соответствии со степенью сложности. Название связанного списка относительно простое. Если вы полностью прочитали дизайн связанного списка в этой статье, по крайней мере, вы можете легко избавиться от него.Вопросов 20, а количество вопросов со связанным списком относительно невелико.Всего около 50 вопросов, и есть десять вопросов, которые требуют членов, то есть, если вы напишите 40 вопросов, вы можете получить предварительное представление о структуре данных связанного списка. Если вы не хотите находить Вопросы, можно отсортировать по порядку в моем репозитории алгоритмов GitHub. Если у вас есть вопросы или концепции, которые вы не знаете Не понял, вы можете прочитать решения, которые я написал. Параллельно я также записал видео. В конце статьи есть ссылка, поэтому приступим к изучению связанного списка.
Что такое связанный список
Обычно мы хотим хранить в программе несколько элементов. Массив, вероятно, наиболее часто используемая структура данных. Массив - очень удобная структура данных. Это можно сделать даже очень простым способом, а именно[]
этот синтаксис для доступа к его элементам
Связный список также хранит упорядоченный набор элементов, но в отличие от массивов элементы в связанном списке не являются смежными в памяти.Каждый элемент состоит из узла, в котором хранится сам элемент и ссылка на следующий элемент (также может называться указатель) композиция
Далее рассмотрим структуру данных массива.У нее есть недостаток.В большинстве языков размер массива фиксированный.Вставка или удаление элементов из начала или середины массива накладно,потому что элементы нужно перемещать , как показано на следующем рисунке.
Приведенный выше массив удаляет элемент с индексом 2 и значением 3, тогда нам нужно сначала удалить элемент 3, потому что элемент с индексом 2 и значением 3 удаляется, а индекс 2 пуст, поэтому тогда , нам нужно также удалить элемент по индексу 3. То есть элемент 4 сдвигается вперед на одно место, и в то же время следующий за ним элемент 5 также нужно перемещать вперед на одно место.Вставка элемента в массив также то же самое.Пока в массиве на одну позицию меньше или на одну больше, следующие элементы должны быть перемещены вперед или назад на одну позицию по очереди, поэтому возможно, что при большой длине массива эффективность вставки и удаления будут постепенно уменьшаться
Давайте снова посмотрим на связанный список
То же самое относится и к удалению элемента 3. Связанному списку нужно только выполнить итерацию к узлу со значением 3 и указать узел 2 на узел 4. Если узел 3 не имеет отношения ссылки, он будет рассматриваться как мусор механизмом сборки мусора, даже когда узел очень во многих случаях удалить элемент все равно можно только изменив ссылочное отношение, при этом вставка элемента реверсируется, то есть сначала разъединяются элементы по обе стороны от позиции вставки, а затем предыдущая элемент указывает на вставленный элемент, а вставленный элемент указывает на следующий элемент, то есть чем больше элементов, тем эффективнее будет массив сравнения.
По сравнению с традиционными массивами одним из преимуществ связанных списков является то, что вам не нужно перемещать другие элементы при добавлении или удалении элементов, но в массиве мы можем напрямую обращаться к любому элементу в любой позиции, что невозможно в связанном списке. список, потому что в связанном списке каждый узел имеет ссылку только на следующий узел, поэтому, если вы хотите получить доступ к элементу в середине связанного списка, вы должны выполнить итерацию связанного списка с начальной точки (головной узел узла). связанный список) до тех пор, пока не найдете нужный элемент, требующий внимания
Связанные списки в JavaScript
Выше мы кратко представили концепцию обычного связанного списка, но как мы можем представить связанный список на языке JavaScript?
Поскольку в JS нет встроенной структуры данных связанного списка, нам нужно использовать объекты для имитации реализации связанных списков, так же, как мы представили связанный список выше, на самом деле это односвязный список, в дополнение к двусвязному списку. , круговой связанный список и т. д. Мы будем вводить их один за другим и использовать JavaScript для реализации следующих
Односвязный список
Давайте сначала посмотрим на базовый односвязный список.Каждый элемент односвязного списка состоит из узла, в котором хранится сам элемент, и указателя на следующий элемент, как показано на следующем рисунке.
Чтобы реализовать структуру данных связанного списка, ключом является сохранение элемента head (то есть элемента head связанного списка) и следующего указателя каждого элемента, С помощью этих двух частей мы можем легко перемещаться по связанному списку чтобы управлять всеми элементами.Вы можете поместить связанный список.Подумайте об этом как о железной цепи.Каждый узел в цепочке связан друг с другом.Нам нужно только найти главу цепочки,и можно найти всю цепочку. Итак, как смоделировать односвязный список в JS?, идем пошагово
Прежде всего нам нужно создать класс.Функция этого класса состоит в описании узлов связанного списка.Это очень просто.Ему нужно всего два атрибута,один используется для сохранения значения этого узла,а other используется для сохранения значения, указывающего на следующий node.pointer, как показано ниже.
/**
* @description: 创建链表单节点类
* @param {*} val 节点值
* @return {*}
*/
function ListNode(val) {
this.val = val
this.next = null
}
Далее нам нужно написать класс связанного списка, где свойство length представляет длину связанного списка, а свойство head представляет головной узел связанного списка.
/**
* @description: 创建链表类
* @param {*}
* @return {*}
*/
function LinkedList() {
this.length = 0
this.head = null
}
Давайте подумаем об этом, так как мы моделируем класс связанного списка, мы должны поместить в этот класс все функции, которые он может использовать, такие как массивы сpush/splice/indexOf/...
Мы также должны иметь эти полезные методы в связанном списке.Давайте сначала тщательно подумаем, какие практические функции или методы добавить в связанный список.Сначала мы создадим базовый скелет.Я перечислил здесь много.Давайте реализуем их один на один. вниз, добро пожаловать, чтобы добавить
// 向链表中追加节点
LinkedList.prototype.append = function (val) { }
// 在链表的指定位置插入节点
LinkedList.prototype.insert = function (index, val) { }
// 删除链表中指定位置的元素,并返回这个元素的值
LinkedList.prototype.removeAt = function (index) { }
// 删除链表中对应的元素
LinkedList.prototype.remove = function (val) { }
// 获取链表中给定元素的索引
LinkedList.prototype.indexOf = function (val) { }
// 获取链表中某个节点
LinkedList.prototype.find = function (val) { }
// 获取链表中索引所对应的元素
LinkedList.prototype.getElementAt = function (index) { }
// 判断链表是否为空
LinkedList.prototype.isEmpty = function () { }
// 获取链表的长度
LinkedList.prototype.size = function () { }
// 获取链表的头元素
LinkedList.prototype.getHead = function () { }
// 清空链表
LinkedList.prototype.clear = function () { }
// 序列化链表
LinkedList.prototype.join = function (string) { }
getElementAt(index)
Давайте сначала получим элемент, соответствующий индексу в связанном списке.getElementAt
метод и получить элемент связанного списка через значение узла, а именноfind
метод, потому что они будут использоваться много раз позже, скажем,getElementAt
метод, выше мы сказали, что если мы хотим найти элемент, мы должны выполнить итерацию с самого начала, поэтому мы можем выполнять итерацию непосредственно по входящему индексу
Сначала определитесь с параметрамиindex
границ, если значение выходит за границы индекса (меньше 0 или большеlength - 1
), затем вернутьсяnull
, начинаем со связанного спискаhead
Начните с узла, пройдитесь по всему связанному списку, пока не найдете узел, соответствующий позиции индекса, а затем вернитесь к этому узлу, не правда ли, это очень просто? Как и все упорядоченные наборы данных, индекс связанного списка также начинается с 0. Пока есть головной узел связанного списка, вы можете пройти, чтобы найти элемент в позиции индекса, поэтому мы вызываем его в конструктор то естьLinkedList
класс сохраненhead
стоимость
// 获取链表中索引所对应的元素
LinkedList.prototype.getElementAt = function (index) {
if (index < 0 || index >= this.length) return null
let cur = this.head
while (index--) {
cur = cur.next
}
return cur
}
find(val)
find
Методы иgetElementAt
Метод аналогичен, один находит элементы по индексу, а другой находит элементы по значению узла, поэтому мы можем выполнять итерацию и сравнивать напрямую.
// 获取链表中某个节点
LinkedList.prototype.find = function (val) {
let cur = this.head
while (cur) {
if (cur.val == val) return cur
cur = cur.next
}
return null
}
append(val)
имеютgetElementAt
После метода мы можем легко реализоватьappend
метод, функция этого метода заключается в добавлении элементов в конец связанного списка
Этот метод передается в значении, мы можем передать указанный выше конструкторListNode
чтобы создать новый узел
Затем нам нужно рассмотреть, если связанный списокhead
дляnull
, эта ситуация указывает на то, что связанный список пуст, поэтому необходимоhead
Узел указывает на вновь добавленный элемент, чтобы убедиться, что головной узел сохранен.Если он не пуст, мы передаемgetElementAt
Метод находит последний узел связанного списка, а индекс последнего узла — это длина связанного списка, который мы сохранили в конструкторе.length
Атрибут минус 1, затем последний узелnext
Указатель указывает на вновь добавленный элемент
недавно добавленный элементnext
Указатель по умолчаниюnull
, последний элемент связанного спискаnext
значениеnull
, кроме того, после присоединения узла к связному списку необходимо прибавить 1 к длине связанного списка, чтобы убедиться, чтоlength
Атрибут равен длине связанного списка, как показано ниже.
// 向链表中追加节点
LinkedList.prototype.append = function (val) {
let node = new ListNode(val)
if (!this.head) {
this.head = node
} else {
let cur = this.getElementAt(this.length - 1)
cur.next = node
}
this.length++
}
insert(index, val)
Далее будем реализовыватьinsert
метод, то есть добавление узла в любую позицию в связанном списке
Чтобы вставить элемент в указанную позицию, в первую очередь нам еще нужно оценить входящийindex
Не выходит ли индекс за пределы
Тогда рассмотрим два случая
когдаindex
Когда значение равно 0, это означает, что новый узел должен быть вставлен в начало связанного списка, аnext
указатель на настоящееhead
, затем обновитеhead
Значение вновь вставленного узла может быть, как показано на следующем рисунке.
когдаindex
Когда значение не равно 0, то есть вставленный узел находится в середине или конце связанного списка, мы сначала находим предыдущий узел в позиции, которую нужно вставить.prevNode
, затем поместите новый узелnewNode
изnext
указатель наprevNode
изnext
соответствующий узел, а затемprevNode
изnext
указатель наnewNode
, чтобы новый узел был вставлен в связанный список.Когда вставленный узел находится в конце связанного списка, этот метод также применим, как показано на следующем рисунке.
Наконец, мы вставили узел, и нам также нужно добавить длину связанного списка вlength
Добавьте 1 к длине, код выглядит следующим образом
// 在链表的指定位置插入节点
LinkedList.prototype.insert = function (index, val) {
if (index < 0 || index > this.length) return false
let node = new ListNode(val)
if (index === 0) {
node.next = this.head
this.head = node
} else {
let prev = this.getElementAt(index - 1)
node.next = prev.next
prev.next = node
}
this.length++
return true
}
removeAt(index)
Точно так же мы можем легко написатьremoveAt
Способ удаления узла в указанном положении в связанном списке
Тем не менее, сначала оцените входящиеindex
Не выходит ли индекс за пределы
или в двух случаях
Если удаляемый узел находится в начале связанного списка,head
Просто перейдите к следующему узлу.Если текущий связанный список имеет только один узел, то следующий узелnull
, тоhead
Указание на следующий узел эквивалентно размещениюhead
установлен вnull
, связанный список пуст после удаления
Если удаляемый узел находится в середине связанного списка, нам нужно выяснитьindex
предыдущий узел на месте, поместите егоnext
указатель наindex
Следующий узел в локации.Короче говоря, чтобы удалить узел, вам нужно всего лишь изменить указатель соответствующего узла, отключить соседние узлы слева и справа от удаленного местоположения, а затем снова подключить его.
Удаленный узел не имеет отношения ссылки, и механизм сборки мусора JavaScript справится с этим.Что касается механизма сборки мусора, то он также не входит в рамки этой статьи.Если вы его знаете, удалите элемент узла.Нам также нужно уменьшить длина связанного списка на 1. Окончательный код выглядит следующим образом
// 删除链表中指定位置的元素,并返回这个元素的值
LinkedList.prototype.removeAt = function (index) {
if (index < 0 || index >= this.length) return null
let cur = this.head
if (index === 0) {
this.head = cur.next
} else {
let prev = this.getElementAt(index - 1)
cur = prev.next
prev.next = cur.next
}
this.length--
return cur.val
}
indexOf(val)
Получить индекс данного элемента в связанном списке, это относительно просто, вы можете выполнить итерацию напрямую, если он совпадает, вернуть соответствующий индекс, если он не совпадает, вернуть -1
// 获取链表中给定元素的索引
LinkedList.prototype.indexOf = function (val) {
let cur = this.head
for (let i = 0; i < this.length; i++) {
if (cur.val === val) return i
cur = cur.next
}
return -1
}
remove(val)
Удалить соответствующий элемент в связанном списке, с предыдущим предзнаменованием, здесь это относительно просто, мы можем напрямую использоватьindexOf
метод, чтобы получить соответствующий индекс, а затем использоватьremoveAt
метод удаления узла
// 删除链表中对应的元素
LinkedList.prototype.remove = function (val) {
let index = this.indexOf(val)
return this.removeAt(index)
}
isEmpty()
Чтобы судить, пуст ли связанный список, нам нужно только оценить длину связанного списка.length
Подождите, пока он не будет равен 0
// 判断链表是否为空
LinkedList.prototype.isEmpty = function () {
return !this.length
}
size()
Какой длина списка получаетсяlength
// 获取链表的长度
LinkedList.prototype.size = function () {
return this.length
}
getHead()
Получить главный элемент связанного списка и вернутьсяhead
характеристики
// 获取链表的头元素
LinkedList.prototype.getHead = function () {
return this.head
}
clear()
Чтобы очистить связанный список, нам нужно толькоhead
пусто, то пустьlength
Равно 0, просто подождите, пока механизм сборки мусора переработает неиспользуемый отброшенный связанный список.
// 清空链表
LinkedList.prototype.clear = function () {
this.head = null
this.length = 0
}
join(string)
Сериализация связанного списка означает вывод связанного списка в указанном формате, аналогичном формату массива.join
Метод, это облегчить наше тестирование
// 序列化链表
LinkedList.prototype.join = function (string) {
let cur = this.head
let str = ''
while (cur) {
str += cur.val
if (cur.next) str += string
cur = cur.next
}
return str
}
Итак, на данный момент наш класс односвязного списка разработан, приходите и тестируйте его, мы вводим следующий код для тестирования
let linkedList = new LinkedList()
linkedList.append(10)
linkedList.append(20)
linkedList.append(30)
console.log(linkedList.join("--"))
linkedList.insert(0, 5)
linkedList.insert(2, 15)
linkedList.insert(4, 25)
console.log(linkedList.join("--"))
console.log(linkedList.removeAt(0))
console.log(linkedList.removeAt(1))
console.log(linkedList.removeAt(2))
console.log(linkedList.join("--"))
console.log(linkedList.indexOf(20))
linkedList.remove(20)
console.log(linkedList.join("--"))
console.log(linkedList.find(10))
linkedList.clear()
console.log(linkedList.size())
Окончательный вывод выглядит следующим образом
// 10--20--30
// 5--10--15--20--25--30
// 5
// 15
// 25
// 10--20--30
// 1
// 10--30
// ListNode { val: 10, next: ListNode { val: 30, next: null } }
// 0
В приведенном выше коде отсутствует проверка некоторых параметров, но нам достаточно изучить, дополнить код и прикрепить ссылку в конце текста.
Двусвязный список
Мы говорили об односвязном списке выше, а теперь поговорим о двусвязном списке, так что же такое двусвязный список? На самом деле, вы можете услышать название, каждый элемент в односвязном списке имеет только одинnext
Указатель, используемый для указания на следующий узел, мы можем пройти весь связанный список только из головы связанного списка, любой узел может найти только его следующий узел, но не его предыдущий узел, каждый элемент в двусвязном списке имеет два указателя, один используется для указания на следующий узел, а другой используется для указания на предыдущий узел.В двусвязном списке, в дополнение к обходу из головы, как в односвязном списке, его также можно пройти из хвост, как показано на следующем рисунке
С односвязным списком мы сначала создаем класс узла связанного списка, разница в том, что ему нужен еще одинprev
атрибут используется для указания на предыдущий узел
/**
* @description: 创建双向链表单节点类
* @param {*} val 节点值
* @return {*}
*/
function ListNode(val) {
this.val = val
this.next = null
this.prev = null
}
Двусвязный список похож на односвязный список с добавлением еще одного хвостового узла.tail
/**
* @description: 创建双向链表类
* @param {*}
* @return {*}
*/
function DoubleLinkedList() {
this.length = 0
this.head = null
this.tail = null
}
Далее реализуем метод-прототип двусвязного списка
getElementAt(index)
Во-первых, получить элемент, соответствующий индексу в двусвязном списке.Поскольку двусвязный список можно итеративно искать в обоих направлениях, здесьgetElementAt
метод, который мы можем оптимизировать, когда индекс больше, чем длина связанного спискаlength/2
, мы можем искать сзади вперед, в противном случае мы можем искать спереди назад, чтобы элемент узла можно было найти быстрее
// 获取双向链表中索引所对应的元素
DoubleLinkedList.prototype.getElementAt = function (index) {
if (index < 0 || index >= this.length) return null
let cur = null
if(index > Math.floor(this.length / 2)){
// 从后往前
cur = this.tail
let i = this.length - 1
while (i > index) {
cur = cur.prev
i--
}
}else{
// 从前往后
cur = this.head
while (index--) {
cur = cur.next
}
}
return cur
}
find(val)
find
Методы иgetElementAt
Метод аналогичен,getElementAt
метод можно оптимизировать, тоfind
Его также можно оптимизировать после того, как он станет двусвязным списком. Мы думаем, что, поскольку его можно повторять в обоих направлениях, не будет ли нам быстрее выполнять итерацию с обеих сторон одновременно. В случае двунаправленной итерации, весь связанный список будет повторяться только тогда, когда его невозможно найти, что более эффективно.
// 获取双向链表中某个节点
DoubleLinkedList.prototype.find = function (val) {
let curHead = this.head
let curTail = this.tail
while (curHead) {
if (curHead.val == val) return curHead
curHead = curHead.next
if (curTail.val == val) return curTail
curTail = curTail.prev
}
return null
}
append(val)
Снова вернитесь к нашему элементу узла добавления, все еще есть некоторая разница между добавлением двусвязного списка и односвязным списком.
Когда связанный список пуст, помимоhead
Указание на текущий добавленный узел, но также и наtail
также указывает на текущий добавляемый узел
Когда связанный список не пуст, непосредственноtail
изnext
Указывает на текущий узел, который нужно добавитьnode
, затем изменитеnode
изprev
указать на старыйtail
,Последний отзывtail
для вновь добавленного узла
Операция добавления двусвязного списка не требует обхода всего связанного списка с самого начала.tail
Хвост связного списка можно найти напрямую, что удобнее, чем работа с односвязным списком.length
Добавьте 1 к значению, чтобы изменить длину связанного списка.
// 向双向链表中追加节点
DoubleLinkedList.prototype.append = function (val) {
let node = new ListNode(val)
if (this.head === null) {
// 链表为空,head 和 tail 都指向当前添加的节点
this.head = node
this.tail = node
}
else {
// 链表不为空,将当前节点添加到链表的尾部
this.tail.next = node
node.prev = this.tail
this.tail = node
}
this.length++
}
insert(index, val)
Далее способ вставки узловых элементов, идея такая же, это не сложно, обращаем внимание наtail
а такжеprev
Указатели обсуждаются по ситуации, после вставки добавляем к длине 1.
// 在双向链表的指定位置插入节点
DoubleLinkedList.prototype.insert = function (index, val) {
if (index < 0 || index > this.length) return false
// 插入到尾部
if (index === this.length) {
this.append(val)
} else {
let node = new ListNode(val)
if (index === 0) { // 插入到头部
if (this.head === null) {
this.head = node
this.tail = node
} else {
node.next = this.head
this.head.prev = node
this.head = node
}
} else { // 插入到中间位置
let curNode = this.getElementAt(index)
let prevNode = curNode.prev
node.next = curNode
node.prev = prevNode
prevNode.next = node
curNode.prev = node
}
this.length++
}
return true
}
removeAt(index)
Удалить элемент в указанной позиции в двусвязном списке, также обратите вниманиеtail
а такжеprev
Указатели обсуждаются по регистру, а длина может быть уменьшена на 1 после последнего удаления.
// 删除双向链表中指定位置的元素,并返回这个元素的值
DoubleLinkedList.prototype.removeAt = function (index) {
if (index < 0 || index >= this.length) return null
let current = this.head
let prevNode
if (index === 0) { // 移除头部元素
this.head = current.next
this.head.prev = null
if (this.length === 1) this.tail = null
} else if (index === this.length - 1) { // 移除尾部元素
current = this.tail
this.tail = current.prev
this.tail.next = null
} else { // 移除中间元素
current = this.getElementAt(index)
prevNode = current.prev
prevNode.next = current.next
current.next.prev = prevNode
}
this.length--
return current.val
}
indexOf(val)
Найдите индекс элемента в двусвязном списке с указанным вышеfind
Метод заключается в том, чтобы проложить путь, здесь все просто, идея та же,
// 获取双向链表中给定元素的索引
DoubleLinkedList.prototype.indexOf = function (val) {
let curHead = this.head
let curTail = this.tail
let idx = 0
while (curHead !== curTail) {
if (curHead.val == val) return idx
curHead = curHead.next
if (curTail.val == val) return this.length - 1 - idx
curTail = curTail.prev
idx++
}
return -1
}
join(string)
Сериализованный связанный список может по-прежнему соответствовать односвязному списку выше.
// 序列化双向链表
DoubleLinkedList.prototype.join = function (string) {
let cur = this.head
let str = ''
while (cur) {
str += cur.val
if (cur.next) str += string
cur = cur.next
}
return str
}
Мы так много ввели про двусвязный список, а остальные методы относительно просты, поэтому я не буду вдаваться в подробности Полный код в случае двусвязного списка в конце статьи
Аналогично, давайте просто проверим, правда это или нет.
let doubleLinkedList = new DoubleLinkedList()
doubleLinkedList.append(10)
doubleLinkedList.append(15)
doubleLinkedList.append(20)
doubleLinkedList.append(25)
console.log(doubleLinkedList.join("<->"))
console.log(doubleLinkedList.getElementAt(0).val)
console.log(doubleLinkedList.getElementAt(1).val)
console.log(doubleLinkedList.getElementAt(5))
console.log(doubleLinkedList.join("<->"))
console.log(doubleLinkedList.indexOf(10))
console.log(doubleLinkedList.indexOf(25))
console.log(doubleLinkedList.indexOf(50))
doubleLinkedList.insert(0, 5)
doubleLinkedList.insert(3, 18)
doubleLinkedList.insert(6, 30)
console.log(doubleLinkedList.join("<->"))
console.log(doubleLinkedList.find(10).val)
console.log(doubleLinkedList.removeAt(0))
console.log(doubleLinkedList.removeAt(1))
console.log(doubleLinkedList.removeAt(5))
console.log(doubleLinkedList.remove(10))
console.log(doubleLinkedList.remove(100))
console.log(doubleLinkedList.join("<->"))
Вывод приведенного выше кода выглядит следующим образом
// 10<->15<->20<->25
// 10
// 15
// null
// 10<->15<->20<->25
// 0
// 3
// -1
// 5<->10<->15<->18<->20<->25<->30
// 10
// 5
// 15
// null
// 10
// null
// 18<->20<->25<->30
Ну ошибки нет, простое сравнение правильное, Нет Проблем
круговой связанный список
Давайте посмотрим на другой связанный список, циклический связанный список.Как следует из названия, хвостовой узел циклического связанного списка указывает на его собственный головной узел.
Циклический связанный список имеет односторонний циклический связанный список, а также может иметь двусторонний циклический связанный список, как показано ниже.
Список цепочек с одним двойным кольцом Здесь мы не будем писать один, вы можете попробовать сами, сравните вершину нашего аннигиляции, вам нужно только обратить внимание на нижний хвостовой узел, чтобы указать на головной узел
Почему бы не встроенный список JavaScript?
Согласно тому, что мы сказали выше, связанный список имеет так много преимуществ, так почемуJavaScript
Разве в этом языке нет встроенной структуры данных, такой как связанный список?
На самом деле в JS массивы реализуют почти все функции связанных списков, так что не стоит лишний раз заморачиваться.Вы можете растеряться, услышав это.Вышесказанное не говорит о том, что производительность массивов в некоторых случаях (таких как вставка головы и т. д.) А не связанный список?
Давайте поговорим с фактами, теперь мы используем класс односвязного списка, завершенный выше.LinkedList
, выполните простой тест времени с собственным массивом
let linkedList = new LinkedList()
let arr = []
// 测试 分别尝试 「总数100 插入节点50」/「总数100000 插入节点50000」
let count = 100
let insertIndex = 50
// let count = 100000
// let insertIndex = 50000
console.time('链表push操作')
for (let i = 0; i < count; i++) {
linkedList.append(i)
}
console.timeEnd('链表push操作')
console.time('数组push操作')
for (let i = 0; i < count; i++) {
arr.push(i)
}
console.timeEnd('数组push操作')
console.time('链表insert操作')
linkedList.insert('test节点', insertIndex)
console.timeEnd('链表insert操作')
console.time('数组insert操作')
arr.splice(insertIndex, 0, 'test节点')
console.timeEnd('数组insert操作')
console.time('链表remove操作')
linkedList.removeAt(insertIndex)
console.timeEnd('链表remove操作')
console.time('数组remove操作')
arr.splice(insertIndex, 1)
console.timeEnd('数组remove操作')
Давайте посмотрим на результаты
Добавить 100 данных, вставить элемент с индексом 50 и удалить вставленный элемент
Добавить 100000 данных, вставить элемент с индексом 50000 и удалить вставленный элемент
какие? ? ? ? ? ?
Из результатов теста видно, что производительность нативного массива по-прежнему подавляет связный список, независимо от того, составляет ли база всего 100 или больше 100 000.
То есть, если эффективность связанного списка выше, чем у массива, в JS его нет. Добавьте промежуточные элементы,console.time
Взгляните на время, вы обнаружите, что используемое время не имеет ничего общего с длиной массива, а это означает, что массив JS соответствует требованиям эффективности связанного списка.
И в массиве мы также можем использоватьsplice()
Метод добавляет и удаляет элементы в указанную позицию массива.После тестирования требуемое время также не зависит от длины массива, и он также может удовлетворять требованиям связанного списка, а индекс массива может полностью заменить значение связанного списка.head,tail,next,prev
и другие методы, и это удобнее в большинстве случаев, и сценариев использования структуры данных связного списка в работе не так уж много, поэтому можно сказать, что массивы в JS — это полностью связанные списки.
Конечно, это ограничивается только языком JavaScript, который связан с механизмом реализации внутреннего массива JS.На самом деле массив в JS просто называется массивом, что отличается от концепции массива в обычных языках. Итак, что касается концепции массива и его внутренней реализации, это не входит в рамки нашего обсуждения в этой главе.Позвольте мне сначала оставить вопрос.Я напишу еще одну статью о массивах JS, когда у меня будет время через несколько дней.На самом деле , лучше всего найти ответ самому.Мы говорим, что JS интерпретирующий тип.Язык высокого уровня, его базовая реализация не так проста, как нам кажется, следуя правилам, немного нарушая правила
Здесь вы можете жаловаться, что эта статья наконец закончена, и теперь вы говорите мне, что она бесполезна. . . Не волнуйся, убери тухлые яйца
Связанный список бесполезен в JavaScript?
Как мы сказали выше, этоJavaScript
Действительно ли связанный список в нем бесполезен?
На самом деле не так, как один из трех волшебных оружийReact
серединаFiber
Структура использует структуру данных связанного списка
Fiber
значит на английском纤维化
, то есть доработка, которая уточняет задачу. Она делит трудоемкую задачу на множество маленьких частей. Время выполнения каждой маленькой части очень короткое. Хотя общее время все еще очень велико, после выполнения каждой маленькой части, она будет передана другим задача имеет шанс на выполнение, так что единственный поток не будет монополизирован, а другие задачи все еще имеют шанс на выполнение,React
серединаFiber
положить весьVDOM
Фрагментация процесса обновления
доReact
серединаrender()
метод получит虚拟DOM
объект и реальный容器DOM
так как虚拟DOM
Основная функция узла монтирования после завершения рендеринга —虚拟DOM
Рендеринг真实DOM
И монтировать его под контейнер.Этот метод выполняет рекурсивную операцию при обновлении.Если в процессе обновления нужно обновить большое количество узлов, то основной поток JS будет занят надолго, и весь рекурсивный процесс не может быть Прервано, потому что поток JS и поток графического интерфейса являются взаимоисключающими (см. 👉«Хардкорный JS» понимает механизм работы JS одновременно), поэтому в случае большого количества обновлений вы можете увидеть некоторое отставание в интерфейсе
Fiber
Архитектура фактически решает две задачи, одна — обеспечить выполнение задачи при бездействии браузера, а другая — фрагментировать задачу, о ней мы и поговорим далее.Fiber
В JS есть экспериментальный методrequestIdleCallback(callback)
, он может передаваться в функцию обратного вызова, а функция обратного вызова может получатьdeadline
объект, через объектtimeRemaining()
Метод может получить время простоя текущего браузера.Если время простоя есть, можно выполнить короткую задачу.Если времени недостаточно, продолжитьrequestIdleCallback
, подождите, пока у браузера не будет времени простоя, а затем снова запустите его, чтобы браузер мог выполняться, когда он бездействует.
но虚拟DOM
Это древовидная структура. Когда задача прерывается, древовидная структура не может возобновить предыдущую задачу и продолжить выполнение, поэтому необходима новая структура данных, то есть наш связанный список. Связанный список может содержать несколько указателей.Fiber
Используемый связанный список содержит три указателя,parent
своему родителюFiber
узел,child
Указывая на своего сынаFiber
узел,sibling
указать на своего братаFiber
узел, аFiber
Узел соответствует узлу задачи, так что следующий узел можно легко найти, а затем можно возобновить выполнение задачи.
Этот простой абзац известенFiber
Архитектура, значит, вы говорите, что связанный список полезен?
Сказав так много, на самом деле, для обычных нужд наш JS не нуждается в использовании связанных списков, и массивы могут полностью его разнести, но в особых случаях связанные списки имеют свои определенные преимущества.Сейчас мы используем JS для объяснения связанных списки, но другие обычные языки не такие мощные, как массивы в JS, и если мы изучим связанные списки, мы будем более удобными, когда будем изучать древовидную структуру дальше.
наконец
Полный код корпуса в тексте следующий 👇
Одно- и двухсвязный список DEMO
В этой статье представлен связный список, одна из структур данных, в качестве небольшого знания перед очисткой связанного списка.
Ходить на работу хуже, чем трогать рыбу и чистить алгоритм. Байли безвреден. Придерживайтесь ежедневной чистки вопросов. Давай.
GitHub создал хранилище алгоритмов для обновления вопросов по алгоритмам/решения текстовых/видео задач с нуля, давайте вместе почистим его 👉Портал GitHub
Смотрите видеоверсию этой статьи 👉Портал станции Б
Увидев это, давайте сделаем тройку, пожалуйста, поправьте меня, если есть какие-то ошибки, и вы можете обратить внимание на паблик «Нерегулярный интерфейс», и сформировать группу с друзьями из группы алгоритма, чтобы почистить алгоритм , что эффективнее