数组的缺点
不同语言对数组的支持是不一样的,此时跳出 JavaScript 数组的范围,因为 JavaScript 数组灵活度很高, 其他语言设计数组灵活度没有这么高(例如:数组的长度是固定的,操作数据也有困难。)。
JavaScript 数组的数组本质:是对象,不是原始类型,是引用类型,操作起来效率相比于其语言可能比较低。
使用链表替代数组
人如果发现数组运行效率的很慢,此时就应该考虑使用 链表。 除去随机访问数据这种情况,其他的情况都可以替代一维数组。
链表概述
- 链表:一组节点的集合。-
- 链表节点: 通过一个对象的引用指向它的后续
- 链:指向一个节点的引用
- 头节点:链表的头部
链表插入删除语言表述
插入
链表在插入一个节点时候效率很高。流程:
当前节点链断开新的节点的前端与当前节点的后端链接新的节点的后度端与当前节点断开后半部分的前端链接
删除
- 指定
当前节点 - 断开
当前节点的前后链 当前节点的前一个节点的链连接当前节点的后一个节点
设计一个基于对象的链表
- 需要以 Node 节点类和 LLink 类
- LLink 类在原型链上挂在方法
export function Node(element) {
this.element = element;
this.next = null;
}
export function LLink() {
this.head = new Node("head");
}
LLink.prototype.find = function (item) {
let currNode = this.head;
while (currNode.element != item) {
currNode = currNode.next;
}
return currNode;
};
LLink.prototype.insert = function (newElement, item) {
let newNode = new Node(newElement);
let current = this.find(item);
newNode.next = current.next; // 当前链条的后面部分折断,赋值给 newNode.next
current.next = newNode;
};
LLink.prototype.findPrevious = function (item) {
let currentNode = this.head;
while (!(currentNode.next == null) && currentNode.next.element != item) {
currentNode = currentNode.next;
}
return currentNode;
};
LLink.prototype.remove = function (item) {
let prevNode = this.findPrevious(item);
if (!(prevNode.next == null)) {
prevNode.next = prevNode.next.next;
}
};
LLink.prototype.display = function () {
let currNode = this.head;
while (!(currentNode.next = null)) {
console.log(currNode.next.element);
currNode = currNode.next;
}
};
- 以下是针对 LLink 的节点测试用例:
import { LLink } from "../LinkedList.js";
describe("测试链表", () => {
it("insert 方法测试", () => {
let cities = new LLink();
cities.insert("Conway", "head");
expect(cities).toMatchInlineSnapshot(`
LLink {
"head": Node {
"element": "head",
"next": Node {
"element": "Conway",
"next": null,
},
},
}
`);
cities.insert("Russellille", "Conway");
expect(cities).toMatchInlineSnapshot(`
LLink {
"head": Node {
"element": "head",
"next": Node {
"element": "Conway",
"next": Node {
"element": "Russellille",
"next": null,
},
},
},
}
`);
cities.insert("Carlise", "Russellille");
cities.insert("Alma", "Carlise");
expect(cities).toMatchInlineSnapshot(`
LLink {
"head": Node {
"element": "head",
"next": Node {
"element": "Conway",
"next": Node {
"element": "Russellille",
"next": Node {
"element": "Carlise",
"next": Node {
"element": "Alma",
"next": null,
},
},
},
},
},
}
`)
cities.remove("Carlise")
expect(cities).toMatchInlineSnapshot(`
LLink {
"head": Node {
"element": "head",
"next": Node {
"element": "Conway",
"next": Node {
"element": "Russellille",
"next": Node {
"element": "Alma",
"next": null,
},
},
},
},
}
`)
});
});
双向链表
- 双向链表有 head 头和 null 尾部
- 链表中元素首尾相连
- head + 最后一个 Node 都会指向自己的 null
以下使用 Node 节点类 + LList 类实现的双向链表
export function Node(element) {
this.element = element;
this.next = null;
this.previous = null;
}
export function LList() {
this.head = new Node("head");
}
LList.prototype.find = function (item) {
let currNode = this.head;
while (currNode.element != item) {
console.log(currNode.next.element);
currNode = currNode.next;
}
return currNode
};
LList.prototype.findLast = function () {
let currNode = this.head;
while (!(currNode.next == null)) {
currNode = currNode.next;
}
return currNode;
};
LList.prototype.insert = function (newElement, item) {
let newNode = new Node(newElement);
let current = this.find(item);
newNode.next = current.next;
newNode.previous = current;
current.next = newNode;
};
LList.prototype.display = function () {
let currNode = this.head;
while (!(currNode.next == null)) {
console.log(currNode.next.element);
currNode = currNode.next;
}
};
LList.prototype.remove = function (item) {
let currNode = this.find(item);
if (!(currNode.next == null)) {
currNode.previous.next = currNode.next;
currNode.next.previous = currNode.previous;
currNode.next = null;
currNode.previous = null;
}
};
LList.prototype.findLast = function () {
let currNode = this.head;
while (!(currNode.next == null)) {
currNode = currNode.next;
}
return currNode;
};
LList.prototype.dispReverse = function () {
let currNode = this.head;
currNode = this.findLast();
console.log("-----", currNode, currNode.previous, currNode.previous == null)
while(!(currNode.previous == null)) {
console.log("翻转->当前节点元素", currNode.element)
currNode = currNode.previous;
}
return currNode;
};
实现了列表之后,下面是对双向链表的测试用例
import { Node, LList } from "../DoublyLLink.js";
describe("测试双向链表", () => {
it("insert 方法测试", () => {
let cities = new LList();
cities.insert("Conway", "head");
expect(cities).toMatchInlineSnapshot(`
LList {
"head": Node {
"element": "head",
"next": Node {
"element": "Conway",
"next": null,
"previous": [Circular],
},
"previous": null,
},
}
`);
cities.insert("Russellille", "Conway");
expect(cities).toMatchInlineSnapshot(`
LList {
"head": Node {
"element": "head",
"next": Node {
"element": "Conway",
"next": Node {
"element": "Russellille",
"next": null,
"previous": [Circular],
},
"previous": [Circular],
},
"previous": null,
},
}
`);
cities.insert("Carlise", "Russellille");
cities.insert("Alma", "Carlise");
expect(cities).toMatchInlineSnapshot(`
LList {
"head": Node {
"element": "head",
"next": Node {
"element": "Conway",
"next": Node {
"element": "Russellille",
"next": Node {
"element": "Carlise",
"next": Node {
"element": "Alma",
"next": null,
"previous": [Circular],
},
"previous": [Circular],
},
"previous": [Circular],
},
"previous": [Circular],
},
"previous": null,
},
}
`);
cities.remove("Carlise");
expect(cities).toMatchInlineSnapshot(`
LList {
"head": Node {
"element": "head",
"next": Node {
"element": "Conway",
"next": Node {
"element": "Russellille",
"next": Node {
"element": "Alma",
"next": null,
"previous": [Circular],
},
"previous": [Circular],
},
"previous": [Circular],
},
"previous": null,
},
}
`);
cities.dispReverse()
expect(cities).toMatchInlineSnapshot(`
LList {
"head": Node {
"element": "head",
"next": Node {
"element": "Conway",
"next": Node {
"element": "Russellille",
"next": Node {
"element": "Alma",
"next": null,
"previous": [Circular],
},
"previous": [Circular],
},
"previous": [Circular],
},
"previous": null,
},
}
`);
});
});
使用 Jest 的快照功能查看是符合我们对 LList 以及其原型上的函数的操作的,测试用例通过。
循环链表
循环列表在单向列表的基础上的扩展:
- 增加的首尾相链接的功能
export function Node(element) {
this.element = element;
this.next = null;
}
export function LLink() {
this.head = new Node("head");
this.head.next = this.head;
}
LLink.prototype.find = function (item) {
let currNode = this.head;
while (currNode.element != item) {
currNode = currNode.next;
}
return currNode;
};
LLink.prototype.insert = function (newElement, item) {
let newNode = new Node(newElement);
let current = this.find(item);
newNode.next = current.next; // 当前链条的后面部分折断,赋值给 newNode.next
current.next = newNode;
};
LLink.prototype.findPrevious = function (item) {
let currentNode = this.head;
while (!(currentNode.next == null) && currentNode.next.element != item) {
currentNode = currentNode.next;
}
return currentNode;
};
LLink.prototype.remove = function (item) {
let prevNode = this.findPrevious(item);
if (!(prevNode.next == null)) {
prevNode.next = prevNode.next.next;
}
};
LLink.prototype.display = function () {
let currNode = this.head;
while (!(currentNode.next = null) && !(currNode.next.element == "head")) {
console.log(currNode.next.element);
currNode = currNode.next;
}
};
在单向链表 的基础上,
下面是测试用例, next 中 [Circular] 进行标记循环:
import { LLink } from "../CircularLLink.js";
describe("循环试链表", () => {
it("insert 方法测试", () => {
let cities = new LLink();
cities.insert("Conway", "head");
expect(cities).toMatchInlineSnapshot(`
LLink {
"head": Node {
"element": "head",
"next": Node {
"element": "Conway",
"next": [Circular],
},
},
}
`);
cities.insert("Russellille", "Conway");
expect(cities).toMatchInlineSnapshot(`
LLink {
"head": Node {
"element": "head",
"next": Node {
"element": "Conway",
"next": Node {
"element": "Russellille",
"next": [Circular],
},
},
},
}
`);
cities.insert("Carlise", "Russellille");
cities.insert("Alma", "Carlise");
expect(cities).toMatchInlineSnapshot(`
LLink {
"head": Node {
"element": "head",
"next": Node {
"element": "Conway",
"next": Node {
"element": "Russellille",
"next": Node {
"element": "Carlise",
"next": Node {
"element": "Alma",
"next": [Circular],
},
},
},
},
},
}
`);
cities.remove("Carlise");
expect(cities).toMatchInlineSnapshot(`
LLink {
"head": Node {
"element": "head",
"next": Node {
"element": "Conway",
"next": Node {
"element": "Russellille",
"next": Node {
"element": "Alma",
"next": [Circular],
},
},
},
},
}
`);
});
});
小结
- 链表的特性,与数组的区别,以及西更能
- 基于原型链实现的单向链表,双向链表,循环列表
- 使用 Jest 对实现的链表数据结构进行快照测试