链表及双向链表
这里暂时不谈环形链表(circular linked list)。
链表
链表是一个非常有用的结构,与数组不同,它并不要求数据结构的内存地址保存在一起,而是可以通过指针/引用的方式,获取下一个结点的位置,因此,链表结构可以将散落的数据串联在一起,更大程度的利用空间,减少数据的重排。
链表结构如下:
每一个节点包含当前的值,以及下一个结点的引用。结点代码如下:
class Node {
constructor(val, next) {
this.val = val !== undefined ? val : 0;
this.next = next ? next : null;
}
}
链表本身的实现就比较简单了,下面实现一些比较基础的功能。
首先是 LinkedList 的基础结构:
class LinkedList {
constructor() {
this.head = null;
}
print() {
let l1 = this.head;
let res = [];
while (l1?.next) {
res.push(l1?.val, "->");
l1 = l1.next;
}
res.push(l1?.val);
console.log(res.join(" "));
}
}
这里简单的实现了一个 print 功能,直接打印会出现 circular object 这种情况。
此时初始化后 LinkedList 会输出一个空字符串:
-
appendToHead 这个方法的目的是新建一个 Node,并且将这个 Node 加到链表的头部,其实现如下:
-
新建一个 Node:
-
将新建结点的 next 指向原有的 head
-
将原有的 head 重置为现在的 head
代码实现如下: class LinkedList {
appendToHead(node) {
const newNode = node instanceof Node ? node : new Node(node);
if (!this.head) return (this.head = newNode);
newNode.next = this.head;
this.head = newNode;
}
}
测试结果如下: -
appendToTail appendToTail 的实现与 appendToHead 相似,只不过这一次不是修改 this.head ,而是将新建的结点连到当前链表最后一个结点后。其逻辑如下:
-
新建一个 Node:
-
让当前链表中最后一个元素的 next 指向新建的结点
这里主要的问题在于,链表无法像数组一样只靠下标就能过获取当前结点,因此这里需要依靠迭代去获取最后一个结点的位置。当然,也可以使用 this.tail 去保存最后一个结点,不过这会增加一些实现的复杂度,而且能实现 head 也能实现 tail ,这里就偷懒了。 实现如下: class LinkedList {
appendToTail(node) {
const newNode = node instanceof Node ? node : new Node(node);
if (!this.head) return (this.head = newNode);
let l = this.head;
while (l.next) {
l = l.next;
}
l.next = newNode;
}
}
测试结果如下: -
appendAfter 这里就是实现将新建的结点连到某个结点之后,是 appendToHead 和 appendToTail 的结合了。
-
新建一个 Node:
-
将 prevNode 的 next 指向 新创立的结点
-
将新建的结点的 next 指向 prevNode 的 next
实现代码如下: class LinkedList {
appendAfter(prevNode, node) {
const newNode = node instanceof Node ? node : new Node(node);
if (!prevNode) return;
const next = prevNode.next;
prevNode.next = newNode;
newNode.next = next;
}
}
测试结果如下:
双向链表
双向链表和普通链表的结构其实差不多,只不过在每个结点上会多使用一个空间去保存上一个节点的指针/引用,大体结构如下:
这样就能够解决向前追溯的问题,比如说 146. LRU Cache。
这里就直接贴一下实现了:
class Node {
constructor(val) {
this.val = val;
this.prev = null;
this.next = null;
}
}
class DLL {
constructor() {
this.head = null;
this.tail = null;
}
print() {
let l1 = this.head;
let res = [];
while (l1?.next) {
res.push(l1?.val, "<->");
l1 = l1.next;
}
res.push(l1?.val);
console.log(res.join(" "));
}
appendToHead(node) {
const newNode = node instanceof Node ? node : new Node(node);
if (!this.head) {
this.head = newNode;
this.tail = newNode;
return;
}
newNode.next = this.head;
this.head.prev = newNode;
this.head = newNode;
}
appendToTail(node) {
const newNode = node instanceof Node ? node : new Node(node);
if (!this.head) {
this.head = newNode;
this.tail = newNode;
return;
}
newNode.prev = this.tail;
this.tail.next = newNode;
this.tail = newNode;
}
moveToHead(node) {
if (node === this.head) return ;
const prev = node.prev, next = node.next;
prev.next = next;
if (next) next.prev = prev;
if (node === this.tail) this.tail = prev;
this.head.prev = node;
node.next = this.head;
this.head = node;
}
removeTail() {
if (this.head === this.tail) {
this.head = null;
this.tail = null;
return ;
}
const prev = this.tail.prev;
prev.next = null;
this.tail = prev;
}
}
const node5 = new Node(5);
const node10 = new Node(10);
const dll = new DLL();
dll.appendToHead(7);
dll.appendToHead(node5);
dll.print();
dll.appendToTail(8);
dll.appendToTail(node10);
dll.print();
dll.moveToHead(node5)
dll.moveToHead(node10)
dll.moveToHead(node5)
dll.print()
dll.removeTail();
dll.print()
|