数据结构——链表——单向链表

172 阅读5分钟

链表:用于存储数据的线性结构

特点:

  • 链表中的元素在内存中不必是连续的空间.
  • 链表的每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(有些语言称为指针或者链接)组成.

与数组相比较:

优点:

  • 内存空间不是比是连续的. 可以充分利用计算机的内存. 实现灵活的内存动态管理.
  • 链表不必在创建时就确定大小, 并且大小可以无限的延伸下去.
  • 链表在插入和删除数据时, 时间复杂度可以达到O(1). 相对数组效率高很多.

缺点:

  • 链表访问任何一个位置的元素时, 都需要从头开始访问.(无法跳过第一个元素访问任何一个元素).
  • 无法通过下标直接访问元素, 需要从头一个个访问, 直到找到对应的位置.

单向链表:

  • 只能从头遍历到尾或者从尾遍历到头(一般从头到尾)
  • 也就是链表相连的过程是单向的. 实现的原理是上一个链表中有一个指向下一个的引用.
  • 单向链表有一个比较明显的缺点:
    • 我们可以轻松的到达下一个节点, 但是回到前一个节点是很难的. 但是, 在实际开发中, 经常会遇到需要回到上一个节点的情况
    • 举个例子: 假设一个文本编辑用链表来存储文本. 每一行用一个String对象存储在链表的一个节点中. 当编辑器用户向下移动光标时, 链表直接操作到下一个节点即可. 但是当用于将光标向上移动呢? 这个时候为了回到上一个节点, 我们可能需要从first开始, 依次走到想要的节点上.

图解:

1.当链表中还没有节点时,设置头结点指向null,链表长度为0

创建空链表:

 class LinkList{
        constructor(){
        //头结点为空(头结点指向为空)
            this.head=null
            //链表长度为0
            this.length=0
        }   
     }

image.png

2.当链表中有节点时,头结点指向第一个节点,链表中最后一个元素指向null。(注意:这里的节点需要自己创建)

创建节点元素:

封装一个构造函数,用于保存元素和及指向下一个元素

 class Londe{
        constructor(data){
            // 存放数据
            this.data=data;
            // 指向下一个节点(引用)
            this.next=null;
        }
    }

image.png

对链表进行操作(在链表中添加方法):

1.向链表最后添加元素 append(ele)

 append(ele){
            // 创建新节点
            let newnode=new Londe(ele);
            // 判断头结点是否为空
            if(this.head==null){
                //头结点指向新节点 
                this.head=newnode
            }else{
                // 声明一个current指向头结点的指向
                let current=this.head
                while(current.next!=null){
                    // 当current的指向一直不为空时,
                    // 就随着当时指向的元素节点继续指向它的下一个元素节点
                    // 直到current的指向为空,就不再执行
                    current=current.next
                }  
                
                // 当current 表示最后一个节点时
                //令current的指向指向新节点
                // 这就是向链表尾部添加新节点
                current.next=newnode   
               }
            //    添加了元素,length长度就+1
               this.length++
        }  

图解:

1.current=this.head

image.png

2.current=current.next

image.png

3.current.next=newnode

image.png

2.向链表中的特定位置插入一个新的节点 insert(position,ele)

 insert(position,ele){
            // 位置是否合法
            // Number.isInteger():用来判断给定的参数是否为整数
            if(position<0||position>this.length||!Number.isInteger(position)){
                return false
            }
            // 创建一个新节点
            let  newnode=new Londe(ele)
            // 2.1在头部位置插入
            if(position==0){
                if(this.head==null){
                    // 如果头部指向空,那么就让头部指向新的节点
                    this.head=newnode
                }else{
                    // 如果头部不为空,
                    // 那么就先让新节点指向头部指向的节点
                    // 再让头部指向新节点
                    newnode.next=this.head
                    this.head=newnode 
                }
                // 插入了一个新节点,链表中的节点数就+1
                this.length++
            }else if(position==this.length){
                // 2.2在尾部位置插入
                // 就直接使用上面的向链表最后添加元素的方法,就行
                this.append(ele)

            }else{
                // 2.3在任意位置插入
                // 创建一个变量,让它指向链表中头部指向的节点
                let current=this.head;
                // 创建一个变量index,方便知道节点的位置,以及current的具体指向节点位置
                let index=0
                // 前一个节点   position-1位置的节点
                while(index<position-1){
                    // 已知你想要插入节点的准确位置
                    // 从链表的第一个位置开始比较
                    // 通过index改变移动current指向的节点
                    // 直到index=position-1时,结束
                    current=current.next;
                    index++
                }
                // current就是插入节点位置的前一个节点
                newnode.next=current.next
                current.next=newnode
                this.length++
            }
        }
        

