(成功的唯一秘诀——坚持最终一分钟。——柏拉图)
链表
众所周知,数组的查询比链表快,但插入比链表慢。
这是因为链表是一种动态的数据结构,不同于数组的是,链表分配内存空间的灵活性,它不会像数组一样被分配一块连续的内存。当你想在数组的任意位置,插入一个新值的时候,必须对数组中的各个元素进行相应的位置移动才能达到目标,开销显然是很大的。然而链表的灵活性在于它的每个元素节点分为两部分,一部分是存储元素本身,另一部分是指向下一个节点元素的引用,也可以称为指针,当你要插入数据时,把上一个节点的向下指针指向新数据节点,新数据节点的向下指针指向原有数据。但是链表不像数组那样可以直接通过索引立刻定位,只能通过遍历。
JavaScript实现链表
function LinkedList() {
this.length = 0;
this.head = null;
}
function Node(val) {
this.val = val;
this.next = null;
}
LinkedList.prototype.getNodeByIndex = function (idx) {
if (typeof idx !== "number") throw new Error("id is not valid");
if (idx < 0 || idx >= this.length) return null;
let cur = this.head;
while (idx--) {
cur = cur.next;
}
return cur;
};
LinkedList.prototype.append = function (val) {
let node = new Node(val);
if (this.length === 0) {
this.head = node;
} else {
this.getNodeByIndex(this.length - 1).next = node;
}
this.length++;
};
LinkedList.prototype.insert = function (pos, val) {
if (pos < 0 || pos >= this.length) return false;
let node = new Node(val);
if (pos === 0) {
node.next = this.head;
this.head = node;
} else {
let prev = this.getNodeByIndex(pos - 1);
node.next = prev.next;
prev.next = node;
}
this.length++;
return true;
};
LinkedList.prototype.removeAt = function (pos) {
if (pos < 0 || pos >= this.length) return false;
if (pos === 0) {
this.head = this.head.next;
} else {
let prev = this.getNodeByIndex(pos - 1);
prev.next = prev.next.next;
}
this.length--;
return true;
};
为什么JavaScript没有提供内置的链表api?
目前并没有权威的机构来说明这个问题,但从js的发展史来看,也可以得出一些原因
- 使用链表的主要目的是提高性能,当有大量记录并且频繁更改它们时,链表有助于优化它。但对于大多数 JavaScript 应用程序而言,情况可能并非如此。对于 JavaScript,数组几乎可以做到这一点
- 大多数框架都依赖于数组,无论是 React、Vue 等。
- 当你需要对一个巨大的列表进行大量修改时,使用链表是合理的——尤其是在列表末尾以外的地方添加或删除项目时,这种情况在 JavaScript/前端中很少见。
- 在过去,js主要针对的是web端开发,而在web端的性能瓶颈在io,所以对于部分性能方面的api并没有提供
- 最后但并非最不重要的一点是,JavaScript 对数组有很好的支持,但您必须编写自己的实现代码或使用一些提供链表实现的库。
- 其他原因
链表的种类和区别
- 单链表
每个节点只有一个指针域,指向下一个节点。单链表的插入和删除操作比较简单,但是查询慢。 - 双链表
每个节点有两个指针域,分别指向前一个节点和后一个节点。双链表可以方便地实现双向遍历,但是占用空间比较大。 - 循环链表
尾节点的指针域指向链表的头节点。循环链表比单链表和双链表的查询效率更高,但是在插入和删除操作时需要注意维护链表的结构。