资讯专栏INFORMATION COLUMN

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

jerryloveemily / 1021人阅读

摘要:本系列所有文章第一篇文章学习数据结构与算法之栈与队列第二篇文章学习数据结构与算法之链表第三篇文章学习数据结构与算法之集合第四篇文章学习数据结构与算法之字典和散列表第五篇文章学习数据结构与算法之二叉搜索树简单介绍链表链表一种常见的数据结构,可

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

简单介绍链表

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

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

单向链表

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

(图片来源谷歌,侵删)

用JavaScript实现单向链表

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

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

链表需要实现以下方法:

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的思路实现一遍

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++
}

其他的代码都很简单,我就放上源代码地址,大家自行查阅吧~

双向链表的实现-源代码

小结

一开始写的时候有点不习惯,但是实现了一两个方法之后发现很多思路是类似的,于是举一反三地写出来,然后跟书上对比之后,发现还是有点差距的。

大家有兴趣也动手去实践一下再对比源码,可以认识到自己有哪些地方不足。

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/87169.html

相关文章

  • 数据结构算法随笔链表-链表是否有环(二)

    摘要:初始化时指针走两步,指针走一步,不停遍历的变化最后快慢指针又相遇了,循环结束,代码实现如下复杂度分析,假设链表长度为时间复杂度,链表无环时,快指针会先到达尾部,时间就是如果有环,那么假设环部长度为,时间就是,也就是空间复杂度 上一篇文章我们分析了下链表之反转单向链表,这篇文章我们来分析下另外一个关于链表的经典题目。 判断链表是否有环(在leetcode上的题目地址:环形链表) 题目描述...

    molyzzx 评论0 收藏0
  • 利用PHP实现《剑指 offer》链表数据结构算法实战 )

    摘要:一定要认真看分析注释题目要求题目描述输入一个链表,从尾到头打印链表每个节点的值。分析因为链表只有知道当前结点才能知道下一结点,所以不可能直接从后往前打印。 一定要认真看 分析 | 注释 | 题目要求 Question 1 题目描述:输入一个链表,从尾到头打印链表每个节点的值。 分析:因为链表只有知道当前结点才能知道下一结点,所以不可能直接从后往前打印。这种逆序的算法(策略)我们常用栈这...

    hiYoHoo 评论0 收藏0
  • PHPer也刷《剑指Offer》链表

    摘要:剑指中链表相关题目俗话说光说不练假把式,既然学习了链表的基础概念和基本操作那我们一定要找些题目巩固下,下面来看剑指中的相关题目。题目分析合并两个排序的链表,需要分别比较两个链表的每个值,然后改变指针。 温故知新 链表由一个一个的作为节点的对象构成的,每一个节点都有指向下一个节点的指针,最后一个节点的指针域指向空。每个节点可以存储任何数据类型。 根据类型可以分为单链表、双链表、环形链表、...

    daydream 评论0 收藏0
  • 03线性表链表

    摘要:带头双向循环链表结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。 肚子饿了就要吃   ~   嗝  ——— 路飞  1.本章重点 链表表示和实现(单链表+双链表)链表的常见OJ题顺序表和链表的区别和联系2.为什么需要链表 引子:顺序表的问题及思考 (1...

    jayce 评论0 收藏0
  • 利用PHP实现常用的数据结构链表(小白系列文章五)

    摘要:因为涉及指针,我们用引用来模拟,所以读者应该有面向对象的知识贮备。引文因为涉及内存,常常会有一些程序的边界限制,需要拥有一定严密的逻辑去保证代码的鲁棒性和健壮性,所以这个知识点是面试的常考点。 tips:因为涉及指针,我们用引用来模拟,所以读者应该有面向对象的知识贮备。 引子 你可以把链表简单理解为动态数组,它不需要一块一块的开辟空间,同时,你又要注意,它存在的主要意义或者说使用场景...

    rollback 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<