JavaScript模拟链表

1,470 阅读10分钟

0 链表

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

  • 相对于数组, 链表有一些优点:
    • 内存空间不是比是连续的. 可以充分利用计算机的内存. 实现灵活的内存动态管理.
    • 链表不必在创建时就确定大小, 并且大小可以无限的延伸下去.
    • 链表在插入和删除数据时, 时间复杂度可以达到O(1). 相对数组效率高很多.
  • 相对于数组, 链表有一些缺点:
    • 链表访问任何一个位置的元素时, 都需要从头开始访问.(无法跳过第一个元素访问任何一个元素).
    • 无法通过下标直接访问元素, 需要从头一个个访问, 直到找到对应的问题.

1 单向链表

参考自https://www.jianshu.com/p/7a2d072a6c3e 和书籍<<JavaScript数据结构与算法(第二版)>>

单向链表(单链表)是链表的一种,它由节点组成,每个节点都包含下一个节点的指针

缺点:

  • 回到前一个节点是很难

定义单向链表:

    //声明单向链表类
    function LinkedList(){
        //要加入列表的项 element表示要添加列表的值
        let Node = function ( element) {
            this.element = element;//当前节点的值
            this.next = null;//下一个节点的引用/指针
        }
        //列表项的个数
        let length = 0;
        //链表的第一个节点
        let head = null;

        /**
         * 添加一个节点到链表中
         * @param element 需要添加的元素
         */
        this.append = function ( element ) {
            //列表为空,添加的是第一个元素,或者列表不为空,向其追加元素

            //创建一个节点
            let node = new Node(element);
            let current = null;
            //判断链表中的第一个节点是否存在,不存在,就把当前节点作为头节点添加
           if(head===null){
               head = node;
           }else{
               //链表不为空,从头遍历链表,找到最后一个节点,在其后面添加当前节点,建立链接
               current = head;
               while(current.next){//最后一个节点的next属性值始终是null
                   current = current.next;
               }
                current.next = node;
           }
           //更新链表的长度
           length++;
        }
        /**
         * 移除指定位置的节点,返回节点的元素值
         * @param position 移除的位置
         * @return {*}  移除的元素
         */
        this.removeAt = function ( position ) {
            //检测是否越界(有效值0-length-1)
            if(position>=0&&position<=length-1){
                let current = head;
                let previous = null;//上一个节点
                let index = 0;
                //如果移除的是第一个节点,就把第一个节点的下一项作为头节点
                if(position==0){
                    head = current.next;
                }else{
                    //移除不是第一个节点,就遍历链表,找到指定位置的节点,
                    while(index!=position){
                        index++;
                        previous = current;
                        current = current.next;
                    }
                    //将指定节点的下一个节点作为上一个节点的next
                    previous.next = current.next;
                }
                length--;//更新长度
                return current.element;//返回被删除的节点的值
            }else{
                //无效位置,什么也不做
                return null;
            }
        }

        /**
         * 向链表的指定位置插入一个节点
         * @param position 插入位置
         * @param element 添加的元素
         * @return {boolean} 插入结果
         */
        this.insert = function ( position,element ) {
            //检测插入位置是否有效(有效值:0到length;其中length代表添加到末尾)
            if(position>=0&&position<=length){
                let node = new Node(element);
                let current = head;
                let index = 0;
                let previous = null;

                //判断是否插入到第一个节点的位置,
                if(position==0){
                    //插入第一个节点的位置,将第一个节点作为node.next的值,并且head指向node
                    node.next = current;
                    head = node;
                }else{
                    //插入不是第一个节点的位置,变量链表,找到指定位置的节点,
                    while(index!=position){
                        index++;
                        previous = current;
                        current = current.next;
                    }
                    //将node的next指向当前节点,再将当前节点的上一个节点的next指向node,建立连接
                    node.next = current;
                    previous.next = node;
                }
                length++;
                return true;//添加成功
            }else{
                //添加失败
                return false;
            }
        }
        /**
         * 返回元素在链表中的位置,找不到就返回-1
         * @param targetElement 需要移除的元素
         * @return {number} 被移除元素在链表中的位置
         */
        this.indexOf = function ( targetElement ) {
            let index =0;
            let current = head;
            //检测节点是否存在,存在就比较节点的值是否等于目标元素,找到返回位置
            while(current){
                if(current.element==targetElement){//如果找到,就返回位置
                    return index;
                }
                index++;
                current=current.next;//下一个节点
            }
            //链表中没有该元素,返回-1
            return -1;
        }
        //移除指定节点
        this.remove = function ( element ) {
            //找到元素在链表中的位置
            let index = this.indexOf(element);
            //通过位置删除节点
            return this.removeAt(index);
        }

        //检测链表是否有节点,没有返回true,否则返回false
        this.isEmpty = function (  ) {
            return length==0;
        }
        //返回链表的长度(元素个数)
        this.size = function (  ) {
            return length;
        }
        //返回链表的第一个节点
        this.getHead = function (  ) {
            return head;
        }
        //声明链表的toString方法:只输出元素的值
        // 把LinkedList对象转换成一个字符串,取出每个节点,将值拼接为字符串
        this.toString = function (  ) {
            let str = "";
            let current = head;
            while(current){
                str+=current.element+(current.next?"->":"");
                current = current.next;
            }
            return str;
        }

    }

验证:

    let linkList1 = new LinkedList();
    console.log ( linkList1.isEmpty () );//true
    linkList1.append("jack");
    linkList1.append("rose");
    linkList1.append("aimer");
    console.log ( linkList1.toString() );//jack->rose->aimer

    //插入元素 头部 中间 尾部
    linkList1.insert(0,"noeH");
    console.log ( linkList1.toString() );//noeH->jack->rose->aimer
    linkList1.insert(2,"noeC");
    console.log ( linkList1.toString() );//noeH->jack->noeC->rose->aimer
    linkList1.insert(5,"noeT");
    console.log ( linkList1.toString() );//noeH->jack->noeC->rose->aimer->noeT
    // //获取位置
    console.log ( linkList1.indexOf ( "aimer" ) );//4
    //
    console.log ( linkList1.removeAt ( 1 ) );//jack
    console.log ( linkList1.toString() );//noeH->noeC->rose->aimer->noeT

    console.log ( linkList1.remove ( "rose" ) );//rose
    console.log ( linkList1.size () );//4
    console.log ( linkList1.isEmpty () );//false
    console.log ( linkList1.toString () );//noeH->noeC->aimer->noeT

2 单向链表(使用原型)

定义:

   function LinkedList() {
       //封装一个node类,保存每个节点信息
      function Node( element ) {
           this.element = element;//当前节点的值
           this.next = null;//下一个节点的引用/指针
       }
       this.length = 0;//链表的长度,bug1 let length
       this.head = null;  //链表的第一个节点

       //链表尾部添加元素
       LinkedList.prototype.append = function ( element ) {
           //1 根据元素创建节点
           let newNode = new Node(element);

           //2 判断链表是否为空
           if(this.head==null){//链表为空,newNode作为头节点添加进去,
               this.head = newNode;
           }else{
               //2.1 链表不为空,定义变量存储当前节点
               let current = head;
               //2.2 遍历链表,找最后一个节点
               while ( current.next ) {
                   current = current.next;
               }
               //2.3 新节点添加到当前节点(最后一个节点)的后面
               current.next = newNode;
           }
           //3 链表长度增加1
           this.length++;
       }
       //任意位置插入元素
       LinkedList.prototype.insert = function ( position,element ) {
           //1 检测边界:如果插入位置无效,插入失败,返回false
           if(position<0||position>this.length){
               return false;
           }
           //2 根据元素创建新节点
           let newNode = new Node(element);

           //3 判断插入位置
           if(position==0){
               //插入链表的开始位置,newNode.next指向head;并且head指向newNode
               newNode.next = this.head;
               this.head = newNode;
           }else{
               //3.1 插入非开始位置
               //定义变量存储当前节点和当前节点的位置
               let current = head;
               let index = 0;
               let previous = null;//存储当前节点的上一个节点

               //查找position位置的节点
               while(index<position){
                   previous = current;
                   current = current.next;
                   index++;
               }
               //将newNode.next指向current节点,previous.next指向newNode节点
               newNode.next = current;
               previous.next = newNode;
           }
           //4 链表长度增加1,并返回true,添加成功
           this.length++;
           return true;
       }

       //移除任意位置的元素
       LinkedList.prototype.removeAt = function ( position ) {
           //1 检测越界:越界不做操作,返回null
           if(position<0||position>=this.length){
               return null;
           }

           //定义变量存储当前节点和当前节点的位置
           let index = 0;
           let current = head;
           let previous = null;//存储当前节点的上一个节点

           //2 判断移除的位置
           if(position==0){
               //2.1 移除第一个节点,设置head指向head.next即可
               this.head = head.next;
           }else{
               //2.2 移除非第一个节点
               //查找position位置的节点
               while(index<position){
                   previous = current;
                   current = current.next;
                   index++;
               }
               //移除当前节点:previous.next指向current.next
               previous.next = current.next;
           }
           //3 链表长度减1,并返回被移除的元素
           this.length--;
           return current.element;
       }

       //获取元素在链表中的位置
       LinkedList.prototype.indexOf = function ( element ) {
           //1 定义变量存储信息
           let current = this.head;
           let index =0;//current节点在链表的位置

           //2 链表中查找元素
           while(current){
               //判断当前节点的元素是否等于目标元素
               if(current.element==element){
                   //找到元素,返回位置
                   return index;
               }
               current=current.next;
               index++;
           }

           //3 遍历链表都没有找到元素,返回-1
           return -1;
       }
       //删除元素
       LinkedList.prototype.reomve = function ( element ) {
           let index = this.indexOf(element);//获取节点在链表中的位置
           return this.removeAt(index);//根据节点位置移除节点,返回节点的值
       }
       //获取第一个节点
       LinkedList.prototype.getHead = function (  ) {
           return this.head;
       }
       //获取链表的长度
       LinkedList.prototype.size = function (  ) {
           return this.length;
       }
       //判断链表是否有节点
       LinkedList.prototype.isEmpty = function (  ) {
           return this.length==0;
       }
       //链表的toString方法
       LinkedList.prototype.toString = function (  ) {
           //定义变量,存储当前节点和节点的值
           let current = this.head;
           let str = "";
           while(current){
               str+= current.element+(current.next?",":"");
               current = current.next;
           }
           return str;
       }
   }

验证:

    let lk = new LinkedList();
    console.log ( lk.size());

3 双向链表

  • 既可以从头遍历到尾, 又可以从尾遍历到头
  • 一个节点既有向前连接的引用, 也有一个向后连接的引用.

缺点:

  • 每次在插入或删除某个节点时, 需要处理四个节点的引用, 而不是两个. 实现比较困难
  • 占用内存空间大

定义:

    //创建双向链表的构造函数
    function DoublyLinkedList(){
        //创建节点的构造函数
        function Node( element ) {
            this.element = element;//元素
            this.prev = null;//上一个节点
            this.next = null;//下一个节点
        }

        //定义属性
        this.length = 0;//链表长度
        this.head = null;//链表第一个节点
        this.tail = null;//链表的最后一个节点

        //尾部添加元素
        DoublyLinkedList.prototype.append = function ( element ) {
            //根据元素创建新节点
            let newNode = new Node(element);
            if(this.head==null){
                //链表中没有节点,新节点newNode直接添加,head和tail都指向newNode
                this.head = newNode;
                this.tail = newNode;
            }else{
                //链表不为空,新节点添加到最后一个节点的后面,并且新节点的prev指向之前的最后一个节点
                this.tail.next =newNode;
                newNode.prev = this.tail;
                this.tail = newNode;
            }
            this.length++;
        }

        //正向遍历链表,转换为字符串返回
        DoublyLinkedList.prototype.forwardString = function (  ) {
            let current = this.head;
            let str = "";
            while(current){
                str+=current.element+(current.next?",":"");
                current=current.next;//取出下一个节点
            }
            return str;
        }

        //正向遍历链表,转换为字符串返回
        DoublyLinkedList.prototype.reverseString = function (  ) {
            let current = this.tail;//当前节点为最后一个节点
            let str = "";
            while(current){
                str+=current.element+(current.prev?",":"");
                current=current.prev;//取出上一个节点
            }
            return str;
        }
        //toString方法正向遍历
        DoublyLinkedList.prototype.toString = function (  ) {
            return this.forwardString();
        }
        //在任意位置插入元素
        DoublyLinkedList.prototype.insert = function ( position,element ) {
            console.log ( this.length );
            //检测边界:越界就插入失败,返回false
            if(position==undefined||position<0||position>this.length){
                return false;
            }
            //创建新节点
            let newNode = new Node(element);

            //3.1 插入开始节点的位置
            if(position==0){
                //1 链表为空,head和tail指向newNode
                if(this.head==null){
                    this.head = newNode;
                    this.tail = newNode;
                }else{
                    //2 链表不为空,新节点的next指向头节点,头节点的prev指向新节点,head指向新节点
                    newNode.next = this.head;
                    this.head.prev = newNode;
                    this.head = newNode;
                }
            }else if(position==this.length){
                //3.2 插入尾部节点的后面,新节点prev指向尾部节点,尾部节点next指向新节点,tail指向新节点
                newNode.prev = this.tail;
                this.tail.next = newNode
                this.tail = newNode;
            }else{
                //3,3 插入中间位置,
                //定义变量,存储信息
                let current = this.head;
                let index = 0;
                let previous = null;

                //从头节点变量链表,找到position位置的节点
                while(index<position){
                    index++;
                    previous = current;
                    current = current.next;
                }
                //新节点的next指向current节点,新节点的prev指向previous节点
                //current.prev指向新节点,previous.next指向新节点
                newNode.next = current;
                newNode.prev = previous;
                current.prev = newNode;
                previous.next = newNode;
            }

            //插入成功,链表长度加1,返回true
            this.length++;
            return true;
        }

        //移除任意位置的元素,成功移除,返回该元素,移除失败,返回null
        DoublyLinkedList.prototype.removeAt = function ( position ) {
            //边界检测
            if(position<0||position>=this.length){
                return null;
            }
            let current = this.head;

            if(position==0){
                //1 移除开始位置的节点
                if(this.length==1){
                    //链表只有一个节点,head和tail赋值为null
                    this.head = null;
                    this.tail = null;
                }else{
                    //链表有多个节点,head指向head的next节点,head的prev赋值为null
                    this.head = head.next;
                    this.head.prev = null;
                }
            }else if(position==this.length-1){
                //移除末尾位置的节点,tail指向tail的prev节点,tail.next赋值为null
                current = this.tail;//取出tail节点给current变量,方便取出元素值
                this.tail = this.tail.prev;
                this.tail.next = null;
            }else{
                //移除中间位置的节点
                let previous = null;
                let index = 0;
                //找到指定位置的节点
                while(index<position){
                    index++;
                    previous = current;
                    current = current.next;
                }
                //previous的next指向current.next节点
                //current.next节点的prev指向previous节点
                previous.next = current.next;
                current.next.prev = previous;
            }
            this.length--;//长度减1
            return current.element; //返回元素
        }

        //获取元素位置
        DoublyLinkedList.prototype.indexOf = function ( element ) {
            //存储节点和节点位置
            let current = this.head;
            let index = 0;

            //遍历链表,查找元素,找到就返回对应的位置
            while(current){
                if(current.element==element){
                    return index;
                }
                index++;
                current=current.next;
            }
            //链表中没有找到元素,返回-1
            return -1;
        }
        //删除指定元素
        DoublyLinkedList.prototype.remove = function ( element ) {
            let index = this.indexOf(element);
            return this.removeAt(index);
        }
        //判断链表是否为空
        DoublyLinkedList.prototype.isEmpty = function (  ) {
            return this.length==0;
        }
        //获取链表长度
        DoublyLinkedList.prototype.size = function (  ) {
            return this.length;
        }
        //获取第一个元素
        DoublyLinkedList.prototype.getHeadElement = function (  ) {
            return this.head.element;
        }
        //获取最后一个元素
        DoublyLinkedList.prototype.getTailElement = function (  ) {
            return this.tail.element;
        }
    }

使用:

    //测试双向链表
    let dLinklist = new DoublyLinkedList();
    console.log ( dLinklist.isEmpty () );//false

    dLinklist.append("Lisa");
    dLinklist.append("aimer");
    dLinklist.append("rose");
    console.log ( dLinklist.toString () );
    console.log ( dLinklist.size () );