JavaScript数据结构与算法Item4--链表

要存储多个元素,数组(或列表)可能是最常用的数据结构,,每种语言都实现了数组。这种数据结构非常方便,提供了一个便利的[]语法来访问它的元素。然而,这种数据结构有一个缺点:(在大多数语言中)数组的大小是固定的,从数组的起点或中间插入或移除项的成本很高,因为需要移动元素.

链表存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置的。每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称指针或链接)组成。下图展示了一个链表的结构:

相对于传统的数组,链表的一个好处在于,添加或移除元素的时候不需要移动其他元素。然而,链表需要使用指针,因此实现链表时需要额外注意。数组的另一个细节是可以直接访问任何位置的任何元素,而要想访问链表中间的一个元素,需要从起点(表头)开始迭代列表直到找到所需的元素。

现实中也有一些链表的例子。一列火车是由一系列车厢(也称车皮)组成的。每节车厢或车皮都相互连接。你很容易分离一节车皮,改变它的位置,添加或移除它。

1 创建一个链表

理解了链表是什么之后,现在就要开始实现我们的数据结构了。以下是我们的LinkedList类的骨架:

function LinkedList() {
    var Node = function(element){ // {1}
        this.element = element;
        this.next = null;
    };
    var length = 0; // {2}
    var head = null; // {3}
    this.append = function(element){};  //向列表尾部添加一个新的项。
    this.insert = function(position, element){};//向列表的特定位置插入一个新的项
    this.removeAt = function(position){};//从列表的特定位置移除一项。
    this.remove = function(element){};//从列表中移除一项。
    this.indexOf = function(element){};//返回元素在列表中的索引。如果列表中没有该元素则返回-1
    this.isEmpty = function() {};//如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false
    this.size = function() {};//返回链表包含的元素个数。与数组的length属性类似
    this.toString = function(){};//
    this.print = function(){};//
}

LinkedList数据结构还需要一个Node辅助类(行{1})。Node类表示要加入列表的项。它包含一个element属性,即要添加到列表的值,以及一个next属性,即指向列表中下一个节点项的指针。

向链表尾部追加元素

向LinkedList对象尾部添加一个元素时,可能有两种场景:列表为空,添加的是第一个元素,或者列表不为空,向其追加元素。

this.append = function(element){
    var node = new Node(element), //{1}
    current; //{2}
    if (head === null){ //列表中第一个节点 //{3}
        head = node;
    } else {
        current = head; //{4}
        //循环列表,直到找到最后一项
        while(current.next){
            current = current.next;
        }
        //找到最后一项,将其next赋为node,建立链接
        current.next = node; //{5}
    }
    length++; //更新列表的长度 //{6}
};

从链表中移除元素

现在,让我们看看如何从LinkedList对象中移除元素。移除元素也有两种场景:第一种是移除第一个元素,第二种是移除第一个以外的任一元素。我们要实现两种remove方法:第一种是从特定位置移除一个元素,第二种是根据元素的值移除元素(稍后我们会展示第二种remove方法)。

this.removeAt = function(position){
    //检查越界值
    if (position > -1 && position < length){ // {1}
        var current = head, // {2}
        previous, // {3}
        index = 0; // {4}
        //移除第一项
        if (position === 0){ // {5}
            head = current.next;
        } else {
            while (index++ < position){ // {6}
                previous = current; // {7}
                current = current.next; // {8}
            }
            //将previous与current的下一项链接起来:跳过current,从而移除它
            previous.next = current.next; // {9}
        }
        length--; // {10}
        return current.element;
    } else {
        return null; // {11}
    }
};

在任意位置插入一个元素

接下来,我们要实现insert方法。使用这个方法可以在任意位置插入一个元素。我们来看一看它的实现:

this.insert = function(position, element){
    //检查越界值
    if (position >= 0 && position <= length){ //{1}
        var node = new Node(element),
        current = head,
        previous,
        index = 0;
        if (position === 0){ //在第一个位置添加
            node.next = current; //{2}
            head = node;
        } else {
            while (index++ < position){ //{3}
                previous = current;
                current = current.next;
            }
            node.next = current; //{4}
            previous.next = node; //{5}
        }
        length++; //更新列表的长度
        return true;
    } else {
        return false; //{6}
    }
};

toString方法

toString方法会把LinkedList对象转换成一个字符串。下面是toString方法的实现:

this.toString = function(){
    var current = head, //{1}
    string = ''; //{2}
    while (current) { //{3}
        string = current.element; //{4}
        current = current.next; //{5}
    }
    return string; //{6}
};

indexOf方法

indexOf是我们下一个要实现的方法。indexOf方法接收一个元素的值,如果在列表中找到它,就返回元素的位置,否则返回-1。来看看它的实现:

this.indexOf = function(element){
    var current = head, //{1}
    index = -1;
    while (current) { //{2}
        if (element === current.element) {
        return index; //{3}
        }
        index++; //{4}
        current = current.next; //{5}
    }
    return -1;
};

实现了这个方法,我们就可以实现remove等其他的方法:

this.remove = function(element){
    var index = this.indexOf(element);
    return this.removeAt(index);
};

isEmpty、size和getHead方法

isEmpty和size方法跟我们在上一章实现的一模一样。但我们还是来看一下:

this.isEmpty = function() {
    return length === 0;
};

如果列表中没有元素,isEmpty方法就返回true,否则返回false。

this.size = function() {
    return length;
};

size方法返回列表的length。和我们之前几章实现的类有所不同,列表的length是内部控
制的,因为LinkedList是从头构建的。最后还有getHead方法:

this.getHead = function(){
    return head;
};

总的结构代码:

function LinkedList(){
      function Node(element){
        this.element = element;
        this.next = null;
      }

      this.head = null;
      this.length = 0;

      this.append = function(element){
        var node = new Node(element),
            current;

        if(this.head == null){
          this.head = node;
        }else{
          current = this.head;
          while(current.next){
            current = current.next;
          }
        current.next = node;
        }
        this.length ++;
      }

      this.removeAt = function(position){
        var current = this.head,
        index = 0,
            prev;
        if(position > -1 && position < this.length){ //检查越界值
          if (position == 0){  //删除第一项,将head指向第一个元素即可;
            this.head = current.next;
          }
          else{
            while(index++ < position){ //将previous与current的下一项链接起来:跳过current,从而移除它,此处找到前一项;
              prev = current;
              current = current.next;
            }
            prev.next = current.next; //此处跳过current;
          }
          this.length --;
          return true;
        }else{
          return null;
        }
      }

      this.insert = function(position, element){
        var current = this.head,
        index = 0,
        prev,
        node = new Node(element);
        if(position> -1 && position < this.length){
          if(position == 0){
            this.head = node;
            node.next = current;
          }else{
            while(index++ < position){
              prev = current;
              current = current.next;
            }
            prev.next = node;
            node.next = current
          }
          this.length ++;
        }else{
          return false;
        }
      }

      this.indexOf = function(element){
        var current = this.head;
        index = -1;
        while(current){
          index ++;
          if(current.element === element){
            return index;
          }
          current = current.next;
        }
          return -1;
      }

      this.remove = function(element){
        var index = this.indexOf(element);
        this.removeAt(index);
      }

      this.isEmpty = function(){
        return this.length === 0;
      }

      this.size = function(){
        return this.length;
      }

      this.getHead = function(){
        return this.head;
      }

      this.toString = function(){
        var current = this.head,
            string = '';
            while(current){
              string += current.element + '-->';
              current = current.next;
            }
            return string;
      }



  }

  var linkedList = new LinkedList();
  linkedList.append(10);
  linkedList.append(15);
  linkedList.append(20);
  linkedList.append(25);
  linkedList.append(30);
  linkedList.append(35);
  console.log(linkedList.toString());
  linkedList.removeAt(2);
  console.log(linkedList.toString());
  linkedList.insert(2,111);
  console.log(linkedList.toString());
  console.log(linkedList.indexOf(10))

2 双向链表

链表有多种不同的类型,这一节介绍双向链表。双向链表和普通链表的区别在于,在链表中,
一个节点只有链向下一个节点的链接,而在双向链表中,链接是双向的:一个链向下一个元素,
另一个链向前一个元素,如下图所示:

先从实现DoublyLinkedList类所需的变动开始:

function DoublyLinkedList() {
    var Node = function(element){
        this.element = element;
        this.next = null;
        this.prev = null; //新增的

    };
    var length = 0;
    var head = null;
    var tail = null; //新增的
    //这里是方法
}

在任意位置插入一个新元素

向双向链表中插入一个新项跟(单向)链表非常类似。区别在于,链表只要控制一个next
指针,而双向链表则要同时控制next和prev(previous,前一个)这两个指针。

this.insert = function(position, element){
       var current = this.head,
       index = 0,
       prev,
       node = new Node(element);
       if(position> -1 && position < this.length){
         if(position == 0){
           if(!this.head){//插入空的链表中的首位
             this.head = node;
             this.tail = node;
           }else{
             this.head = node;
             node.next = current;
             current.prev = node;
           }
         }else if(position == this.length){
           current = tail;
           current.next = node;
           node.prev = current;
           this.tail = node;
         }else{
           while(index++ < position){
             previous = current;
             current = current.next;
           }
           previous.next = node;
           node.next = current;
           node.prev = previous;
           current.prev = node;
         }
         this.length ++;
         return true;
       }else{
         return false;
       }
     }

