Списки
Удалить count элементов, начиная с i-того можно вызовом lst.splice(i, count). Эта же функция при вызове lst.splice(i, 0, it) вставляет элемент it на место индекса i (после lst[i-1]). Функция lst.reverse() обращает последовательность элементов списка. Ниже, справа от кода скрипта приведен результат вызова document.write(res); и тег <br> в html означает переход к следующей строке:
Класс List
Перейдём теперь к созданию класса List, который будет оперировать со списками. Зададим список при помощи набора структур вида , где nm — имя узла (может хранить любые данные), prv — указатель на предыдущий и nxt — на следующий узел. Если узлы не имеют указателя prv — то это односторонний (правосторонний) список List, иначе список двухсторонний и он реализован классом List2. Односторонний список чуть быстрее двухстороннего, однако, он заметно медленнее при удалении из конца (pop). Если используется эта функция, необходим List2.
Класс списка так же хранит указатели на первый — beg и последний — end его узел. Существует несколько способов интерпретации этих указателей. Мы подробнее рассмотрим более быстрый вариант, когда beg и end — это фиктивные узлы, а реальные находятся между ними.
Определим функцию, которая будет конструктором класса List: Функция List будет вызвана при объявлении объекта (экземпляра класса List) при помощи оператора new когда мы напишем: var lst = new List(); Теперь объект lst будет хранить данные списка, доступ к которым производится через точку: (lst.length и т.д.). Такие данные будет помнить каждый экземпляр класса (и у каждого они будут иметь свои значения). Выше подобным образом объявлены 3 переменные-свойства класса (end, beg и length).
В конструкторе класса List введен фиктивный указатель end, следующий за последним элементом списка. Фиктивный указатель beg через свойство beg.nxt ссылается на первый элемент списка. Так как список пока пустой, beg.nxt ссылается на end. Функцию (метод класса) push мы определим ниже.
Отметим, что поля nm и nxt инициализированы нулевым указателем null (напомним, что между undefined и null существует заметная разница). Фиктивный указатель end, при вставке в конец списка, будет превращаться в реальный последний узел. Поэтому, таким объявлением, мы предлагаем движку JavaScript сразу выделить память под объект с соответствующими полями, чтобы не пересоздавать его потом (при вставке). Это ускоряет работу с объектами (иногда в разы).
Методы класса
Кроме хранения данных, список должен выполнять определённые действия (иметь методы). Напишем, например, функцию, которая выводит список как строку в круглых скобках: Конструкция (условие? значение_если_истина : значение_если_ложь), являющаяся компактной записью условного оператора if, служит для «ликвидации» запятой после последнего узла в списке.
Функция с именем toString может быть в любом классе. Если она определена, то экземпляр класса, когда этого требует соответствующий контекст, преобразуется в строку. Например, следующий скрипт: так как список пока пустой, выведет строку: . Функция toString есть и у массивов, поэтому их элементы можно выводить аналогичным образом.
Вставка элементов
Напишем теперь функцию, вставляющую элемент с именем nm в начало списка: Так как мы хотим, чтобы список был похож на массив, при добавлении увеличивается переменная length (число элементов в списке). Затем (читая справа-налево) создаётся новый элемент-узел, который по указателю nxt ссылается на прежний первый элемент списка beg.nxt, а фиктивный узел beg по beg.nxt начинает ссылается на вновь добавленный узел. Аналогично выглядит функция, вставляющая элемент в конец списка: Последнюю строку также необходимо читать справа-налево: сначала создаётся новый фиктивный последний узел, на который по указателю end.nxt ссылается реальный последний узел (которым становится «старый» фиктивный). Затем указатель на новый фиктивный сохраняется в this.end.
Графически, последовательная вставка 0, 1, 2 в начало и конец списка выглядит следующим образом: (первые картинки в каждой строке изображают пустые списки):
Удаление элементов
Обсудим алгоритмы удаления элементов из списка. Удаление в правосторонних списках (есть только ссылки nxt) из начала не представляет труда: Отметим одну тонкость. Возвращаемый функцией указатель на имя первого узла nm остаётся в фиктивном узле nm, даже, если вызывающей функцию shift программе он уже не нужен. При необходимости (если данные, хранимые в списке занимают много памяти) его можно освободить, написав lst.beg.nm=null.
Удаление из конца одностороннего списка, существенно более затратная операция, так как необходимо получить последний элемент (реальный) о котором указатель end ничего не знает: При частом выполнении этой операции, лучше использовать класс двунаправленного списка List2, в котором фиктивный финальный узел end (как и остальные узлы) хранит указатель на предыдущий элемент end.prv.
Ещё немного функций
Приведём несколько функций класса List, эффективность которых не столь высока, по сравнению с вставкой в начало или конец списка. Так, получение узла под номером pos (начиная с нуля) выглядит следующим образом:
Упорядоченная вставка
Добавим ещё функцию, которая вставляет элемент в начало списка, но при этом следит за упорядоченностью элементов: Предполагается, что в общем случае имя узла nm не является числом или строкой, поэтому в цикле используется функция сравнения lt(a, b), которая должна возвращать true, если a < b, иначе (больше или равны) — false. Она задаётся извне (хотя по умолчанию она определена так, как в закомментированной строке):
Нефиктивные указатели
Рассмотрим кратко ещё один способ работы со списком, когда указатели beg и end реально ссылаются на первый и последний элементы списка (напрямую, а не через поле nxt). Оформим его в виде класса List1 Для удобства рядом с функциями-методами этого класса повторим аналогичные функции класса List. Добавление в начало списка имеет вид:
Двухсторонние списки
Элементы (узлы) двухcторонних списков хранят в себе указатели, как на следующий элемент (nxt), так и на предыдущий (prv): Они реализованы классом List2, конструктор которого выглядит так: Функции добавления в начало и конец реализованы следующим образом:
Тестирование быстродействия
Ниже тестируется вставка в начало и конец списка, реализованного массивом (медленный unshift) и правосторонними (List, List1) и двухсторонним списком (List2). Как организовать процесс тестирования описано в документе Speed.
добавлений раз: (ms) ( цикл: )
push | unshift | |||
---|---|---|---|---|
Array | 13 | 9088 | Обычный массив JavaScript | |
List | 25 | 15 | Односторонний список List | |
List2 | 30 | 25 | Двухсторонний список List2 | |
List0 | 47 | 34 | Односторонний список List без инициализаций null | |
List1 | 49 | 33 | Односторонний список List с «нефиктивными» beg и end |
Ещё один тест связан с последовательной вставкой всех элементов в конец, а затем их удалением из конца и начала списка:
добавлений раз: (ms) ( цикл: )
push,pop | push,shift | |||
---|---|---|---|---|
Array | 19 | 147 | Обычный массив JavaScript | |
List | 10693 | 13 | Односторонний список List | |
List2 | 22 | 30 | Двухсторонний список List2 |
Общий вывод такой: для операций push, unshift и shift, которые активно используются в алгоритмах поиска в ширину или глубину, стоит использовать класс List. Однако, если необходимо часто удалять элементы из конца списка pop, то List лучше не использовать.
Работа со списками
Подытожим. Методы работы со списками полностью аналогичны методам работы с массивами:
Добавить элемент в список HTML с помощью JavaScript/jQuery
В этом посте будет обсуждаться, как добавить элемент в упорядоченный или неупорядоченный список в HTML с помощью JavaScript и jQuery.
1. Использование jQuery
С помощью jQuery вы можете создать новый <li> элемент и добавить его к существующему <ol> или же <ul> элемент с помощью .append() или .prepend() метод.
The .append() метод вставляет указанное содержимое в конец совпадающих элементов, а метод .prepend() метод вставляет его в начало.
Связный список (Linked Lists)
Связный список — это структура данных, в которой несколько значений хранятся линейно. Каждое значение содержит своё собственное значение узла, а также содержит данные вместе со ссылкой на следующий узел в списке. Ссылка — это указатель на другой объект узла или на null , если следующего узла нет. Если у каждого узла есть только один указатель на другой узел (чаще всего называется next ), то этот список считается односвязный (singly linked list); тогда как если у каждого узла есть две ссылки (обычно previous и next ), то он считается двусвязный (doubly linked list).
В этом посте я остановлюсь на односвязном списке.
Если ты хочешь сразу перейти к коду и пропустить объяснение — можешь посмотреть его здесь (TypeScript).
Зачем использовать связный список
Основное преимущество связных списков состоит в том, что они могут содержать произвольное количество значений, используя необходимый объем памяти. Сохранение памяти было очень важно на старых компьютерах, где мало памяти. Для массивов требовалось резервировать необходимый обьем памяти под элементы и можно было легко исчерпать доступную память или наоборот, не использовать то, что было зарезервировано. Связные списки были созданы, чтобы обойти эту проблему.
Связные списки стали популярными, когда разработчики не знали, сколько элементов в конечном итоге будет содержать массив. Было гораздо проще использовать связный список и добавлять значения по мере необходимости, чем точно угадывать максимальное количество значений, которое может содержать массив. Такие связные списки часто используются в качестве основы для встроенных структур данных в различных языках программирования.
Встроенный тип JavaScript Array не реализован как связный список, хотя его размер динамический и всегда является лучшим вариантом для начала. Мы можем пройти всю свою карьеру без необходимости в использовании связных списков в JavaScript, но они по-прежнему являются хорошим способом узнать о создании собственных структур данных.
Наиболее важной частью связного списка является его структура узлов. Каждый узел должен содержать некоторые данные и указатель на следующий узел в списке.
Узел имеет две части информации:
- указатель или ссылка на следующий узел в списке next (для односвязного списка);
- значение узла value .
Также, он будет содержать один метод — toString : который возвращает значение в строке.
Когда экземпляр класса LinkedListNode сформирован, вызывается функция конструктора, чтобы инициализировать объект с двумя свойствами, value — текущее значение и next — указатель на следующий узел в списке. Указатель next инициализируется со значением по умолчанию null , в случае, если никакое значение не передается в качестве аргумента.
Метод toString принимает обратный вызов для того что бы, если value будет объектом, указать callback , который достанет из этого объекта нужное значение в виде строки. Если же мы передадим просто объект в шаблонные строки, то получим «[object Object]» . callback может выглядеть так:
Теперь углубимся в класс LinkedList . Наш список узлов будет содержать десять методов:
- prepend(значение) : принимает значение и создаёт новый узел с этим значением, помещая его в начало связного списка;
- append(значение) : принимает значение и создаёт новый узел с этим значением, помещая его в конец связного списка;
- delete(значение) : принимает значение и удаляет все узлы, которые имеют указаное значение;
- find(значение) : принимает значение и находит первый узел с таким же значением;
- deleteTail() : удаляет последний узел из списка;
- deleteHead() : удаляет первый узел из списка;
- fromArray(массив значений) : принимает массив значений и создаёт новые узлы из каждого элемента массива, добавляя их в конец списка;
- toArray() : создаёт массив из всех узлов;
- toString(обратный вызов) : принимает обратный вызов (не обязательно) и создаёт строку из всех значений узлов;
- reverse() : создаёт обратный список, меняя узлы местами.
Мы используем синтаксис class JavaScript, хотя также можем использовать замыкание для создания связного списка. Итак, давай настроим конструктор.
В нашем конструкторе нам понадобится два элемента информации:
- head : ссылка на узел в начале списка;
- tail : ссылка на узел в конце списка.
Первый узел в связном списке обычно называется head , последний tail .
Когда создаётся экземпляр класса LinkedList, вызывается функция конструктора для инициализации объекта со свойством head и tail . Указателям head и tail присваивается значение null , поскольку при первоначальном создании объекта связного списка он не содержит никаких узлов.
Prepend метод принимает значение в качестве аргумента и создаёт новый узел с этим значением, помещая его в начало связного списка.
Append метод принимает значение в качестве аргумента и создаёт новый узел с этим значением, помещая его в конец связного списка.
Delete метод принимает значение в качестве аргумента, удаляет все узлы, которые имеют указаное значение и возвращает последний удалённый узел.
Find метод принимает значение в качестве аргумента, находит первый узел с таким же значением и возвращает его.
DeleteTail — метод, который удаляет последний узел из списка и возвращает его.
DeleteHead — метод, который удаляет из списка первый узел и возвращает его.
FromArray — принимает массив значений в качестве аргумента и создаёт новые узлы из каждого элемента массива, по очереди добавляя их в конец списка.
ToArray — метод, что создаёт массив из всех узлов и возвращает его.
ToString — принимает обратный вызов в качестве аргумента (не обязательно) и создаёт строку из всех значений узлов.
Если значение value узла является объектом, нужно указать callback , который достанет из этого объекта значение в виде строки. Если же мы передадим объект в метод toString , то получим «[object Object]» ;
callback может выглядеть так:
Reverse — метод, создающий обратный список, сменой узлов местами. Первый узел становится последним, а последний первым, все узлы и их ссылки меняются соответственно.
Связные списки — это не то, что ты, вероятно, будешь использовать каждый день, но они являются фундаментальной структурой данных в информатике. Концепция использования узлов, которые указывают друг на друга, используется во многих других структурах данных, встроенных во многие языки программирования более высокого уровня. Хорошее понимание того, как работают связные списки, важно для общего понимания о создании и использовании других структур данных.
Добавление и удаление элементов из списков
Меня интересует практическое использование JS. Я написал программу для изобретателей «Функционально-стоимостный анализ технических систем» на VB. Немного ознакомившись с JS, понял, что кое-что можно сделать и здесь. Представляю один из этапов ФСА в виде скрипта. Такой скрипт является основой всей программы и нужен на всех ее этапах. Следующий скрипт, который является развитием этого скрипта, смотрите Здесь
С уважением,
invem
надо бы изменить:
1. когда поле пустое, то в список ничего добавлять не нужно
2. сначала выбрать из списка удаляемый элемент, а потом удалять
Спасибо за замечания.
Ввод пустого списка устранил и выборку сделал. Только через select.
Здравствуйте!
Возможно удобнее было бы использовать Google Apps, который позволяет использовать Java.
Есть один косячек:
при попытке удалить более одного элемента удаление происходит неправильно.
в данном коде производится обращение по индексу элемента и потом он «зануляется», но при «заулении» индексация всех элементов меняется, и удление происходит «половинчато», т.е. имея 4-е элемента в списке, и при попыте удалить их все удалится сначала 2-а, потом 1.
решение: