图解数据结构——链表

链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。——维基百科

链表

如果将链表简单抽象成图片,大概长这样。

是不是跟链子很像?(好吧,不是很像)但是你细品,应该还是能发现链表跟你认识的某位中老年女性的珍珠项链有几分相似。
图片中画的是一个最简单的单向链表,所以最后一个节点node5指向了null。后面还有双向链表循环链表等,其实都是单向链表的升级版,后面会有图介绍。

  • 说明
    • 节点(node):节点一般由数据data指针组成。
    • 数据(data):代表每个节点中储存的数据。一般可以储存任何类型。如:自定义的对象、int类型、String等等。
    • 箭头:代表指针,在上面的单向链表中,只有一个指向下一个节点的指针,一般叫做:next指针
    • 前一个节点的next指针,指向下一个节点的内存地址。注意链表中每个节点的内存地址不一定是按顺序储存的。可能node1的内存地址是0x00001,而node2的内存地址却是0x00008,到node3的地址又是0xAC01了。(内存地址相当于数据在计算机内存中的门牌号)

链表的数据操作

既然是数据结构,就一定涉及数据的增删查等操作。

  • 在单向链表尾部增加一个节点

    上图展示了在单向链表尾部添加节点的过程。还有在中间,在头部添加节点。步骤都一样。

    • 链表添加步骤
      1. 声明一个新节点(图中的node2)。
      2. 将指定位置节点(图中的node1)的next指针指向新建的节点。
      3. 将新建的节点(node2)的next指针指向前一个节点之前指向的位置(图中是node1之前指向的是null)。

理论上只要内存足够大,可以添加很多很多的节点。所以链表能储存的数据多少就非常灵活。二且添加节点几乎不耗费什么时间,只要修改几个指针的指向就OK了。

  • 查找单向列表里面的一个节点
    在链表:node1 -> node2 -> node3 -> node4 -> node5 -> null中查找node3节点。

    无论用值对比(对比data中的数据是否相等),还是用索引(第几个节点)来查找节点,都需要挨个遍历之前的节点。如果链表太长就会很耗时。

  • 删除单向链表一个节点
    下面的例子,在一个链表:node1->node2->node3->node4->node5->null中删除node3。

    遍历链表,发现了要删除的node3就像node2的next指针直接指向node4就好。因为是单向链表,所以这个node3永远访问不到了,也就等于从链表里面删除。

    • 单向链表删除节点步骤
      • 遍历链表,找到需要删除的节点(node3)。
      • 将其前一个节点(node2)的next指针,指向其后一个节点(node4)。

    虽然查找到要删除的节点可能会费点时间。但是删除节点也是非常快速的,只要修改指针的指向。

单向链表的缺点

上面我们看了单向链表的增、删、查。但是我们在查找或者删除一个节点的时候只能next、next的挨个查找。

  • 反面例子:在链表node1 -> node2 -> node3 -> node4 -> node5 -> null 中。我先查到了node5,然后再想查找node4就需要重新从node1重新开始查找,如果这个链表有一千万个节点,这样明显比较浪费效率。
  • 缺点:只能单向遍历。

解决单向链表的缺点——双向链表

单向链表每个节点只有一个next指针,我们可以增加一个pre指针用来指向前一个节点,这样就可以往前往后的双向查找了。

双向链表的缺点

双向链表虽然可以双向遍历,但是每次都要从头开始查找。

  • 反面例子:在链表 null < - node1 -> node2 <=> node3 <=> node4 <=> node5 -> null 中。我要查找node5,很明显要从node1开始遍历,一个个的查找。但是如果链表有一百万个节点,而我就要最后一个,那岂不是每次都要遍历一百万个节点。
  • 缺点:只能从头往后的开始遍历。

解决双向链表的问题——循环链表

双向链表第一个节点的pre指针,跟最后一个节点的next指针都是指向null的。我们可以把第一个节点的pre指向最后一个,而最后一个节点的next指针指向第一个节点,从而形成一个环。
(其实可以直接记录一下,第一个节点first,和最后一个节点last。不用循环链表也可以)

你细品,是不是更像个“项链”,哈哈哈。

总结

  • 链表的优点

    • 插入和删除速度快。
    • 内存利用率高(因为每个节点的内存不是连续的,找到点内存就能用,而数组就需要找到连续的内存才能储存)。
    • 可以储存数据的个数很灵活,理论上只要内存足够,没什么限制。
  • 链表的缺点

    • 查找效率很低,不能随机查找,需要挨个遍历(因为内存地址不连续)。

作者:BobC

文章原创。如你发现错误,欢迎指正,在这里先谢过了。博主的所有的文章、笔记都会在优化并整理后发布在个人公众号上,如果我的笔记对你有一定的用处的话,欢迎关注一下,我会提供更多优质的笔记的。
原文地址:https://www.cnblogs.com/Eastry/p/12960264.html