你们都用过js中的数组,数组实际上是一种线性表的顺序存储结构,它的特色是用一组地址连续的存储单元依次存储数据元素。而它的缺点也正是其特色而形成,好比对数组作删除或者插入的时候,可能须要移动大量的元素。前端
这里大体模拟一下数组的插入操做:node
function insert(arr, index, data) { for (let i = arr.length; i >index; i--) { arr[i] = arr[i - 1]; } arr[index] = data; }
从上面的代码能够看出数组的插入以及删除都有可能会是一个O(n)的操做。从而就引出了链表这种数据结构,链表不要求逻辑上相邻的元素在物理位置上也相邻,所以它没有顺序存储结构所具备的缺点,固然它也失去了数组在一块连续空间内随机存取的优势。数组
class Node { constructor(key) { this.next = null; this.key = key; } }
class List { constructor() { this.head = null; } }
static createNode(key) { return new createNode(key); }
这里说明一下,这一块我是向外暴露了一个静态方法来建立节点,而并不是直接把它封装进插入操做里去,由于我感受这样的逻辑会更加正确一些。 从建立一个链表 -> 建立一个节点 -> 将节点插入进链表中。可能你会遇到一些文章介绍的方式是直接将一个数据做为参数去调用insert操做,在insert内部作了一个建立节点。数据结构
插入操做只须要去调整节点的指针便可,两种状况:this
head没有指向任何节点,说明当前插入的节点是第一个spa
head有指向的节点指针
insert(node) { // 若是head有指向的节点 if(this.head){ node.next = this.head; }else { node.next = null; } this.head = node; }
find(key) { let node = this.head; while(node !== null && node.key !== key){ node = node.next; } return node; }
这里分三种状况:code
所要删除的节点恰好是第一个,也就是head指向的节点blog
要删除的节点为最后一个节点队列
在列表中间删除某个节点
delete(node) { // 第一种状况 if(node === this.head){ this.head = node.next; return; } // 查找所要删除节点的上一个节点 let prevNode = this.head; while (prevNode.next !== node) { prevNode = prevNode.next; } // 第二种状况 if(node.next === null) { prevNode.next = null; } // 第三种状况 if(node.next) { prevNode.next = node.next; } }
class ListNode { constructor(key) { this.next = null; this.key = key; } } class List { constructor() { this.head = null; this.length = 0; } static createNode(key) { return new ListNode(key); } // 往头部插入数据 insert(node) { // 若是head后面有指向的节点 if (this.head) { node.next = this.head; } else { node.next = null; } this.head = node; this.length++; } find(key) { let node = this.head; while (node !== null && node.key !== key) { node = node.next; } return node; } delete(node) { if (this.length === 0) { throw 'node is undefined'; } if (node === this.head) { this.head = node.next; this.length--; return; } let prevNode = this.head; while (prevNode.next !== node) { prevNode = prevNode.next; } if (node.next === null) { prevNode.next = null; } if (node.next) { prevNode.next = node.next; } this.length--; } }
若是你把上面介绍的单向列表都看明白了,那么这里介绍的双向列表其实差很少。
从上面的图能够很清楚的看到双向链表和单向链表的区别。双向链表多了一个指向上一个节点的指针。
class ListNode { this.prev = null; this.next = null; this.key = key; }
class List { constructor(){ this.head = null; } }
static createNode(key){ return new ListNode(key); }
insert(node) { node.prev = null; node.next = this.head; if(this.head){ this.head.prev = node; } this.head = node; }
这里和单向节点同样,就直接贴代码了
search(key) { let node = this.head; while (node !== null && node.key !== key) { node = node.next; } return node; }
和以前单向链表同样,分三种状况去看:
删除的是第一个节点
删除的是中间的某个节点
删除的是最后一个节点
delete(node) { const {prev,next} = node; delete node.prev; delete node.next; if(node === this.head){ this.head = next; } if(next){ next.prev = prev; } if(prev){ prev.next = next; } }
class ListNode { constructor(key) { // 指向前一个节点 this.prev = null; // 指向后一个节点 this.next = null; // 节点的数据(或者用于查找的键) this.key = key; } } /** * 双向链表 */ class List { constructor() { this.head = null; } static createNode(key) { return new ListNode(key); } insert(node) { node.prev = null; node.next = this.head; if (this.head) { this.head.prev = node; } this.head = node; } search(key) { let node = this.head; while (node !== null && node.key !== key) { node = node.next; } return node; } delete(node) { const { prev, next } = node; delete node.prev; delete node.next; if (node === this.head) { this.head = next; } if (prev) { prev.next = next; } if (next) { next.prev = prev; } } }
这里作一个小总结吧,可能有一部分人读到这里还不是特别的明白,个人建议是先好好看懂上面的单向链表。 其实只要你明白了链表的基础概念,就是有一个head,而后在有好多的节点(Node),而后用一个指针把他们串起来就行了,至于里面的插入操做也好,删除也好,其实都是在调整节点中指针的指向。
后续可能还会是数据结构,多是讲二叉堆,也可能回过头来说一些队列和栈的思想在程序中的应用。欢迎你们指出文章的错误,若是有什么写做建议也能够提出。我会持续的去写关于前端的一些技术文章,若是你们喜欢的话能够关注一下哈