从任意位置移除元素

我们需要处理三种场景:从头部、从中间和从尾部移除一个元素。

this.removeAt = function(position){
       var current = this.head,
       index = 0,
           prev;
       if(position > -1 && position < this.length){ //检查越界值
         if (position == 0){  //删除第一项,将head指向第一个元素即可;
           this.head = current.next;
           if(this.length == 1){
             this.tail = null;
           }else{
             this.head.prev = null;
           }
         }else if(position == this.length -1){
           current = this.tail;
           this.tail = current.prev;
           this.tail.next = null;

         }
         else{
           while(index++ < position){ //将previous与current的下一项链接起来:跳过current,从而移除它,此处找到前一项;
             previous = current;
             current = current.next;
           }
           previous.next = current.next; //此处跳过current;
           current.next.prev = previous; 

         }
         this.length --;
         return current.element;
       }else{
         return null;
       }
     }

3 循环链表

循环链表可以像链表一样只有单向引用,也可以像双向链表一样有双向引用。循环链表和链
表之间唯一的区别在于,最后一个元素指向下一个元素的指针(tail.next)不是引用null,
而是指向第一个元素(head),如下图所示。


双向循环链表有指向head元素的tail.next,和指向tail元素的head.prev。

双向链表总程序

function DoubleLinkedList(){
     function Node(element){
       this.element = element;
       this.prev = null;
       this.next = null;
     }

     this.head = null;
     this.tail = null;
     this.length = 0;

     this.append = function(element){
       var node = new Node(element),
           current;

       if(this.head == null){
         this.head = node;
         this.tail = node;
       }else{
         current = this.head;
         while(current.next){
           current = current.next;
         }
       current.next = node;
       node.prev = current;
       this.tail = node;
       }
       this.length ++;
     }

     this.removeAt = function(position){
       var current = this.head,
       index = 0,
           prev;
       if(position > -1 && position < this.length){ //检查越界值
         if (position == 0){  //删除第一项,将head指向第一个元素即可;
           this.head = current.next;
           if(this.length == 1){
             this.tail = null;
           }else{
             this.head.prev = null;
           }
         }else if(position == this.length -1){
           current = this.tail;
           this.tail = current.prev;
           this.tail.next = null;

         }
         else{
           while(index++ < position){ //将previous与current的下一项链接起来:跳过current,从而移除它,此处找到前一项;
             previous = current;
             current = current.next;
           }
           previous.next = current.next; //此处跳过current;
           current.next.prev = previous; 

         }
         this.length --;
         return current.element;
       }else{
         return null;
       }
     }

     this.insert = function(position, element){
       var current = this.head,
       index = 0,
       prev,
       node = new Node(element);
       if(position> -1 && position < this.length){
         if(position == 0){
           if(!this.head){//插入空的链表中的首位
             this.head = node;
             this.tail = node;
           }else{
             this.head = node;
             node.next = current;
             current.prev = node;
           }
         }else if(position == this.length){
           current = tail;
           current.next = node;
           node.prev = current;
           this.tail = node;
         }else{
           while(index++ < position){
             previous = current;
             current = current.next;
           }
           previous.next = node;
           node.next = current;
           node.prev = previous;
           current.prev = node;
         }
         this.length ++;
         return true;
       }else{
         return false;
       }
     }

     this.indexOf = function(element){
       var current = this.head;
       index = -1;
       while(current){
         index ++;
         if(current.element === element){
           return index;
         }
         current = current.next;
       }
         return -1;
     }

     this.remove = function(element){
       var index = this.indexOf(element);
       this.removeAt(index);
     }

     this.isEmpty = function(){
       return this.length === 0;
     }

     this.size = function(){
       return this.length;
     }

     this.getHead = function(){
       return this.head;
     }

     this.toString = function(){
       var current = this.head,
           string = '';
           while(current){
             string += current.element + '-->';
             current = current.next;
           }
           return string;
     }
}
  var linkedList = new DoubleLinkedList();
  linkedList.append(10);
  linkedList.append(15);
  linkedList.append(20);
  linkedList.append(25);
  linkedList.append(30);
  linkedList.append(35);
  console.log(linkedList.toString());
  linkedList.removeAt(2);
  console.log(linkedList.toString());
  linkedList.insert(2,111);
    console.log(linkedList.toString());
  console.log(linkedList.indexOf(10))
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值