1.数组的缺点
JavaScript 中数组的主要问题是, 它们被实现成了对象, 与其他语言(比如 C++ 和 Java)的数组相比, 效率很低
2. 定义链表
链表:由一组节点组成的集合,每个节点都使用一个对象的引用指向它的后继, 指向另一个节点的引用叫做链。
注意:数组元素靠它们的位置进行引用, 链表元素则是靠相互之间的关系进行引用。 我们说 bread 跟在 milk 后面, 而不说 bread 是链表中的第二个元素。 遍历链表, 就是跟着链接, 从链表的首元素一直走到尾元素(但这不包含链表的头节点, 头节点常常用来作为链表的接入点),链表的尾元素指向一个 null 节点。
头节点:许多链表的实现都在链表最前面有一个特殊节点, 叫做头节点。
插入节点:链表中插入一个节点的效率很高。 向链表中插入一个节点, 需要修改它前面的节点(前驱), 使其指向新加入的节点, 而新加入的节点则指向原来前驱指向的节点。
删除节点:从链表中删除一个元素也很简单。 将待删除元素的前驱节点指向待删除元素的后继节点, 同时将待删除元素指向 null, 元素就删除成功了。
3 设计一个基于对象的链表
1 Node类:用来表示节点
Node 类包含两个属性: element 用来保存节点上的数据, next 用来保存指向下一个节点的链接。 我们使用一个构造函数来创建节点, 该构造函数设置了这两个属性的值:
function Node(element) {
this.element = element;
this.next = null;
}
2 LinkedList类
LList 类提供了对链表进行操作的方法。 该类的功能包括插入删除节点、 在列表中查找给定的值。 该类也有一个构造函数, 链表只有一个属性, 那就是使用一个 Node 对象来保存该链表的头节点。
function LList() {
this.head = new Node("head");
this.find = find;
this.insert = insert;
this.remove = remove;
this.display = display;
}
3 插入新节点
insert方法向链表中插入一个节点。
function LList() {
this.head = new Node("head");
this.find = find;
this.insert = insert;
//this.remove = remove;
this.display = display;
}
//查找
function find(item) {
var currNode = this.head;
while (currNode.element != item) {
currNode = currNode.next;
}
return currNode;
}
//插入
function insert(newElement, item) {
var newNode = new Node(newElement);
var current = this.find(item);
newNode.next = current.next;
current.next = newNode;
}
//显示
function display() {
var currNode = this.head;
while (!(currNode.next == null)) {
print(currNode.next.element);
currNode = currNode.next;
}
}
4 从链表中删除一个节点
//方法遍历链表中的元素, 检查每一个节点的下一个节点中是否存储着待删除数据。
//如果找到, 返回该节点(即“前一个” 节点), 这样就可以修改它的 next 属性了
function findPrevious(item) {
var currNode = this.head;
while (!(currNode.next == null) &&
(currNode.next.element != item)) {
currNode = currNode.next;
}
return currNode;
}
//跳过了待删除节点, 让“前一个” 节点指向了待删除节点的后一个节点。
function remove(item) {
var prevNode = this.findPrevious(item);
if (!(prevNode.next == null)) {
prevNode.next = prevNode.next.next;
}
}
4 双向链表
通过给 Node 对象增加一个属性, 该属性存储指向前驱节点的链接, 此时向链表插入一个节点需要更多的工作, 我们需要指出该节点正确的前驱和后继。
1.为 Node 类增加一个 previous 属性:
function Node(element) {
this.element = element;
this.next = null;
this.previous = null;
}
2.insert()方法
设置新节点的 previous 属性, 使其指向该节点的前驱
function insert(newElement, item) {
var newNode = new Node(newElement);
var current = this.find(item);
newNode.next = current.next;
newNode.previous = current;
current.next = newNode;
}
3.remove() 方法
优点:比单向链表的效率更高, 因为不需要再查找前驱节点了。
首先需要在链表中找出存储待删除数据的节点, 然后设置该节点前驱的 next 属性, 使其指向待删除节点的后继; 设置该节点后继的 previous 属性, 使其指向待删除节点的前驱。
function remove(item) {
var currNode = this.find(item);
if (!(currNode.next == null)) {
currNode.previous.next = currNode.next;
currNode.next.previous = currNode.previous;
currNode.next = null;
currNode.previous = null;
}
}
4.findLast() 方法
找出了链表中的最后一个节点, 同时免除了从前往后遍历链表之苦
function findLast() {
var currNode = this.head;
while (!(currNode.next == null)) {
currNode = currNode.next;
}
return currNode;
}
5.dispReverse() 方法
反序显示双向链表中的元素
function dispReverse() {
var currNode = this.head;
currNode = this.findLast();
while (!(currNode.previous == null)) {
print(currNode.element);
currNode = currNode.previous;
}
}
5 循环链表
在创建循环链表时, 让其头节点的 next 属性指向它本身, 即:
head.next = head
这种行为会传导至链表中的每个节点, 使得每个节点的 next 属性都指向链表的头节点。 换句话说, 链表的尾节点指向头节点, 形成了一个循环链表。
1.创建循环链表
只需要修改 LList 类的构造函数:
function LList() {
this.head = new Node("head");
this.head.next = this.head;
this.find = find;
this.insert = insert;
this.display = display;
this.findPrevious = findPrevious;
this.remove = remove;
}
2.循环列表中一些方法需要修改
function display() {
var currNode = this.head;
while (!(currNode.next == null) &&
!(currNode.next.element == "head")) {
print(currNode.next.element);
currNode = currNode.next;
}
}
6 链表的其他方法
•advance(n)
在链表中向前移动 n 个节点。
• back(n)
在双向链表中向后移动 n 个节点。
• show()
只显示当前节点。