图解:

1.position=0,this.length=0

image.png

2.position=0,this.length!=0

image.png

3.position=this.length,使用append(ele)方法

4.0<position&&position<this.length

image.png

image.png

image.png

3.从链表的特定位置移除一项 removeAt(position)

 removeAt(position){
            // 位置是否合法
            if(position<0||position>this.length-1||!Number.isInteger(position)){
                return false
            }
            if(this.head==null){
                // 空链表
                return 
            }else{
                // 非空链表
                if(position==0){
                    // 移除头部元素
                    this.head=this.head.next
                }else{
                    // 移除其他位置的元素
                    let current=this.head,index=0;
                    while(index<position-1){
                        // 已知你想要移除节点的准确位置
                    // 从链表的第一个位置开始比较
                    // 通过index改变移动current指向的节点
                    // 直到index=position-1时,结束
                        current=current.next;
                        index++
                    }
                    // 将移除节点的前面一个节点指向移除节点的后面一个节点
                    current.next=current.next.next
                }
                // 移除节点,链表中节点数-1
                this.length--
            }
        }
        

图解:

1.当length=0,就不操作

2.position=0

image.png

3.position>0

image.png

image.png

4.返回元素在链表中的索引,如果链表中没有该元素则返回-1. indexOf(ele)

 indexOf(ele){
            let current=this.head,index=0
            while(index<this.lengthgth){
                // 遍历链表中的所有节点
                if(current.data==ele){
                    // 如果current指向的节点中的数据与我们查找的指定元素一致
                    // 就返回当前current指向的节点的位置
                    return index
                }else{
                    // 如果current指向的节点中的数据与我们查找的指定元素不一致
                    // 继续找
                    // 并且,index位置向后移动,,也就是+1
                    // current的指向,随着index的位置改变
                    current=current.next
                    index++
                }
            }
            // 找完了所有,都没有找到,返回-1
            // 表明链表中没有相对应的节点
            return -1
        }
        

图解:

1.

image.png

2.

image.png

3.如果一直没有匹配上,就返回-1

5.从链表中移除指定元素 remove(ele)

    remove(ele){
            // 查询指定元素所在的节点位置
            let index=this.indexOf(ele)
            // 移除该位置的指定元素
            this.removeAt(index)
        }

6.如果链表中不包含任何元素,返回true,如果链表长度大于0,则返回false。 isEmpty()

       isEmpty(){
            if(this.length==0){
                return true
            }else{
                return false
            }
        }

7.返回链表包含的元素个数。与数组的length属性类似。 size()

 size(){
        return this.length
       }

8.由于链表项使用了node类,就需要重写继承自javaScript对象默认的toString方法,让其只输出元素的值 toString()

toString(){
            let current=this.head,index=0,res="";
            while(index<this.length){
                // 将current指向的节点的数据连接为字符串
                res+="-"+current.data;
                current=current.next ;
                index++
           }
        //    利用字符串的slice(start,end)方法, 提取字符串的片断,并在新的字符串中返回被提取的部分。
           return res.slice(1)
        }
        

完整的代码:

<script>
    // 封装一个构造函数,用于保存元素和元素的下一个引用
    class Londe{
        constructor(data){
            // 存放数据
            this.data=data;
            // 指向下一个节点(引用)
            this.next=null;
        }
    }
    class LinkList{
        constructor(){
            this.head=null
            this.length=0
        }   
        // 1.append()  向链表最后添加元素
        append(ele){
            // 创建新节点
            let newnode=new Londe(ele);
            // 判断头结点是否为空
            if(this.head==null){
                //头结点指向新节点 
                this.head=newnode
            }else{
                // 声明一个current指向头结点的指向
                let current=this.head
                while(current.next!=null){
                    // 当current的指向一直不为空时,
                    // 就随着当时指向的元素节点继续指向它的下一个元素节点
                    // 直到current的指向为空,就不再执行
                    current=current.next
                }  
                
                // 当current 表示最后一个节点时
                //令current的指向指向新节点
                // 这就是向链表尾部添加新节点
                current.next=newnode   
               }
            //    添加了元素,length长度就+1
               this.length++
        }  
        // 2.insert(指定位置,插入的元素)
        insert(position,ele){
            // 位置是否合法
            // Number.isInteger():用来判断给定的参数是否为整数
            if(position<0||position>=this.length||!Number.isInteger(position)){
                return false
            }
            // 创建一个新节点
            let  newnode=new Londe(ele)
            // 2.1在头部位置插入
            if(position==0){
                if(this.head==null){
                    // 如果头部指向空,那么就让头部指向新的节点
                    this.head=newnode
                }else{
                    // 如果头部不为空,
                    // 那么就先让新节点指向头部指向的节点
                    // 再让头部指向新节点
                    newnode.next=this.head
                    this.head=newnode 
                }
                // 插入了一个新节点,链表中的节点数就+1
                this.length++
            }else if(position==this.length){
                // 2.2在尾部位置插入
                // 就直接使用上面的向链表最后添加元素的方法,就行
                this.append(ele)

            }else{
                // 2.3在任意位置插入
                // 创建一个变量,让它指向链表中头部指向的节点
                let current=this.head;
                // 创建一个变量index,方便知道节点的位置,以及current的具体指向节点位置
                let index=0
                // 前一个节点   position-1位置的节点
                while(index<position-1){
                    // 已知你想要插入节点的准确位置
                    // 从链表的第一个位置开始比较
                    // 通过index改变移动current指向的节点
                    // 直到index=position-1时,结束
                    current=current.next;
                    index++
                }
                // current就是插入节点位置的前一个节点
                newnode.next=current.next
                current.next=newnode
                this.length++
            }
        }
        // 3.removeAt(position)  移除指定位置的元素
        removeAt(position){
            // 位置是否合法
            if(position<0||position>=this.length-1||!Number.isInteger(position)){
                return false
            }
            if(this.head==null){
                // 空链表
                return 
            }else{
                // 非空链表
                if(position==0){
                    // 移除头部元素
                    this.head=this.head.next
                }else{
                    // 移除其他位置的元素
                    let current=this.head,index=0;
                    while(index<position-1){
                        // 已知你想要移除节点的准确位置
                    // 从链表的第一个位置开始比较
                    // 通过index改变移动current指向的节点
                    // 直到index=position-1时,结束
                        current=current.next;
                        index++
                    }
                    // 将移除节点的前面一个节点指向移除节点的后面一个节点
                    current.next=current.next.next
                }
                // 移除节点,链表中节点数-1
                this.length--
            }
        }
        // 4.indexOf(ele)  查找指定元素的位置,存在返回index,不存在返回-1
        indexOf(ele){
            let current=this.head,index=0
            while(index<this.length){
                // 遍历链表中的所有节点
                if(current.data==ele){
                    // 如果current指向的节点中的数据与我们查找的指定元素一致
                    // 就返回当前current指向的节点的位置
                    return index
                }else{
                    // 如果current指向的节点中的数据与我们查找的指定元素不一致
                    // 继续找
                    // 并且,index位置向后移动,,也就是+1
                    // current的指向,随着index的位置改变
                    current=current.next
                    index++
                }
            }
            // 找完了所有,都没有找到,返回-1
            // 表明链表中没有相对应的节点
            return -1
        }
        // 5.remove(ele)  移除指定元素
        remove(ele){
            // 查询指定元素所在的节点位置
            let index=this.indexOf(ele)
            // 移除该位置的指定元素
            this.removeAt(index)
        }
        // 6.将链表中的数据连接为字符串
        toString(){
            let current=this.head,index=0,res="";
            while(index<this.length){
                // 将current指向的节点的数据连接为字符串
                res+="-"+current.data;
                current=current.next ;
                index++
           }
        //    利用字符串的slice(start,end)方法, 提取字符串的片断,并在新的字符串中返回被提取的部分。
           return res.slice(1)
        }
        // 7.isEmpty():如果链表中不包含任何元素,返回true,如果链表长度大于0,则返回false。
        isEmpty(){
            if(this.length==0){
                return true
            }else{
                return false
            }
        }
        // 8.size():返回链表包含的元素个数。与数组的length属性类似。  
       size(){
        return this.length
       }
    }
    // 测试代码
       let list=new LinkList();
       for(let i=0;i<5;i++){
        list.append(i)
       }
       list.removeAt(4)
       console.log(list)