JS数据结构学习:链表

在存储多个元素时,我们最常用的数据结构可能是数组,究其原因可能是数组访问方便,可以直接通过[]访问,但是数组也存在一定的缺点,数组的大小是固定,数组在执行插入或者删除的时候成本很高。

链表存储的是有序的元素集合,和数组不同的是,链表中的元素在内存并不是连续放置,每个元素由一个存储元素本身的节点和一个指向下一个元素的引用组成,结构如下图所示:
JS数据结构学习:链表
和数组相比,链表的优势在于:添加或者删除元素不需要移动其他元素,劣势在与链表相对于数组结构更复杂,需要一个指向下一个元素的指针,在访问链表中的某个元素也需要从头迭代,而不是像数组一样直接访问

链表的创建

首先让我们来看一下链表的大概骨架,需要定义一些什么属性:

function LinkedList() {
  let Node = function (element) {
    this.element = element
    this.next = null
  }
  let length = 0
  let 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 () {
  }
}

首先LinkedList里面需要定义一个Node辅助类,表示要加入链表的项,包含一个element属性和一个指向下一个节点的指针next,接下来定义了一个指针的头head和指针的长度length,然后就是最重要的类里面的方法,接下来就让我们一起来看这些方法的职责和实现

append(向链表尾部追加元素)

链表在向尾部追加元素的时候有两种情况:链表为空,添加的是第一个元素,或者链表不为空,追加元素,下面来看具体实现:

this.append = function (element) {
    let node = new Node(element), current
    if (head === null) {
      head = node // 链表为空时,插入
    } else {
      current = node
      while (current.next) { // 循环链表,直到最后一项
        current = current.next
      }
      current.next = node // 找到最后一项,将新增元素连接
    }
    length++
  }

现在让我们先来看第一个种情况,链表为空时,插入就直接是头,因此直接将node赋值给head就行,第二种情况,当链表有元素时,就需要先循环链表,找到最后一项,然后将最后一项的next指针指向node

removeAt(移除元素)

现在让我们来看看怎么从指定位置移除元素,移除元素也有两个场景,第一种是移除第一个元素,第二种是移除第一个以外的任意一个元素,来看具体实现:

this.removeAt = function (position) {
    if (position > -1 && position < length) { // 判断边界
      let previous, index = 0, current = head
      if (position === 0) { // 移除第一项
        head = current.next
      } else {
        while (index++ < position) {
          previous = current
          current = current.next
        }
        previous.next = current.next
      }
      length--
      return current.element
    } else {
      return null
    }
  }

接下来一起来分析一下上面的代码,首先判断要删除的位置是不是有效的位置,然后来看第一种情况,当移除的元素是第一项是时,此时直接将head指向第二个元素就行了,第二种情况就会稍微复杂一点,首先需要一个index控制递增,previous记录前一个位置,移除当前元素,就是将前一个元素的next指向下一个元素,来看一个示意图:
JS数据结构学习:链表
因此在while循环中始终用previous记录上一个位置元素,current记录下一个元素,跳出循环时,上一个元素的next指针指向当前元素的next指针指向的元素,就将当前元素移出链表

insert(任意位置插入)

接下来来看在任意位置插入的insert方法,这个方法同样需要考虑两种情况,插入位置在头部和插入位置不在头部,下面来看一下具体实现:

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

先来看第一种情况,链表起点添加一个元素,将node.next指向current,然后再将node的引用赋值给head,这样就再链表的起点添加了一个元素,第二种情况,在其他位置插入一个元素,previous是插入元素的前一个元素,current为插入元素的后一个元素,想要插入一个元素,就需要将前一个元素的next指向要插入的元素,要插入元素的next指向下一个元素,来看示意图:
JS数据结构学习:链表
如上图所示:将新项node插入到previous和current之间,需要将previous.next指向node,node.next指向current,这样就在链表中插入了一个新的项

toString

toString方法会把LinkedList对象转换成一个字符串,下面来看具体实现:

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

循环遍历所有元素,以head为起点,当存在下一个元素时,就将其拼接到字符串中,直到next为null

indexOf

indexOf方法返回对应元素的位置,存在就返回对应的索引,不存在返回-1,来看具体的实现:

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

遍历链表,当前元素的值与目标值一致时返回元素的位置index,遍历完链表还没找到则返回-1

remove、isEmpty、size、getHead

由于这几个方法实现比较简单,直接来看具体实现:

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
  }

总结

这篇文章主要对链表做了简单介绍,对链表的简单实现。如果有错误或不严谨的地方,欢迎批评指正,如果喜欢,欢迎点赞。

相关推荐