JavaScript数据结构04 - 链表

一、定义

1.1 观点

前面我们进修了数组这类数据结构。数组(或许也能够称为列表)是一种异常简朴的存储数据序列的数据结构。在这一节,我们要进修怎样完成和运用链表这类动态的数据结构,这意味着我们能够从中恣意增加或移除项,它会按需举行扩容。

要存储多个元素,数组(或列表)多是最经常使用的数据结构,它供应了一个方便的[]语法来接见它的元素。但是,这类数据结构有一个瑕玷:(在大多数强范例语言中)数组的大小是牢固的,须要预先分派,从数组的出发点或中心插进去或移除项的本钱很高,因为须要挪动元素。
(注重:在JavaScript中数组的大小随时可变,不须要预先定义长度)

链表存储有序的元素鸠合,但不同于数组,链表中的元素在内存中并非一连安排的。每一个元素由一个存储元素自身的节点和一个指向下一个元素的援用(也称指针或链接)构成。

相对于传统的数组,链表的一个长处在于,增加或删除元素的时刻不须要挪动其他元素。但是,链表须要运用指针,因而完成链表时须要分外注重。数组的另一个细节是能够直接接见任何位置的元素,而想要接见链表中心的一个元素,须要从出发点(表头)最先迭代列表直到找到所需的元素。

火车能够当作生涯中一个典范的链表的例子。一列火车是由一系列车箱构成的。每节车箱都相互连接。你很轻易星散一节车箱,转变它的位置,增加或移除它。

1.2 分类

链表最经常使用的有三类

  1. 单向链表
  2. 双向链表
  3. 轮回链表

二、链表的完成

2.1 单向链表

建立单向链表类:

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

  var length = 0;
  var head = null;
  
  this.append = function (element) {};
  this.insert = function (position, element) {};
  this.removeAt = function (position) {};
  this.remove = function (element) {};
  this.indexOf = function (element) {};
  this.isEmpty = function () {};
  this.size = function () {};
  this.getHead = function () {};
  this.toString = function () {};
  this.print = function () {};
}

SinglyLinkedList须要一个辅佐类Node。Node类示意要到场链表的项。它包括一个element属性,即要增加到链表的值,以及一个next属性,即指向链表中下一个节点项的指针。

链表内里有一些声明的辅佐要领:

  • append(element):向链表尾部增加新项
  • insert(position, element):向链表的特定位置插进去一个新的项
  • removeAt(position):从链表的特定位置移除一项
  • remove(element):从链表中移除一项
  • indexOf(element):返回元素在链表中的索引。假如链表中没有该元素则返回-1
  • isEmpty():假如链表中不包括任何元素,返回true,假如链表长度大于0,返回false
  • size():返回链表包括的元素个数,与数组的length属性相似
  • getHead():返回链表的第一个元素
  • toString():因为链表运用了Node类,就须要重写继续自JavaScript对象默许的toString()要领,让其只输出元素的值
  • print():打印链表的一切元素

下面我们来逐一完成这些辅佐要领:

  // 向链表尾部增加一个新的项
  this.append = function (element) {
    var node = new Node(element);
    var currentNode = head;

    // 推断是不是为空链表
    if (currentNode === null) {
      // 空链表
      head = node;
    } else {
      // 从head最先一向找到末了一个node
      while (currentNode.next) {
        // 背面另有node
        currentNode = currentNode.next;
      }
      currentNode.next = node;
    }

    length++;
  };

  // 向链表特定位置插进去一个新的项
  this.insert = function (position, element) {
    if (position < 0 && position > length) {
      // 越界
      return false;
    } else {
      var node = new Node(element);
      var index = 0;
      var currentNode = head;
      var previousNode;

      if (position === 0) {
        node.next = currentNode;
        head = node;
      } else {
        while (index < position) {
          index++;
          previousNode = currentNode;
          currentNode = currentNode.next; 
        }
  
        previousNode.next = node;
        node.next = currentNode;
      }

      length++;

      return true;
    }
  };

  // 从链表的特定位置移除一项
  this.removeAt = function (position) {
    if (position < 0 && position >= length || length === 0) {
      // 越界
      return false;
    } else {
      var currentNode = head;
      var index = 0;
      var previousNode;

      if (position === 0) {
        head = currentNode.next;
      } else {
        while (index < position) {
          index++;
          previousNode = currentNode;
          currentNode = currentNode.next;
        }
        previousNode.next = currentNode.next;
      }

      length--;

      return true;
    }
  };

  // 从链表的特定位置移除一项
  this.removeAt = function (position) {
    if (position < 0 && position >= length || length === 0) {
      // 越界
      return false;
    } else {
      var currentNode = head;
      var index = 0;
      var previousNode;

      if (position === 0) {
        head = currentNode.next;
      } else {
        while (index < position) {
          index++;
          previousNode = currentNode;
          currentNode = currentNode.next;
        }
        previousNode.next = currentNode.next;
      }

      length--;

      return true;
    }
  };

  // 从链表中移除指定项
  this.remove = function (element) {
    var index = this.indexOf(element);
    return this.removeAt(index);
  };

  // 返回元素在链表的索引,假如链表中没有该元素则返回-1
  this.indexOf = function (element) {
    var currentNode = head;
    var index = 0;

    while (currentNode) {
      if (currentNode.element === element) {
        return index;
      }

      index++;
      currentNode = currentNode.next;
    }

    return -1;
  };

  // 假如链表中不包括任何元素,返回true,假如链表长度大于0,返回false
  this.isEmpty = function () {
    return length == 0;
  };

  // 返回链表包括的元素个数,与数组的length属性相似
  this.size = function () {
    return length;
  };

  // 猎取链表头部元素
  this.getHead = function () {
    return head.element;
  };

  // 因为链表运用了Node类,就须要重写继续自JavaScript对象默许的toString()要领,让其只输出元素的值
  this.toString = function () {
    var currentNode = head;
    var string = '';

    while (currentNode) {
      string += ',' + currentNode.element;
      currentNode = currentNode.next;
    }

    return string.slice(1);
  };

  this.print = function () {
    console.log(this.toString());
  };

建立单向链表实例举行测试:

// 建立单向链表实例
var singlyLinked = new SinglyLinkedList();
console.log(singlyLinked.removeAt(0));              // true
console.log(singlyLinked.isEmpty());              // true
singlyLinked.append('Tom');                       
singlyLinked.append('Peter');
singlyLinked.append('Paul');
singlyLinked.print();                             // "Tom,Peter,Paul"
singlyLinked.insert(0, 'Susan');                  
singlyLinked.print();                             // "Susan,Tom,Peter,Paul"
singlyLinked.insert(1, 'Jack');                   
singlyLinked.print();                             // "Susan,Jack,Tom,Peter,Paul"
console.log(singlyLinked.getHead());              // "Susan"
console.log(singlyLinked.isEmpty());              // false
console.log(singlyLinked.indexOf('Peter'));       // 3
console.log(singlyLinked.indexOf('Cris'));        // -1
singlyLinked.remove('Tom');                       
singlyLinked.removeAt(2);                         
singlyLinked.print();                             // "Susan,Jack,Paul"

2.2 双向链表

双向链表和一般链表的区分在于,在一般链表中,一个节点只要链向下一个节点的链接,而在双向链表中,链接是双向的:一个链向下一个元素,另一个链向前一个元素。

建立双向链表类:

// 建立双向链表DoublyLinkedList类
function DoublyLinkedList () {
  function Node (element) {
    this.element = element;
    this.next = null;
    this.prev = null;        // 新增
  }

  var length = 0;
  var head = null;
  var tail = null;          // 新增
}

能够看到,双向链表在Node类里有prev属性(一个新指针),在DoublyLinkedList类里也有用来保留对列表末了一项的援用的tail属性。

双向链表供应了两种迭代列表的要领:从头至尾,或许从尾到头。我们能够接见一个特定节点的下一个或前一个元素。

在单向链表中,假如迭代链表时错过了要找的元素,就须要回到链表出发点,重新最先迭代。在双向链表中,能够从任一节点,向前或向后迭代,这是双向链表的一个长处。

完成双向链表的辅佐要领:

  // 向链表尾部增加一个新的项
  this.append = function (element) {
    var node = new Node(element);
    var currentNode = tail;

    // 推断是不是为空链表
    if (currentNode === null) {
      // 空链表
      head = node;
      tail = node;
    } else {
      currentNode.next = node;
      node.prev = currentNode;
      tail = node; 
    }

    length++;
  };

  // 向链表特定位置插进去一个新的项
  this.insert = function (position, element) {
    if (position < 0 && position > length) {
      // 越界
      return false;
    } else {
      var node = new Node(element);
      var index = 0;
      var currentNode = head;
      var previousNode;

      if (position === 0) {
        if (!head) {
          head = node;
          tail = node;
        } else {
          node.next = currentNode;
          currentNode.prev = node;
          head = node;
        }
      } else if (position === length) {
        this.append(element);
      } else {
        while (index < position) {
          index++;
          previousNode = currentNode;
          currentNode = currentNode.next; 
        }
  
        previousNode.next = node;
        node.next = currentNode;

        node.prev = previousNode;
        currentNode.prev = node;
      }

      length++;

      return true;
    }
  };

  // 从链表的特定位置移除一项
  this.removeAt = function (position) {
    if (position < 0 && position >= length || length === 0) {
      // 越界
      return false;
    } else {
      var currentNode = head;
      var index = 0;
      var previousNode;

      if (position === 0) {
        // 移除第一项
        if (length === 1) {
          head = null;
          tail = null;
        } else {
          head = currentNode.next;
          head.prev = null;
        }
      } else if (position === length - 1) {
        // 移除末了一项
        if (length === 1) {
          head = null;
          tail = null;
        } else {
          currentNode = tail;
          tail = currentNode.prev;
          tail.next = null;
        }
      } else {
        while (index < position) {
          index++;
          previousNode = currentNode;
          currentNode = currentNode.next;
        }
        previousNode.next = currentNode.next;
        previousNode = currentNode.next.prev;
      }

      length--;

      return true;
    }
  };

  // 从链表中移除指定项
  this.remove = function (element) {
    var index = this.indexOf(element);
    return this.removeAt(index);
  };

  // 返回元素在链表的索引,假如链表中没有该元素则返回-1
  this.indexOf = function (element) {
    var currentNode = head;
    var index = 0;

    while (currentNode) {
      if (currentNode.element === element) {
        return index;
      }

      index++;
      currentNode = currentNode.next;
    }

    return -1;
  };

  // 假如链表中不包括任何元素,返回true,假如链表长度大于0,返回false
  this.isEmpty = function () {
    return length == 0;
  };

  // 返回链表包括的元素个数,与数组的length属性相似
  this.size = function () {
    return length;
  };

  // 猎取链表头部元素
  this.getHead = function () {
    return head.element;
  };

  // 因为链表运用了Node类,就须要重写继续自JavaScript对象默许的toString()要领,让其只输出元素的值
  this.toString = function () {
    var currentNode = head;
    var string = '';

    while (currentNode) {
      
      string += ',' + currentNode.element;
      currentNode = currentNode.next;
    }

    return string.slice(1);    
  };

  this.print = function () {
    console.log(this.toString());
  };

建立双向链表实例举行测试:

// 建立双向链表
var doublyLinked = new DoublyLinkedList();
console.log(doublyLinked.isEmpty());              // true
doublyLinked.append('Tom');                       
doublyLinked.append('Peter');
doublyLinked.append('Paul');
doublyLinked.print();                             // "Tom,Peter,Paul"
doublyLinked.insert(0, 'Susan');                  
doublyLinked.print();                             // "Susan,Tom,Peter,Paul"
doublyLinked.insert(1, 'Jack');                   
doublyLinked.print();                             // "Susan,Jack,Tom,Peter,Paul"
console.log(doublyLinked.getHead());              // "Susan"
console.log(doublyLinked.isEmpty());              // false
console.log(doublyLinked.indexOf('Peter'));       // 3
console.log(doublyLinked.indexOf('Cris'));        // -1
doublyLinked.remove('Tom');                       
doublyLinked.removeAt(2);                         
doublyLinked.print();                             // "Susan,Jack,Paul"

2.3 轮回链表

轮回链表能够像单向链表一样只要单向援用,也能够像双向链表一样有双向援用。轮回链表和一般链表之间唯一的区分在于,末了一个元素指向下一个元素的指针(next)不是援用null,而是指向第一个元素(head)。这里就不举行代码完成了,人人能够连系上面的单向链表和双向链表本身完成一个轮回链表。

三、完毕

本文会同步到我的个人博客,完全代码能够到我的github堆栈检察,假如对你有协助的话迎接点一个Star~~

迎接关注我的民众号

《JavaScript数据结构04 - 链表》

    原文作者:liuxuan
    原文地址: https://segmentfault.com/a/1190000015874540
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