数据结构学习-链表、双向链表、循环链表

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

相对于传统的数组,链表的一个好处在于,添加或移除元素的时候不需要移动其他元素,只需要找到指针的位置。而数组添加元素和移除元素,需要移动元素的位置,成本比较高

链表

链表要实现的方法

append(element) :向列表尾部添加一个新的项。
insert(position, element) :向列表的特定位置插入一个新的项。
remove(element) :从列表中移除一项。
indexOf(element) :返回元素在列表中的索引。如果列表中没有该元素则返回 -1 。
removeAt(position) :从列表的特定位置移除一项。
isEmpty() :如果链表中不包含任何元素,返回 true ,如果链表长度大于0则返回 false 。
size() :返回链表包含的元素个数。与数组的 length 属性类似。
toString() :由于列表项使用了 Node 类,就需要重写继承自JavaScript对象默认的toString 方法,让其只输出元素的值。
function LinkedList() {
  let Node = function(element){
    this.element = element;
    this.next = null;
  };
  let length = 0;
  let head = null;

  this.append = function(element){
    let node = new Node(element),
      current;
    if (head === null){ //列表中第一个节点
      head = node;
    } else {
      current = head; // current = {element: 'a', next: null}
      // 从第一项开始循环列表,直到找到最后一项
      while(current.next){
        current = current.next;
      }
      //找到最后一项,将其next赋为node,建立链接
      current.next = node;
    }
      length++; //更新列表的长度
  };

  this.removeAt = function(position) {
    //检查越界值
    if (position > -1 && position < length) { 
      let current = head, 
        previous, 
        index = 0; 
      //移除第一项
      if (position === 0) { 
        head = current.next;
      } else {
        // 找到移除项的前一项和待移除项
        while (index++ < position) { 
          previous = current; 
          current = current.next; 
        }
        // 将previous与current的下一项链接起来:跳过current,从而移除它
        previous.next = current.next; 
      }
      length--; 
      return current.element; // 返回移除项
    } else {
      return null; 
    }
  };


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

  this.toString = function() {
    let current = head,
      string = '';
    // 从第一项遍历到最后一项,拼接element的值
    while (current) {
      string += current.element + (current.next ? ',' : '');
      current = current.next;
    }
    return string;
  };

  this.indexOf = function(element) {
    let current = head,
      index = -1;
    while (current) {
      // 找到链表中匹配项的位置
      if (element === current.element) {
        return index;
      }
      index++;
      current = current.next;
    }
    return -1;
  };

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

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

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

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

let l = new LinkedList();
l.append('a');
l.append('b');
console.log(l.size()); // 2

l.insert(1, 'aa');
console.log(l.size()); // 3

以上就是链表的实现方法,这里的属性和方法都定义在了函数内部而非原型对象上,这么做是为了保证私有属性不会暴露到外面

双向链表

链表有多种不同的类型,上面实现的是链表是单向的,即每个节点只有链向下一个节点的链接。而在双向链表中,链接是双向的:一个链向下一个元素,另一个链向前一个元素,如下图所示:

基于前面的链表稍加改动,便可以实现双向链表

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

  let length = 0;
  let head = null; // 第一个节点
  let tail = null; // 最后一个节点 新增

  this.append = function(element) {
    let node = new Node(element),
      current;
    if (head === null) { // 链表中的第一个节点
      head = node;
      tail = node; // 新增
    } else {
      // 新增
      tail.next = node; // 尾节点的下一个节点指向新增节点
      node.prev = tail; // 新节点的前一个节点指向尾节点
      tail = node; // 更新尾节点
    }
    length++; // 更新链表长度
  };

  this.insert = function(position, element) {
    // 边界值检测
    if (position >= 0 && position <= length) {
      let node = new Node(element),
        current = head,
        previous,
        index = 0;
      if (position === 0) { // 特殊情况:新节点要添加到头部
        if (!head) { // 新增
          head = node;
          tail = node;
        } else {
          node.next = current;
          current.prev = node; // 新增
          head = node;
        }
      } else if (position === length) { // 特殊情况:新节点要添加到头部尾部 // 新增
        current = tail;
        current.next = node;
        node.prev = current;
        tail = node;
      } else {
        while (index++ < position) {
          previous = current;
          current = current.next;
        }
        node.next = current;
        previous.next = node;

        current.prev = node; // 新增
        node.prev = previous; // 新增
      }
      length++; // 更新链表长度
      return true;
    } else {
      return false;
    }
  };

  this.removeAt = function(position) {
    // 边界值检测
    if (position > -1 && position < length) {
      let current = head,
        previous,
        index = 0;

      // 移除第一个节点
      if (position === 0) {
        head = current.next;
        // 如果链表只有一个节点,此时需要更新尾节点 // 新增
        if (length === 1) {
          tail = null;
        } else {
          head.prev = null;
        }
      } else if (position === length - 1) { // 移除最后一个节点 // 新增
        current = tail;
        tail = current.prev;
        tail.next = null;
      } else {
        while (index++ < position) { // 移除中间节点
          previous = current;
          current = current.next;
        }
        // 将previous与current的下一项链接起来——跳过current
        previous.next = current.next;
        current.next.prev = previous; // 新增
      }
      length--;
      return current.element;
    } else {
      return null;
    }
  };

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

  this.indexOf = function(element) {
    let current = head,
      index = -1;
    // 第一个节点,无需遍历,节省时间
    if (element == current.element) {
      return 0;
    }
    index++;
    // 中间节点,需要遍历才能找到位置
    while (current.next) {
      if (element == current.element) {
        return index;
      }
      current = current.next;
      index++;
    }
    // 最后一个节点
    if (element == current.element) {
      return index;
    }
    return -1;
  };

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

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

  this.toString = function() {
    let current = head,
      s = current ? current.element : '';
    while (current && current.next) {
      current = current.next;
      s += ', ' + current.element;
    }
    return s;
  };

  this.inverseToString = function() {
    let current = tail,
      s = current ? current.element : '';
    while (current && current.prev) {
      current = current.prev;
      s += ', ' + current.element;
    }
    return s;
  };

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

  this.getTail = function() {
    return tail;
  }
}

节点移除示意图

移除链表头部节点

移除链表尾部节点

移除链表中间节点

节点新增示意图

链表头部新增节点

链表尾部新增节点

链表中间新增节点

循环链表

还有一种链表叫做循环链表,循环链表和链表之间唯一的区别在于,最后一个元素指向下一个元素的指针再是null,而是指向第一个元素( head ),整体形成了一个环装,如下图所示

循环链表可以像链表一样只有单向引用,也可以像双向链表一样有双向引用,双向引用的叫双向循环链表。这种链表有指向 head 元素的 tail.next ,和指向 tail 元素的 head.prev,如图

循环链表的实现和链表类似,只是需要调整最后一个节点的指向为第一个节点

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

  let length = 0;
  let head = null;

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

    if (head === null) { // 链表中第一个节点
      head = node;
    } else {
      current = head;
      //找到链表中最后一个节点
      while (current.next !== head) { //最后一个元素指向head而不是NULL
        current = current.next;
      }
      //获取最后一项,并将其分配到添加项中以创建链接
      current.next = node;
    }
    //设置 node.next 指向 head
    node.next = head;
    length++;
  };

  this.insert = function(position, element) {
    if (position >= 0 && position <= length) {
      let node = new Node(element),
        current = head,
        previous,
        index = 0;
      if (position === 0) {
        if (!head) {
          head = node;
          node.next = head;
        } else {
          node.next = current;
          // 更新最后一个节点
          while (current.next !== head) { //最后一个元素指向head而不是NULL
            current = current.next;
          }
          head = node;
          current.next = head;
        }
      } else {
        while (index++ < position) {
          previous = current;
          current = current.next;
        }
        node.next = current;
        previous.next = node;
      }
      length++;
      return true;
    } else {
      return false;
    }
  };

  this.removeAt = function(position) {
    if (position > -1 && position < length) {
      let current = head,
        previous,
        index = 0;
      if (position === 0) {
        while (current.next !== head) { // 需要更新最后一个节点的指向
          current = current.next;
        }
        head = head.next;
        current.next = head;
      } else {
        while (index++ < position) {
          previous = current;
          current = current.next;
        }
        //把前一个节点和当前节点的next链接,删除当前节点
        previous.next = current.next;
      }
      length--;
      return current.element;
    } else {
      return null;
    }
  };

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

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

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

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

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

  this.toString = function() {
    let current = head,
      s = current.element;
    while (current.next !== head) {
      current = current.next;
      s += ', ' + current.element;
    }
    return s.toString();
  };
}

总结

链表相比数组最重要的优点,就是无需移动链表中的元素,就能轻松地添加和移除元素。因此,当需要添加和移除很多元素时,最好的选择就是链表,而非数组。

posted @ 2021-09-28 18:45  wmui  阅读(176)  评论(0编辑  收藏  举报