JavaScript单链表详解(增,删,改,查、逆序)
1、addNode(val):在链表尾部添加元素(val为节点的值)。
2、Length():链表的长度。
3、display():打印链表。
4、ModifyValue(val1,val2):替换指定值。
5、findValue(val):查找指定的值,返回它的位置。
6、DeleteValue(val):删除指定节点。
7、ModifyLc(location,val):修改指定位置的值。
8、insert(location,val):向指定位置添加元素。
9、ReverseOrder():逆序。
单链表及节点。
链表是一系列的存储数据元素的单元通过指针串接起来形成
的,因此每个单元至少有两个域,
一个域用于数据元素的存储
,另一个域是指向其他单元的指针
。
这里具有一个数据域和多个指针域的存储单元通常称为 结点(node)
一种最简单的结点结构如图所示,它是构成单链表的基本结点结构
。在结点中数据域用来存储数据元素,
指针域用于指向下一个具有相同结构的结点。
特性
单链表的一个重要特性就是只能通过前驱结点找到后续结点
,而无法从后续结点找到前驱结点。
优缺点
优点:
1、链表是一个动态数据结构
,因此它可以在运行时通过分配和取消分配内存来增长和收缩。 因此,无需给出链表的初始大小
。
2、节点的插入和删除确实非常容易。 与数组不同,在插入或删除元素后,我们不必移动元素
。 在链表中,我们只需要更新节点的下一个指针中存在的地址即可。
3、由于链表的大小可以在运行时增加或减小,因此不会浪费内存
。 在数组的情况下,会浪费大量内存,例如,如果我们声明一个大小为10的数组并在其中仅存储6个元素,那么就会浪费4个元素的空间。 链表中没有这种问题,因为仅在需要时才分配内存
。
缺点:
1、与数组相比,在链表中存储元素需要更多的内存
。 因为在链表中,每个节点都包含一个指针,并且它本身需要额外的内存。
2、在链表中很难遍历元素或节点。 我们不能像按索引在数组中那样随机访问任何元素
。 例如,如果我们要访问位置n处的节点,则必须遍历它之前的所有节点。 因此,访问节点所需的时间很大。
3、在链表中,反向遍历确实很困难。 在双链表的情况下,它比较容易,但是向后指针需要额外的内存,因此浪费了内存。
1、addNode(val)
1、在尾部插入元素时,如果head为空(长度length=0),也就是链表为空,我们只需要将头结点指向新创建的节点。
2、如果链表不为空,我们只需找到最后一个节点,并将它的next指向新创建的节点。
//在链表尾部添加元素(val为节点的值)
ListNode.prototype.addNode = function(val){
//创建需要添加的节点
let newNode = new Node(val);
//判断链表是否为空,若不为空则在链表后面添加元素,否则它就是头节点
if(this.length==0){
this.head=newNode;
}else{
let head1 = this.head;
while(head1.next!==null){//找到链表的尾部
head1 = head1.next;
}
head1.next = newNode;//向链表的尾部添加节点
}
this.length++;//添加节点后对应的长度增加
}
2、Length()
链表的长度,由于我们增加节点和删除节点时都会更新链表中的length属性,所以length的值就是链表的长度。
ListNode.prototype.Length = function(){
return this.length;//返回它的长度,就是链表的长度
}
3、display()
我们建立一个数组来保存链表的遍历结果,如果当前指针不为空,就把它的值存入数组,然后把指针移动到下一个位置。直到它为空,这样就可以遍历链表中所有的值了。
//打印链表
ListNode.prototype.display = function(){
let head1 = this.head;
let res = [];//用数组保存遍历的链表里的值
let i = 0;
while(head1!==null){//依次遍历链表,并将值存入数组
res[i] = head1.val;
head1 = head1.next;
i++;
}
return res;
}
4、ModifyValue(val1,val2)
替换值时,我们先找到这个值的位置,然后在修改它的值。
//替换指定值
ListNode.prototype.ModifyValue = function(val1,val2){//将val1替换为val2
let location = this.findValue(val1);//查找到val1的位置
if(location==false){//如果没有查找到,返回false
return false;
}
let head1 = this.head;//如果查找到了,就找到那一个节点,并将值替换
while(head1!==null&&location>1){
head1 = head1.next;
location--;
}
head1.val = val2;
}
5、findValue(val)
查找指定值时,我们通过依次遍历链表中所有的值来查找指定值的位置,通过计算遍历了几个值来返回它的相对位置。
//查找指定的值,返回它的位置
ListNode.prototype.findValue = function(val){
let head1 = this.head;
let count = 0;
while(head1!==null){//依次遍历链表
if(head1.val==val){//如果找到了,就返回它的位置
count++;
return count;
}else{//没找到就一直往下找;
head1 = head1.next;
count++;
}
}
return false;//遍历完后,如果没找到就返回false
}
6、DeleteValue(val)
我们还是先查找指定值出现的位置,如果没查到,就说明链表里没有这个值。
如果找到的是头节点,只需要将它的头指针指向它下一个节点:
否则就找到要删除节点的上一个节点和下一个节点:
然后让上一个节点指向要删除的下一个节点:
//删除指定节点
ListNode.prototype.DeleteValue = function(val){
let location = this.findValue(val);//查找到val的位置
if(location==false){//如果没找到,就返回false
return false;
}
let head1 = this.head;
let head2 = null;
if(location==1){//如果需要删除头结点,则直接让头结点指向下一个节点
this.head=this.head.next;
this.length--;
}else{
while(location>2&&head1.next!==null){//否则就找到它的上一个节点
head1 = head1.next;
location--;
}
head2 = head1.next.next;//找到他的下一个节点
head1.next = head2;//将它的上一个节点和下一个节点连接在一起
this.length--;
}
}
7、ModifyLc(location,val)
直接找到需要修改的位置,然后将它的值修改为val。
//修改指定位置的值
ListNode.prototype.ModifyLc = function(location,val){
location = location-1;//起始位置是从0开始的,而正常是从1开始
if(this.length<location||location<0){ //判断是否越界
return false;
}
if(location==0){//改变头节点的值
this.head.val = val
}else{//改变后面的值
let count = 0;
let head1 = this.head;
while(count<location&&head1!==null){//找到需要修改的位置
head1 = head1.next;
count++;
}
head1.val=val;//找到指定的位置后修改值
}
}
8、insert(location,val)
如果在头部插入:
在中间或尾部插入:
插入前:
插入后:
//向指定位置添加元素
ListNode.prototype.insert = function(location,val){
location = location-1//起始位置是从0开始的,而正常是从1开始
//判断链表的长度和位置,如果需要插入的位置大于链表长度或者位置小于0,返回false
if(this.length<location||location<0){
return false;
}
let newNode = new Node(val);//创建需要插入的节点
if(location==0){//如果在头部插入
newNode.next = this.head;//将节点的next指向head
this.head = newNode;//head指向第一个节点
}else{//否则找到需要插入的位置
let count = 0;
let head1 = this.head;
let head2 = null
while(count<location&&head1!==null){
head2 = head1;//找到需要插入的前一个节点
head1 = head1.next;//找到需要插入的后一个节点
count++;
}
head2.next = newNode;//将前一个节点的next指向插入的节点
newNode.next = head1;//将插入节点的next指向后一个节点
}
this.length++;
}
9、ReverseOrder()
如果链表只有一个节点或者为空,则不需要逆序。
否则
逆序过程的第一步图示如下:
以上步骤整理后:
//逆序
ListNode.prototype.ReverseOrder = function(){
let l1 = this.head;
if(l1===null||(l1!==null&&l1.next==null)){//判断链表为空或者只存在一个节点,直接返回
return l1;
}else{//链表存在两个及以上的节点
let head2 = l1.next;//让head指向l1的下一个节点
let head1 = l1;//head1指向第一个节点
while(head2.next!==null){
l1.next = head2.next;//断开第二个指针,让第一个节点和第三个节点连接
head2.next = head1;//将第一个节点☞向头节点
head1 = head2;//head1更新到头节点上
head2=l1.next;//head重新指向l1的第二个节点
}
if(head2!==null&&head2.next==null){//将最后一个节点指向头节点
l1.next = null;
head2.next = head1;
}
this.head = head2
}
}
完整链表的创建以及常用的方法。
//单链表的建立和基础操作
class Node{//创建一个节点类
constructor(val, next){
this.val = (val===undefined ? 0 : val)
this.next = (next===undefined ? null : next)
}
}
function ListNode(){//链表类
this.length = 0;
this.head = null;
}
//在链表尾部添加元素(val为节点的值)
ListNode.prototype.addNode = function(val){
//创建需要添加的节点
let newNode = new Node(val);
//判断链表是否为空,若不为空则在链表后面添加元素,否则它就是头节点
if(this.length==0){
this.head=newNode;
}else{
let head1 = this.head;
while(head1.next!==null){//找到链表的尾部
head1 = head1.next;
}
head1.next = newNode;//向链表的尾部添加节点
}
this.length++;//添加节点后对应的长度增加
}
ListNode.prototype.Length = function(){
return this.length;//返回它的长度,就是链表的长度
}
//打印链表
ListNode.prototype.display = function(){
let head1 = this.head;
let res = [];//用数组保存遍历的链表里的值
let i = 0;
while(head1!==null){//依次遍历链表,并将值存入数组
res[i] = head1.val;
head1 = head1.next;
i++;
}
return res;
}
//替换指定值
ListNode.prototype.ModifyValue = function(val1,val2){//将val1替换为val2
let location = this.findValue(val1);//查找到val1的位置
if(location==false){//如果没有查找到,返回false
return false;
}
let head1 = this.head;//如果查找到了,就找到那一个节点,并将值替换
while(head1!==null&&location>1){
head1 = head1.next;
location--;
}
head1.val = val2;
}
//查找指定的值,返回它的位置
ListNode.prototype.findValue = function(val){
let head1 = this.head;
let count = 0;
while(head1!==null){//依次遍历链表
if(head1.val==val){//如果找到了,就返回它的位置
count++;
return count;
}else{//没找到就一直往下找;
head1 = head1.next;
count++;
}
}
return false;//遍历完后,如果没找到就返回false
}
//删除指定节点
ListNode.prototype.DeleteValue = function(val){
let location = this.findValue(val);//查找到val的位置
if(location==false){//如果没找到,就返回false
return false;
}
let head1 = this.head;
let head2 = null;
if(location==1){//如果需要删除头结点,则直接让头结点指向下一个节点
this.head=this.head.next;
this.length--;
}else{
while(location>2&&head1.next!==null){//否则就找到它的上一个节点
head1 = head1.next;
location--;
}
head2 = head1.next.next;//找到他的下一个节点
head1.next = head2;//将它的上一个节点和下一个节点连接在一起
this.length--;
}
}
//修改指定位置的值
ListNode.prototype.ModifyLc = function(location,val){
location = location-1;//起始位置是从0开始的,而正常是从1开始
if(this.length<location||location<0){ //判断是否越界
return false;
}
if(location==0){//改变头节点的值
this.head.val = val
}else{//改变后面的值
let count = 0;
let head1 = this.head;
while(count<location&&head1!==null){//找到需要修改的位置
head1 = head1.next;
count++;
}
head1.val=val;//找到指定的位置后修改值
}
}
//向指定位置添加元素
ListNode.prototype.insert = function(location,val){
location = location-1//起始位置是从0开始的,而正常是从1开始
//判断链表的长度和位置,如果需要插入的位置大于链表长度或者位置小于0,返回false
if(this.length<location||location<0){
return false;
}
let newNode = new Node(val);//创建需要插入的节点
if(location==0){//如果在头部插入
newNode.next = this.head;//将节点的next指向head
this.head = newNode;//head指向第一个节点
}else{//否则找到需要插入的位置
let count = 0;
let head1 = this.head;
let head2 = null
while(count<location&&head1!==null){
head2 = head1;//找到需要插入的前一个节点
head1 = head1.next;//找到需要插入的后一个节点
count++;
}
head2.next = newNode;//将前一个节点的next指向插入的节点
newNode.next = head1;//将插入节点的next指向后一个节点
}
this.length++;
}
//逆序
ListNode.prototype.ReverseOrder = function(){
let l1 = this.head;
if(l1===null||(l1!==null&&l1.next==null)){//判断链表为空或者只存在一个节点,直接返回
return l1;
}else{//链表存在两个及以上的节点
let head = l1.next;//让head指向l1的下一个节点
let head1 = l1;//head1指向第一个节点
while(head.next!==null){
l1.next = head.next;//断开第二个指针,让第一个节点和第三个节点连接
head.next = head1;//将第一个节点☞向头节点
head1 = head;//head1更新到头节点上
head=l1.next;//head重新指向l1的第二个节点
}
if(head!==null&&head.next==null){//将最后一个节点指向头节点
l1.next = null;
head.next = head1;
}
this.head = head
}
}
let list = new ListNode();
list.addNode(1);
list.addNode(2);
list.addNode(3)
list.insert(4,5)
list.ModifyLc(4,9)
console.log("查找到的位置:",list.findValue(9))
console.log("链表的长度",list.Length());
console.log("链表的遍历:",list.display());
list.DeleteValue(9)
console.log("删除节点后链表的长度",list.Length());
console.log("删除节点后链表的遍历:",list.display());
list.ModifyValue(3,7)
console.log("替换后的链表",list.display())
// let list1 = list.head;
list.ReverseOrder();
console.log("逆序后的链表",list.display())