在上一篇文章中,咱们了解了队列和栈的JavaScript描述,如今让咱们来了解一下 单链表
和双向链表
的实现。本文的代码并不是全部都由本人所写,只是出于学习目的
,在此分享出来,并加上必定的解释,便于你们学习。node
本系列文章的代码可在 https://github.com/HolyZheng/...找到。
咱们直入话题:git
单链表
是存储结构的一种,它具备如下特色:github
如今咱们建立一个单链表,并给它添加<font color=#A52121>add、searchNode、remove</font> 三个方法。
建立一个单链表:函数
//单链表 function SinglyList () { this._length = 0; this.head = null; }
这个单链表暂时又两个属性: _length
链表的长度,head
链表头节点。学习
每个节点须要存储数据,还要指向下一节点,因此为每一个节点建立一个node类
:this
//结点 function Node (data) { this.data = data; this.next = null; }
node类
具备两个属性,data
存储数据,next
指向下一节点。
如今咱们为它添加几个基本的方法:add、searchNode 和 remove
函数。咱们先实现 add 方法,给单链表添加节点。在 add 方法中咱们须要考虑两中状况,分别为:单链表为空和单链表不为空。prototype
//add方法 SinglyList.prototype.add = function (value) { var node = new Node(value), currentNode = this.head; //1st:当单链表为空时 if (!currentNode) { this.head = node; this._length++; return node; } //2nd:单链表不为空 while (currentNode.next) { currentNode = currentNode.next; } currentNode.next = node; this._length++; return node; }
能够看到在代码中,咱们先定义了一个 currentNode
变量,指向 this.head ,而后判断若是当前链表为空,直接将新节点赋值给 this.head ,若是不为空,先将currentNode指向最后的节点,而后再执行 currentNode.next = node
将新节点添加到链表的末尾。
再来实现 searchNode 方法:
searchNode方法的做用是搜索特定位置的节点code
//searchNode方法 SinglyList.prototype.searchNode = function (position) { var currentNode = this.head, length = this._length, message = {failure: "Failure: non-existent node in this list"}; //1st:位置position非法 if (length === 0 || position < 1 || position > length) { throw new Error(message.failure); } //2nd:位置position合法 for (var i = 1; i < position; i++) { currentNode = currentNode.next; } return currentNode; }
咱们很简单就实现了这个方法,先检测链表是否为空和查询的位置是否合法,不为空且位置合法的话,利用一个循环,将currentNode指向特定的position,而后就能够访问到须要的节点了。队列
咱们如今来看一下最后的一个方法: remove。 remove方法比起前两个方法的话,要复杂一点,由于要考虑删减了一个元素以后,还要保持整个链表的连续性。ip
//remove方法 SinglyList.prototype.remove = function (position) { var currentNode = this.head, length = this._length, message = {failure: "Failure: non-existent node in this list"}, beforeNodeToDelete = null, nodeToDelete = null; //1st 位置position非法 if (position < 0 || position > length) { throw new Error(message.failure); } //2nd 位置position为 1 if (position === 1) { this.head = currentNode.next; nodeToDelete = currentNode; currentNode = null; this._length--; return nodeToDelete; } //3rd position为其余位子 for(var i = 1; i < position; i++) { beforeNodeToDelete = currentNode; nodeToDelete = currentNode.next; currentNode = currentNode.next; } beforeNodeToDelete.next = nodeToDelete.next; currentNode = null; this._length--; return nodeToDelete; }
首先检查 position 的值是否合法,而后看position的值是否为 1,若是为 1 那就好办了,将 this.head 指向原this.head.next,而后长度减 1 便可。若是position为其余位置,那就要先拿到 要删除节点的前一节点 <
和 要删除的节点
而后将前一节点的next指向要删除节点的next,以保持删除节点后,链表的连续。理解了这点,那就基本能够理解代码了。
双向链表就是在单链表的基础上,添加了一个指向当前结点的前驱的变量,这样就能够方便的由后继来找到其前驱,就能够双向的访问链表。
一样咱们先来建立一个 结点类
:
//节点类 function Node (value) { this.data = value; this.previous = null; this.next = null; }
能够看到这里多了一个 this.previous ,做用就是指向它的前驱。
而后再来看一下双向链表这个类:
function DoublyList () { this._length = 0; this.head = null; this.tail = null; }
比起 单链表
, 双向链表
多了一个指向尾结点的 this.tail。
一样,在这里咱们实现 add、searchNode 和 remove
三个方法。先来看 add 方法:
//add方法 DoublyList.prototype.add = function (value) { var node = new Node(value); if(this._length) { this.tail.next = node; node.previous = this.tail; this.tail = node; } else { this.head = node; this.tail = node; } this._length++; return node; }
在插入新结点的时候咱们一如既往的须要检查链表是否为空,若是链表为空,就将 this.head 和 this.tail 都指向新结点,若是不为空,那就将新结点添加到链表末尾,并将新结点的 previous 指向原 this.tail 。这样就完成了 add 方法。
searchNode方法:
//searchNode DoublyList.prototype.searchNode = function (position) { var currentNode = this.head, length = this._length, message = {failure: "Failure: non-existent node in this list"}; if(length === 0 || position < 1 || position > length) { throw new Error(message.failure); } for (var i = 1; i < position; i++) { currentNode = currentNode.next; } return currentNode; }
双向链表的searchNode方法和单链表的差很少,都是借助循环直接拿到要访问的结点。
最后是最复杂的remove方法
//remove方法 DoublyList.prototype.remove = function (position) { var currentNode = this.head, length = this._length, message = {failure: "Failure: non-existent node in this list"}, beforeNodeToDelete = null, nodeToDelete = null, afterNodeToDelete = null; //1st: 位置position非法 if (length === 0 || position < 1 || position > length) { throw new Error(message.failure); } //2nd 位置为第一个节点 if (position === 1) { nodeToDelete = this.head; this.head = currentNode.next; if (this.head) { this.head.previous = null; } else { this.tail = null; } //3rd 位置为最后一个节点 } else if (position === this._length) { this.tail = this.tail.previous; this.tail.next = null; //4th 位置为其余节点 } else { for (var i = 1; i < position; i++) { currentNode = currentNode.next; } beforeNodeToDelete = currentNode.previous; nodeToDelete = currentNode; afterNodeToDelete = currentNode.next; beforeNodeToDelete.next = afterNodeToDelete; afterNodeToDelete.previous = beforeNodeToDelete; } this._length--; return nodeToDelete; }
remove方法要对传进来的 position 进行判断,分红 4 种状况,
position非法
,抛出错误。 position为 1
,将this.head 指向下一个结点,而后将this.head.previous = null
,这时要判断一下 this.head 是否为空,若是为空就代表这个双向链表本来只有一个结点,因此 remove 后 须要把 this.tail = null
。当 position 为最后一个结点
时,咱们把 this.tail 前移this.tail = this.tail.previous
,此时 this.tail 指向倒数第二个结点,再执行this.tail.next = null
,就把最后一个结点remove掉了position 为其余位置
,咱们先定位到要remove掉的结点,而后将要删除结点的前一结点与要删除结点的后一结点连接起来,就把要删除的结点remove掉了,既beforeNodeToDelete.next = afterNodeToDelete ; afterNodeToDelete.previous = beforeNodeToDelete
单链表和双向链表具备如下特色: