用 JavaScript 完成链表

《用 JavaScript 完成链表》

什么是链表

单链表是示意一系列节点的数据结构,个中每一个节点指向链表中的下一个节点。 相反,双向链表具有指向其前后元素的节点。

与数组差别,链表不供应对链表表中特定索引接见。 因而,假如须要链表表中的第三个元素,则必需遍历第一个和第二个节点才到获得它。

链表的一个优点是能够在牢固的时间内从链表的开首和末端增添和删除项。

这些都是在手艺口试中常常被问到的数据结构,所以让我们最先吧。

别的,能够对链表举行排序。 这意味着当每一个节点增添到链表中时,它将被安排在相关于其他节点的恰当位置。

节点

链表只是一系列节点,所以让我们从 Node 对象最先。

《用 JavaScript 完成链表》

一个节点有两条信息

  • 指向链表中下一项的指针或援用(关于单链表)
  • 节点的值

关于我们的节点,我们只须要建立一个函数,该函数接收一个值,并返回一个具有上面两个信息的对象:指向下一个节点的指针和该节点的值

注重,我们能够只声明
value 而不是
value: value。这是由于变量称号雷同(ES6 语法)

节点链表

如今,让我们深入研究 NodeList 类,以下就是节点链表模样。

《用 JavaScript 完成链表》

节点链表将包括五个要领:

  • push(value): 将值增添到链表的末端
  • pop() :弹出链表中的末了一个值
  • get(index):返回给定索引中的项
  • delete(index):从给定索引中删除项
  • isEmpty(): 返回一个布尔值,指导链表是不是为空

printList():不是链表的原生要领,它将打印出我们的链表,重要用于调试

组织函数

组织函数中须要三个信息:

  • head:对链表开首节点的援用
  • tail:对链表末端节点的援用
  • length:链表中有若干节点
class LinkedList {
  constructor() {
    this.head = null;
    this.tail = null;
    this.length = 0;
  }
}

IsEmpty

isEmpty() 要领是一个协助函数,假如链表为空,则返回true

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

printList

这个实用程序要领用于打印链表中的节点,仅用于调试目标。

printList () {
  const nodes = [];
  let current = this.head;
  while (current) {
    nodes.push(current.value);
    current = current.next;
  }
  return nodes.join(' -> ');
}

Push

在增添新节点之前,push 要领须要搜检链表是不是为空。怎样晓得链表是不是为空? 两种体式格局:

  • isEmpty()要领返回true(链表的长度为零)
  • head 指针为空

关于这个例子,我们运用 head是不是为null来推断链表是不是为空。

假如链表中没有项,我们能够简朴地将head 指针和tail指针都设置为新节点并更新链表的长度。

if (this.head === null) {
  this.head = node;
  this.tail = node;
  this.length++;
  return node;
}

假如链表不是空的,我们必需实行以下操纵:

  • tail.next 指向新节点
  • tail 指向新节点
  • 更新链表长度

《用 JavaScript 完成链表》

以下是完全的 push 要领:

push(value) {
  const node = Node(value);
  // The list is empty
  if (this.head === null) {
    this.head = node;
    this.tail = node;
    this.length++;
    return node;
  }
  this.tail.next = node;
  this.tail = node;
  this.length++;
}

Pop

在删除链表中的末了一项之前,我们的pop要领须要搜检以下两项内容:

  • 搜检链表是不是为空
  • 搜检链表中是不是只要一项

能够运用isEmpty要领搜检链表是不是包括节点。

if (this.isEmpty()) {
  return null;
}

我们怎样晓得链表中只要一个节点? 假如 headtail 指向同一个节点。然则在这类状况下我们须要做什么呢? 删除唯一的节点意味着我们实际上要从新设置链表。

if (this.head === this.tail) {
  this.head = null;
  this.tail = null;
  this.length--;
  return nodeToRemove;
}

假如链表中有多个元素,我们能够实行以下操纵

当链表中有节点时,
 假如链表中的下一个节点是 tail 
   更新 tail 指向当前节点
   当前节点设置为 null,
   更新链表的长度
   返回前一个 tail 元素

它看起来像如许:

    1  let currentNode = this.head;
    2  let secondToLastNode;
    3
    4  //从前面最先并迭代直到找到倒数第二个节点
    5 
    6  while (currentNode) {
    7    if (currentNode.next === this.tail) {
    8      // 将第二个节点的指针挪动到末了一个节点
    9      secondToLastNode = currentNode;
   10      break;
   11    }
   12    currentNode = currentNode.next;
   13  }
   14  // 弹出该节点
   15  secondToLastNode.next = null;
   16  // 将 tail 挪动到倒数第二个节点
   17  this.tail = secondToLastNode;
   18  this.length--;
   19 
   20  // 初始化 this.tail
   21   return nodeToRemove;

