学习数据结构与算法之链表

本系列全部文章:
第一篇文章:学习数据结构与算法之栈与队列
第二篇文章:学习数据结构与算法之链表
第三篇文章:学习数据结构与算法之集合
第四篇文章:学习数据结构与算法之字典和散列表
第五篇文章:学习数据结构与算法之二叉搜索树javascript

简单介绍链表

链表一种常见的数据结构,能够存储有序的元素集合。不一样于数组,链表中元素在内存中不是连续放置,同时每个链表元素中除了自己的节点还保存了指向下一个元素的引用,这些特色使得链表元素在动态增长和删除时没必要移动其余元素,可是访问链表元素时必须从起点开始迭代列表直到找到目标元素。java

下面是两种链表的JavaScript实现:node

单向链表

下图就是一个典型的单向链表,这里注意:通常链表中的第一个元素会有一个head指针指向它,这也是咱们操做链表必不可少的一环。git

单向链表

(图片来源谷歌,侵删)github

用JavaScript实现单向链表

与以前实现栈和队列同样,这里先声明一个构造函数:算法

function LinkedList () {
  var Node = function (element) {
    this.element = element
    // 保存指向下个元素的引用,默认为null
    this.next = null
  }
  
  // 链表长度
  var length = 0
  // head保存指向第一个元素的引用
  var head = null
}

链表须要实现如下方法:segmentfault

  • append(element):向链表尾部添加元素
  • insert(position, element):向链表特定位置插入元素
  • removeAt(position):从链表特定位置移除一项
  • remove(element):在链表中移除某元素
  • indexOf(element):返回元素在链表中的索引,若不存在则返回-1
  • isEmpty():若是链表不包含任何元素就返回true,不然为false
  • size():返回链表长度
  • toString():返回元素的值转成字符串

实现append

相似数组的push方法,可是只能添加一个元素。实现方法的时候分两种状况考虑:1. 链表为空时添加第一个元素;2. 链表不为空时在尾部添加元素数组

this.append = function (element) {

  var node = new Node(element),
      current

  if (head === null) { // 当链表为空时
    // 将head指向新增的元素
    head = node
  } else { // 链表不为空
    // 使用一个current变量从head开始迭代链表
    current = head

    // 迭代链表,直到找到最后一项
    while (current.next) {
      current = current.next
    }

    // 找到最后一项,将其next赋为node,创建连接
    current.next = node
  }

  // 更新列表长度
  length++
}

实现removeAt

在链表中特定位置移除元素,实现时也须要考虑两种状况:1. 移除第一个元素;2. 移除其余元素(包括最后一个)数据结构

this.removeAt = function (position) {
  // 判断位置是否越界
  if (position > -1 && position < length) {
    var current = head,
        previous,
        index = 0

    // 若是删除了第一个元素,把head指向下一个元素就好了
    if (position === 0) {
      head = current.next
    } else {
      // 根据输入的位置查找要删除的元素
      while (index++ < position) {
        previous = current
        current = current.next
      }
      // 将上一个元素的next指向current的下一项,跳过current,实现移除current
      previous.next = current.next
    }

    // 更新列表长度
    length--

    // 返回删除的元素
    return current.element
  } else {
    return null
  }
}

实现insert

与removeAt相似的实现,你们能够先不看源码,本身按着removeAt的思路实现一遍app

this.insert = function (position, element) {
  // 检查位置是否越界
  if (position >= 0 && position <= length) {
    var node = new Node(element),
        index = 0,
        previous,
        current = head

    // 在第一个位置添加
    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
}
}

实现indexOf

根据元素查找在链表中的位置,没找到就返回-1

this.indexOf = function (element) {
  var current = head,
      index = 0

  while (current) {
    if (element === current.element) {
      return index
    }
    index++
    current = current.next
  }

  return -1
}

实现其余方法

// 返回全部元素的值转成字符串
this.toString = function () {
  var current = head,
      string = ''

  while (current) {
    string += current.element
    current = current.next
  }

  return string
}

// 移除特定元素
this.remove = function (element) {
  var index = this.indexOf(element)
  return this.removeAt(index)
}

// 判断链表是否为空
this.isEmpty = function () {
  return length === 0
}

// 返回链表长度
this.size = function () {
  return length
}

// 返回第一个元素
this.getHead = function () {
  return head
}

以上是单向链表的实现,有兴趣的能够下载源码来看:

单向链表的实现-源代码

双向链表

双向链表和单向链表的区别就是每个元素是双向的,一个元素中包含两个引用:一个指向前一个元素;一个指向下一个元素。除此以外,双向链表还有一个指向最后一个元素的tail指针,这使得双向链表能够从头尾两个方向迭代链表,所以更加灵活。以下图:

双向链表

(图片来源谷歌搜索,侵删)

用JavaScript实现双向链表

同单向链表同样,先声明一个构造函数

function DoubleLinkedList () {
  var Node = function (element) {
    this.element = element
    this.prev = null // 新增了一个指向前一个元素的引用
    this.next = null
  }

  var length = 0
  var head = null
  var tail = null //新增了tail指向最后一个元素
}

双向链表须要有如下方法:

  • append(element):向链表尾部添加元素
  • insert(position, element):向链表特定位置插入元素
  • removeAt(position):从链表特定位置移除一项
  • showHead():获取双向链表的头部
  • showLength():获取双向链表长度
  • showTail():获取双向链表尾部

实现insert

同单向链表相似,只不过状况更复杂了,你不只须要额外考虑在第一个元素的位置插入新元素,还要考虑在最后一个元素以后插入新元素的状况。此外若是在第一个元素插入时,链表为空的状况也须要考虑。

this.insert = function (position, element) {
  // 检查是否越界
  if (position >= 0 && position <= length) {
    var 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
      node.prev = current
      current.next = node
      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
  }
}

实现removeAt

与insert相似,这里很少解释直接贴代码

this.removeAt = function (position) {
  // 检查是否越界
  if (position > -1 && position < length) {
    var 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.next = current.next
      current.next.prev = previous
    }

    length--

    return current.element
  } else {
    return null
  }
}

实现append

和单向链表的同样,只不过多了tail有一些不一样

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

  if (head === null) {
    head = node
    tail = node
  } else {
    node.prev = current
    current.next = node
    tail = node
  }

  length++
}

其余的代码都很简单,我就放上源代码地址,你们自行查阅吧~

双向链表的实现-源代码

小结

一开始写的时候有点不习惯,可是实现了一两个方法以后发现不少思路是相似的,因而触类旁通地写出来,而后跟书上对比以后,发现仍是有点差距的。

你们有兴趣也动手去实践一下再对比源码,能够认识到本身有哪些地方不足。

链表暂时就这样了,明天继续攻克集合!

相关文章
相关标签/搜索