要存储多个元素,数组(或列表)可能是最常用的数据结构。正如本书之前提到过的,每种语言都实现了数组。这种数据结构非常方便,提供了一个便利的[]语法来访问它的元素。然而这种数据结构有一个缺点:(在大多数语言中)数组的大小是固定的,从数组的起点或中间插入或移除项的成本很高,因为需要移动元素(尽管我们已经学过JavaScript的array类方法可以帮我们做这些事,但背后的情况是一样的)。
链表存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置的。每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称指针或链接)组成。相较于之前学习的 栈/队列 只关心 栈顶/首尾 的模式,链表更加像是数组。链表和数组都是用于存储有序元素的集合,但有几点大不相同
- 链表不同于数组,链表中的元素在内存中并不是连续放置的
- 链表添加或移除元素不需要移动其他元素链表添加或移除元素不需要移动其他元素
- 数组可以直接访问任何一个位置的元素,链表必须从表头开始迭代到指定位置访问 下面是单链表的基本结构数组可以直接访问任何一个位置的元素,链表必须从表头开始迭代到指定位置访问 下面是单链表的基本结构
有一个可能是用来说明链表的最流行的例子,那就是火车。一列火车是由一系列车皮组成的。每节车厢或车皮都相互连接。你很容易分离一节车皮,改变它的位置,添加或移除它。下图演示了一列火车。每节车皮都是列表的元素,车皮间的连接就是指针:
理解了链表是什么之后,现在就要开始实现我们的数据结构了。以下是我们的LinkedList
类的主要结构:
function LinkedList() {
let Node = function(element){
this.element = element;
this.next = null;
};
let length = 0;
let head = null;
this.append = function(element){};
this.insert = function(position, element){};
this.removeAt = function(position){};
this.remove = function(element){};
this.indexOf = function(element){};
this.toString = function(){};
}
以上我们搭好了链表类的架构还没有写这些类的方法,下面我们来一起实现这些方法:
append
this.append = function (element) {
let node = new Node(element),
current;
if (head === null) {
head = node;
} else {
current = head;
while (current.next) {
current = current.next;
}
current.next = node;
}
length++;
};
removeAt
this.removeAt = function (position) {
//检查越界值
if (position > -1 && position < length) {
let current = head,
previous,
index = 0;
//移除第一项
if (position === 0) {
head = current.next;
} else {
while (index++ < position) {
previous = current;
current = current.next;
}
//将previous与current的下一项链接起来:跳过current,从而移除它
previous.next = current.next;
}
length--;
return current.element;
} else {
return null;
}
};
insert
insert的实现和removeAt大同小异
this.insert = function (position, element) {
//检查越界值
if (position >= 0 && position <= length) {
let node = new Node(element),
current = head,
previous,
index = 0;
if (position === 0) { //在第一个位置添加
node.next = current;
head = node;
} else {
while (index++ < position) {
previous = current;
current = current.next;
}
node.next = current;
previous.next = node;
}
length++; //更新列表的长度
return true;
} else {
return false;
}
};
toString
this.toString = function(){
let current = head,
string = '';
while (current) {
string +=current.element + (current.next ? ',' : '');
current = current.next;
}
return string;
};
indexOf
this.indexOf = function (element) {
var current = head,
index = 0;
while (current) {
if (current.element === element) {
return index;
}
index++;
current = current.next;
}
return -1;
};
以上即是单链表的简单实现,除了单链表之外其实还有双链表,双链表就是在单链表的基础之上,内部类节点不仅有next属性还添加了previous属性,在这里就不再赘述了。
应用
实现了链表之后,来看有哪些应用:
基于链表实现栈:
class Stack {
constructor() {
this._link = new LinkedList();
}
push(item) {
this._link.append(item);
}
pop() {
const tailIndex = this._link - 1;
return this._link.removeAt(tailIndex);
}
peek() {
if (this._link.size() === 0) return undefined;
return this._link.getTail().data;
}
size() {
return this._link.size();
}
isEmpty() {
return this._link.isEmpty();
}
clear() {
this._link.clear()
}
}
基于链表实现队列:
class Queue {
constructor() {
this._link = new LinkedList();
}
enqueue(item) {
this._link.append(item);
}
dequeue() {
return this._link.removeAt(0);
}
head() {
if (this._link.size() === 0) return undefined;
return this._link.getHead().data;
}
tail() {
if (this._link.size() === 0) return undefined;
return this._link.getTail().data;
}
size() {
return this._link.size();
}
isEmpty() {
return this._link.isEmpty();
}
clear() {
this._link.clear()
}
}
链表的翻转:
reverse () {
if (!this._head) return false;
let prevNode = null;
let currNode = this.head;
while (currNode) {
// 记录下一节点并重塑连接
const nextNode = currNode.next;
currNode.next = prevNode;
// 轮询至下一节点
prevNode = currNode;
currNode = nextNode;
}
// 交换首尾
[this._head,this._tail] = [this._tail,this._head]
return true;
}
或
_reverseByRecusive (node) {
if (!node) return;
if (!node.next) {
this.head = node;
return ;
}; // 递归终止条件
var reversedHead = this._reverseByRecusive(node.next);
node.next.next = node;
node.next = null;
return;
};
最后还有链表的逆向输出:
_reversePrint(node){
if(!node) return;// 递归终止条件
this._reversePrint(node.next);
console.log(node.data);
};
ps:终于搞完了 打游戏去了~~
补充一下双向链表,大同小异
function DoublyLinkedList() {
const Node = function (element) {
this.element = element;
this.next = null;
this.prev = null;
};
let length = 0;
let head = null;
let tail = null;
this.append = function (ele) {
let index = 1;
let current = head;
let node = new Node(ele);
if (head === null) {
head = node;
tail = node;
length++;
}else {
while (index < length){
current = current.next;
index++;
}
node.prev = current;
current.next = node;
length++;
}
}
this.insert = function (ele,pos) {
if(pos < 0 || pos > length) return false;
const node = new Node(ele)
if(length === 0){
head = node;
tail = node;
length++
}
let index = 1,
current = head;
while (index < pos) {
current = current.next
index++
}
node.next = current.next;
current.next = node;
node.prev = current;
}
this.getHead = function () {
return head;
}
}
const double = new DoublyLinkedList();
double.append('a')
double.append('b')
double.append('c')
double.insert('ljj',2)
console.log(double.getHead());