假如你没法设想这一点,那末让我们来看看它。

第6-10行:假如链表中的下一个节点是末了一个项,那末这个当前项目就是新tail,因而我们须要保留它的援用。

if (currentNode.next === this.tail) {
  secondToLastNode = currentNode;
}

《用 JavaScript 完成链表》

第15行:将secondToLastNode更新为null,这是从链表中“弹出”末了一个元素的行动。

secondToLastNode.next = null;

《用 JavaScript 完成链表》

第17行:更新tail以指向secondToLastNode

this.tail = secondToLastNode;

《用 JavaScript 完成链表》

第18行:更新链表的长度,由于我们刚删除了一个节点。

第21行:返回方才弹出的节点。

以下是完全的pop要领:

pop() {
  if (this.isEmpty()) {
    return null;
  }
  const nodeToRemove = this.tail;
  // There's only one node!
  if (this.head === this.tail) {
    this.head = null;
    this.tail = null;
    this.length--;
    return nodeToRemove;
  }

  let currentNode = this.head;
  let secondToLastNode;

  // Start at the front and iterate until
  // we find the second to last node
  while (currentNode) {
    if (currentNode.next === this.tail) {
      // Move the pointer for the second to last node
      secondToLastNode = currentNode;
      break;
    }
    currentNode = currentNode.next;
  }
  // Pop off that node
  secondToLastNode.next = null;
  // Move the tail to the second to last node
  this.tail = secondToLastNode;
  this.length--;

  // Initialized to this.tail
  return nodeToRemove;
}


Get

get要领必需搜检三种状况:

  • 索引是不是超出了链表的局限
  • 链表是不是为空
  • 查询第一个元素

假如链表中不存在要求的索引,则返回null

// Index is outside the bounds of the list
if (index < 0 || index > this.length) {
  return null;
}

假如链表为空,则返回null。你能够把这些if语句组合起来,然则为了坚持清楚,我把它们分开了。

if (this.isEmpty()) {
  return null;
}

假如我们要求第一个元素,返回 head

// We're at the head!
if (index === 0 )  {
  return this.head;
}

不然,我们只是一个一个地遍历链表,直到找到要查找的索引。

let current = this.head;
let iterator =  0;

while (iterator < index) {
  iterator++;
  current = current.next;
}

return current;

以下是完全的get(index)要领:

get(index) {
// Index is outside the bounds of the list
if (index < 0 || index > this.length) {
  return null;
}

if (this.isEmpty()) {
  return null;
}

// We're at the head!
if (index === 0 )  {
  return this.head;
}

let current = this.head;
let iterator =  0;

while (iterator < index) {
  iterator++;
  current = current.next;
}

return current;
}

Delete

delete要领须要考虑到三个处所

  • 删除的索引超出了链表的局限
  • 链表是不是为空
  • 我们想要删除 head

假如链表中不存在我们要删除的索引,则返回 null

// Index is outside the bounds of the list
if (index < 0 || index > this.length) {
  return null;
}

假如我们想删除head,将head设置为链表中的下一个值,减小长度,并返回我们方才删除的值。

if (index === 0) {
  const nodeToDelete = this.head;
  this.head = this.head.next;
  this.length--;
  return nodeToDelete;
}

假如以上都 不是,则删除节点的逻辑以下:

轮回遍历正在查找的索引

   增添索引值

   将前一个和当前指针向上挪动一个

将当前值保留为要删除的节点

更新上一个节点的指针以指向下一个节点

假以下一个值为 `null`

   将`tail`设置为新的末了一个节点

更新链表长度

返回已删除的节点

假如你须要可视化图片,请参考Pop部份中的图表。

以下是完全的 delete 要领:

delete(index) {
   // Index is outside the bounds of the list
  if (index < 0 || index > this.length - 1) {
    return null;
  }

  if (this.isEmpty()) {
    return null;
  }

  if (index === 0) {
    const nodeToDelete = this.head;
    this.head = this.head.next;
    this.length--;
    return nodeToDelete;
  }

  let current = this.head;
  let previous;
  let iterator = 0;

  while (iterator < index) {
    iterator++;
    previous = current;
    current = current.next;
  }
  const nodeToDelete = current;
  // Re-direct pointer to skip the element we're deleting
  previous.next = current.next;

  // We're at the end
  if(previous.next === null) {
    this.tail = previous;
  }

  this.length--;

  return nodeToDelete;
}


你的点赞是我延续分享好东西的动力,迎接点赞!

迎接到场前端大家庭,内里会常常分享一些手艺资本。

《用 JavaScript 完成链表》

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