开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情
1 什么是链表?
- 链表:链表是物理存储单元上非连续的、非顺序的存储结构
链表分类:单链表、双链表、双向链表、循环链表、有序链表。
- 单链表:单链表节点有两部分:
节点数据域和节点指针域(指向下一个节点)。
2 单链表
// 单链表节点结构
class LinkNode{
constructor(val,next){
this.value = val;
this.next = next;// 指向后一个节点指针域
}
}
- 单链表代码实现:
- 初始化节点:创建一个虚拟头节点。
为什么需要添加size链表长度、链表头head、链表的尾巴tail?都是为了后面方便处理链表的长度边界情况的分析,以及对链表的尾部处理。
var MyLinkedList = function() {
this.size = 0;
this.tail = null;
this.head = null;
};
- 获取节点:先创建一个虚拟空节点,从开头遍历链表获取当前节点,为什么传的是index索引,传索引为了后面while遍历链表时,index代表循环的次数,也就是遍历index次改变指针域,直到找到当前节点为止,通俗地,
我们也可以把index当作链表索引。
MyLinkedList.prototype.getNode = function(index) {
if(index<0||index>=this.size) return null;
let curNode = new LinkNode(0, this.head);// 创建虚拟空节点
while(index-->=0){
curNode = curNode.next;// 遍历获取当前节点
}
return curNode;// 返回当前索引的节点
};
- 添加节点:创建一个节点,从链表头或链表中或链表尾添加节点。
MyLinkedList.prototype.addAtHead = function(val) {
const node = new LinkNode(val,this.head);// 创建节点
this.head = node;// 当前链表的头节点为刚刚新增的
this.size++;
if(!this.tail){// 如果链表尾节点不存在,也就是当前链表只有一个节点
this.tail = node;
}
};
MyLinkedList.prototype.addAtTail = function(val) {
const node = new LinkNode(val,null);// 创建一个指向null的尾节点
this.size++;
if(this.tail){//当前链表的尾节点不为空
this.tail.next = node;// 先更新尾节点的指针域
this.tail = node;// 更新尾节点
return;
}
this.head = node;// 当链表为空时
this.tail = node;// 当链表为空时
};
MyLinkedList.prototype.addAtIndex = function(index, val) {
if(index>this.size) return;
if(index<=0){
this.addAtHead(val);// 执行头节点的添加
return;
}else if(index===this.size){
this.addAtTail(val);// 执行尾节点添加
return;
}
const node = this.getNode(index-1);// 查找当前索引的上一个节点
node.next = new LinkNode(val,node.next);// 创建一个指向当前索引的后一个节点的节点
this.size++;
};
- 删除节点:删除链表头或链表中或链表尾节点。当删除链表头元素地时候,直接更新head地指针域指向。当删除链表尾或链表中的节点时,直接更新tail的前一个节点指针域指向。
MyLinkedList.prototype.deleteAtIndex = function(index) {
if(index<0||index>=this.size) return;
if(index===0){
this.head = this.head.next;
if(index===this.size-1){
this.tail = this.head;
}
this.size--;
return;
}
const preNode = this.getNode(index-1);;
preNode.next = preNode.next.next;
if(index===this.size-1){
this.tail = preNode;
}
this.size--;
};
3 双链表
-
双链表:既可以从头遍历到尾,也可以从尾巴遍历到头。也就是既有向前连接的引用,也有一个向后连接的引用。双向链表的结构:
节点的数据域和节点的指针域。// 双链表节点结构 class DoubleLinkNode{ constructor(val,prev,next){ this.prev = prev;// 指向前一个节点指针域 this.value = val; this.next = next;// 指向后一个节点指针域 } } // 初始化链表 var MyDoubleLink = function () { this.head = null; this.tail = null; this.size = 0; } -
双链表的代码实现:双链表区别于单链表的就是对prev指针域的处理,在增删查改的操作当中都需要考虑节点的prev的指向。
-
获取节点:区别单链表主要是对边界的处理(双链表是index > this.size,而单链表可以等于),因为要考虑下一个节点
// 区别于单链表的getNode
if (index < 0 || index > this.size) return null;
let node = new DoubleLinkNode(0, null, this.head);
-
在链表头添加元素:与单链表不不一样的是定义节点部分。
let node = new DoubleLinkNode(val, null, this.head); -
在链表尾添加元素:与单链表不一样的也是定义节点部分。
let node = new DoubleLinkNode(val, this.tail, null); -
在链表中添加元素:关键在于非链表头尾元素的添加,需要获取要添加元素位置的上一个节点以及当前节点,控制当前节点的prev指针域和控制上一个节点next指针域。
MyDoubleLink.prototype.addAtIndex = function (index, val) {
if (index > this.size) return;
if (index <= 0) {
this.addAtHead(val);
return;
} else if (index === this.size) {
this.addAtTail(val);
return;
}
let node = this.getNode(index - 1);// 获取上一个节点
let addNode = new DoubleLinkNode(val, node, node.next);// 创建节点
const nextNode = this.getNode(index);// 获取当前节点
if (nextNode) {// 如果存在
nextNode.prev = addNode;
}
node.next = addNode;
}
- 删除链表元素:关键在于非链表头尾元素的删除,需要获取要删除元素位置的上一个节点以及下一个节点,控制
当前节点的上一节点的next指针域和当前节点的下一个节点prev指针域的指向。
MyDoubleLink.prototype.deleteAtIndex = function (index) {
if (index < 0 || index >= this.size) return;
if (index === 0) {
this.head = this.head.next;
if (index === this.size - 1) {
this.tail = this.head;
}
this.size--;
return;
}
let preNode = this.getNode(index - 1);// 获取当前链表的上一个节点
let nextNode = this.getNode(index+1);// 获取当前链表的下一个节点
preNode.next = preNode.next.next;
if (nextNode) {
nextNode.prev = preNode.next.prev;
}
if (index === this.size - 1) {
this.tail = preNode;
}
this.size--;
};