什么是链表
链表和数组类似,是一种线性的数据结构,与数组不同的是,链表中的数据在内存中并不是顺序存储的,而是通过在链表的每个元素中,保存指向下一个元素的引用,来找到下一个元素。
链表元素(构成)
把元素叫做节点,节点后面的叫后继节点,节点前面的叫前置节点。
访问节点
通过.next来访问下一个节点。
应用场景
P2P网络(分布式网络)、文件系统、基础数据结构(队列)
常见链表种类
1.单链表
2.双向链表
3.循环链表
4.双向循环链表
创建链表
创建一个节点
可以使用一个类来表示一个节点。
class Node{
constructor(value, next = null){
this.value = value;
this.next = next;
}
}
/*
这里的Node Class 包含两个属性,value为节点的数据,next为对后继节点的引用。
这里的next的值也是一个Node对象,所以这是一个递归结构。
*/
创建一个链表链表为例子。
/*
以单链表为例子。
对于单链表来说,只需要头节点的引用就行了。所有的操作(增删改查)都需要通过头节点来操作。
*/
class LinkedList{
constructor(value){
this.head = new Node(value);
}
}
/*
在LinkedList链表类中,通过构造函数接收头部节点的值。创建一个Node对象,保存在head属性中。
head就是头部节点的引用。
注意:头部节点代表整个链表。
*/
查找节点
/*
这个findNode方法接收要查找的值做为参数,返回查找到的节点,如果没有找到就返回null。
方法中我们先定义一个临时变量currentNode(局部变量let)来存储当前遍历到的节点(默认为头节点)。
在循环中让currentNode与value值进行对比:
如果值不相等,就把currentNode的值设置为CurrentNode的下一个节点。(currentNode = currentNode.next;)
如果值相等,while循环就会退出。
如果遍历到了链表的尾部节点next还没有找到所对应的值,那就返回null。
*/
findNode(value){
let currentNode = this.head;
while(currentNode.value != value){
currentNode = currentNode.next;
}
if(!currentNode){
return null;
}
return currentNode;
}
/*
while(currentNode.value != value){
currentNode = currentNode.next;
}
为在while循环的条件判断中,currentNode.value != value,
当当前节点的值不等于要查找的值时,会继续执行currentNode = currentNode.next;,即移动到下一个节点继续查找。
当遍历到链表的尾部节点时,currentNode.next为null,此时currentNode.value != value条件不成立,循环结束。
*/
/*
if(!currentNode){
return null;
}
return currentNode;
if语句会判断!currentNode是否为真。
如果为真,说明已经遍历到了链表的尾部节点,没有找到要查找的值,此时返回null。
如果为假,说明找到了要查找的值,此时返回currentNode。
*/
这段代码中的if语句为什么不在while循环里面?
这段JavaScript代码中的if语句不在while循环里面,是因为在while循环中已经有一个条件判断currentNode.value != value
。如果这个条件成立,说明当前节点的值不等于要查找的值,那么就会执行currentNode = currentNode.next;
,即移动到下一个节点继续查找。如果这个条件不成立,说明已经找到了要查找的节点,此时应该跳出循环,而不是继续执行if语句。因此,将if语句放在while循环外面是正确的。
这段代码中我如果传入了一个在链表中没有的值value,那么在while循环中,是不是会一直遍历到链表的尾部节点,然后呢?怎么退出这个while循环,然后if语句是判断什么?
这段代码中,如果传入了一个在链表中没有的值value,那么在while循环中,会一直遍历到链表的尾部节点。因为在while循环的条件判断中,currentNode.value != value
,当当前节点的值不等于要查找的值时,会继续执行currentNode = currentNode.next;
,即移动到下一个节点继续查找。当遍历到链表的尾部节点时,currentNode.next
为null,此时currentNode.value != value
条件不成立,循环结束。
退出这个while循环后,if语句会判断!currentNode
是否为真。如果为真,说明已经遍历到了链表的尾部节点,没有找到要查找的值,此时返回null。如果为假,说明找到了要查找的值,此时返回currentNode
。
指定位置插入节点
/*
方法insertAfter接收两个参数, 第一个value是要在那个节点的后面插入,接收的是节点的value值,第二个newValue接收新节点的值。
在insertAfter方法中先创建一个新节点的对象,然后调用findNode方法找到insertAfter方法接收的第一个值value所在的节点。
然后把新节点的next指向value所在节点的next,value所在的节点的next指向新节点(注意:要先将新节点的next指向value所在节点的next,然后才能将value节点的next指向新节点,要不然不行报错的)
这样新的节点就插入到链表当中了,其实就是新的节点指向之前位置节点的指向,之前的节点指向新的节点。
*/
insertAfter(value,newValue){
const newNode = new Node(newValue);
const currentNode = this.findNode(value);
newNode.next = currentNode.next;
currentNode.next = newNode;
}
在尾部插入节点
/*
append方法接收一个参数value,作为新节点的值。
方法中,根据valude然后创建一个新节点(new Node)。
定义一个临时变量currentNode(局部变量let)来存储当前遍历到的节点(默认为头节点)。
然后遍历整个链表,找到尾部节点。(在while循环中判断currentNode.next是否为空null)
也就是说遍历到的节点没有后继节点了,那当前的节点就是尾部节点。
最后在currentNode(此时已经为尾部节点了)的next指向值指向新节点。
这样就完成了在尾部插入一个新的节点的操作了。
*/
append(value){
const newNode = new Node(value);
let currentNode = this.head;
while(currentNode.next){
currentNode = currentNode.next;
}
currentNode.next = newNode;
}
在头部插入节点
/*
perpend方法接收一个参数value做为节点的值。
根据value创建一个新节点对象。
然后让新节点的head指向现在的头部节点。
然后让现在的头部节点指向新节点。让现有的head指向新的节点。(不加这一步的话head还是指向之前的head)
*/
perpend(value){ //perpend方法接收一个参数value做为节点的值。
const newNode = new Node(value); //根据value创建一个新节点对象。
newNode.next = this.head; //然后让新节点的head指向现在的头部节点。
this.head = newNode; //然后让现在的头部节点指向新节点。让现有的head指向新的节点。(不加这一步的话head还是指向之前的head)
}
perpend(value){
const newNode = new Node(value);
newNode.next = this.head;
this.head = newNode;
}
删除指定节点
/*
remove方法接受一个参数value,要删除的节点值。
定义两个临时变量:一个保存查找到的要删除的节点;一个保存要删除的节点的前置节点。(因为删除一个节点,要让要删除的节点的前置节点指向要删除的节点的后继节点)
定义一个临时变量currentNode(局部变量let)来存储当前遍历到的节点(默认为头节点)。
利用while循环找到要删除的节点,和他的前置节点。(perviousNode在while循环里赋值为了当前节点即为要删除的节点,currentNode在while循环里面赋值为要删除的节点的next)
加个if判断:如果删除的是头节点,那么就让head直接指向他后面的节点就可以了。
如果不是头节点,那么就让要删除的节点的前置节点指向要删除的节点的后继节点。
*/
remove(value){ //remove方法接受一个参数value,要删除的节点值。
let currentNode = this.head;//定义一个临时变量currentNode(局部变量let)来存储当前遍历到的节点(默认为头节点)。
let perviousNode = null;//定义两个临时变量:一个保存查找到的要删除的节点;一个保存要删除的节点的前置节点。(因为删除一个节点,要让要删除的节点的前置节点指向要删除的节点的后继节点)
while(currentNode.value != value){
perviousnode = currentNode;
currentNode = currentNode.next;
}
//利用while循环找到要删除的节点,和他的前置节点。(perviousNode在while循环里赋值为了当前节点即为要删除的节点,currentNode在while循环里面赋值为要删除的节点的next)
if(currentNode == this.head){
this.head = currentNode.next;
}else{
perviousNode.next = currentNode.next;
}
//加个if判断:如果删除的是头节点,那么就让head直接指向他后面的节点就可以了。如果不是头节点,那么就让要删除的节点的前置节点指向要删除的节点的后继节点。
}
remove(value){
let currentNode = this.head;
let perviousNode = null;
while(currentNode.value != value){
perviousnode = currentNode;
currentNode = currentNode.next;
}
if(currentNode == this.head){
this.head = currentNode.next;
}else{
perviousNode.next = currentNode.next;
}
}