链表是比数组稍微复杂一点的数据结构,也是两个非常重要与基本的数据结构。如果说数组是纪律严明排列整齐的「正规军」那么链表就是灵活多变的「地下党」。

关注公众号 MageByte,有你想要的精彩内容。

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。链表的节点由数据和一个或多个指针域组成。如果不考虑插入、删除操作之前查找元素的过程,只考虑纯粹的插入与删除,那么链表在插入和删除操作上的算法复杂 O(1)。

链表大集结

电影中我们看过,一个特务只知道自己上级、下级的信息,地下党通过这种单线联络的的方式灵活隐秘的传递着消息。链表家族也有多种分类,最简单的单链表、循环链表、双向链表、双向循环链表。

底层数据结构与数组最大的区别,数组需要一块连续的内存空间来存储,而链表并不需要一块连续的内存空间,它通过「指针」将一组零散的内存块串联起来使用。这就是地下党灵活多变的本质原因,可随时切换自己的上下级。

单链表

我们把内存块称为链表的「节点」,为了把所有分散的节点串连起来,每个节点除了存储数据外,还需要记录节点的下一个节点的地址,叫做「后继指针 next」。

有两个节点需要注意,分别是第一个节点「头节点」和最后一个节点「尾节点」。头结点记录链表的基地址,这样就可以遍历得到整条链表数据,而尾节点的 「next」只想一个 空地址 NULL,代表这是最后一个节点。

链表的删除与插入操作,只需要考虑相邻节点的指针改变,所以时间复杂度是 O(1)。

有利就有弊,链表想要随机访问第 j 个元素,就没有数组高效。链表的数据并不是连续存储的,无法像数组一样根据首地址和下标通过寻址公式就可以计算出对应的 j 位置内存地址,需要根据指针一个一个节点的一次遍历,直到查找到对应的节点。

这个就像地下党组织,每个人只知道自己的后边是谁,想要把消息传到 k 特务,就需要从消息发布者开始通知他的下一级,直到 k 为止。所以随机访问的性能没有数组好,时间复杂度是 O(n)。

插入节点

尾部插入

与数组类似,插入节点也可以分头部插入、中部插入、尾部插入。尾部插入最简单,把最后一个节点的「next」指针指向新插入的节点即可。

头部插入

分为两个步骤。

第一步,把新节点的「next」指针指向原先的头节点。

第步,把新节点变为链表的头节点。

中间插入

同样分为两个步骤。

  1. 把插入位置的节点前置节点的「next」指针指向指定插入的新节点。
  2. 将新节点的「next」指针指向前置节点的「next」指针原先所指定的节点。

删除节点

单链表的删除也分为三种情况。

  • 头部删除
  • 中部删除
  • 尾部删除

尾部删除最简单,把倒数第二个节点的「next」指针指向 null,同时把要删除的节点全都设置 null 让垃圾收集器回收即可。

头部删除,把原先链表的头节点的「next」节点设置成头结点,并且把原来的头结点设置 null 便于 gc 即可。

中间删除,把要删除的节点前置节点的「next」指针指向被删除节点的「next」指针即可。

对于删除与插入,只要我们画下图就很清晰了。

循环链表

循环链表是一种特殊的单链表。实际上,循环链表也很简单。它跟单链表唯一的区别就在尾结点。我们知道,单链表的尾结点指针指向空地址,表示这就是最后的结点了。而循环链表的尾结点指针是指向链表的头结点。从我画的循环链表图中,你应该可以看出来,它像一个环一样首尾相连,所以叫作“循环”链表。

跟单链表差别不大,主要就是尾部节点遍历到头部节点方便。

双向链表

实际开发中最常见的链表-双向链表。Java 中的 LinkedList就是一个双向链表。

单链表只有一个方向,每个节点只有一个后继指针 「next」指向下一个节点。双向链表则是有两个指针,每个节点分别有一个「next」指针指向后面节点和一个「prev」指针指向前置节点。

可以看出来,双向链表需要额外的两个空间存储后继节点和前驱节点地址。所以存储同样多的数据,双向链表比单项链表占用更多的空间,但是优势则是可以双向遍历。

双向链表可以支持在 O(1) 时间复杂度情况定位到前驱结点,正是这样的特点,也使双向链表在某些情况下的插入、删除等操作都要比单链表简单、高效。

之前我们说单项链表的删除、插入时间复杂度是 O(1)了,那为啥这里还说双向链表的删除、插入还能更高效呢?

从链表删除一个元素,其实有两种情况:

  • 删除「值等于给定的内容」的节点。
  • 删除给定指针指向的节点。

第一种情况,其实都一样,不管是单项还是双向都需要从头节点遍历比对找到要删除的节点。

对于第二种情况,我们已经找到了要删除的结点,但是删除某个结点 q 需要知道其前驱结点,而单链表并不支持直接获取前驱结点,所以,为了找到前驱结点,我们还是要从头结点开始遍历链表,直到 p->next=q,说明 p 是 q 的前驱结点。

但是对于双向链表来说,这种情况就比较有优势了。因为双向链表中的结点已经保存了前驱结点的指针,不需要像单链表那样遍历。所以,针对第二种情况,单链表删除操作需要 O(n) 的时间复杂度,而双向链表只需要在 O(1) 的时间复杂度内就搞定了!

双向循环链表

数组与链表性能比较

数组和链表都属于线性的数据结构,用哪一个好呢?其实数据结构没有绝对的好坏,各有千秋。

查找 更新 删除 插入
数组 O(1) O(1) O(n) O(n)
链表 O(n) O(1) O(1) O(1)

数组简单易用,在实现上使用的是连续的内存空间,可以借助 CPU 的缓存机制,预读数组中的数据,所以访问效率更高。而链表在内存中并不是连续存储,所以对 CPU 缓存不友好,没办法有效预读。

对链表进行频繁的插入、删除操作,还会导致频繁的内存申请和释放,容易造成内存碎片,如果是 Java 语言,就有可能会导致频繁的 GC(Garbage Collection,垃圾回收)。

本文讲解了链表的理论原理与知识,下一篇将代码实操撸一遍。

课后作业

如何基于链表实现一个 LRU 缓存淘汰算法?公众号后台回复 LRU,可以获取答案。

欢迎加群与我们讨论分享,我们第一时间反馈。

推荐阅读

1.跨越数据结构与算法

2.时间复杂度与空间复杂度

3.最好、最坏、平均、均摊时间复杂度

4.线性表之数组

5L-链表导论心法的更多相关文章

  1. 6L-单向链表实现

    关注公众号 MageByte,有你想要的精彩内容.文中涉及的代码可访问 GitHub:https://github.com/UniqueDong/algorithms.git 上一篇<链表导论心 ...

  2. 7L-双线链表实现

    链表是基本的数据结构,尤其双向链表在应用中最为常见,LinkedList 就实现了双向链表.今天我们一起手写一个双向链表. 文中涉及的代码可访问 GitHub:https://github.com/U ...

  3. 《算法导论》习题解答 Chapter 22.1-2(邻接矩阵与链表)

    链表如图: 矩阵: 1 2 3 4 5 6 7 1 0 1 1 0 0 0 0 2 1 0 0 1 1 0 0 3 1 0 0 0 0 1 1 4 0 1 0 0 0 0 0 5 0 1 0 0 0 ...

  4. 基于visual Studio2013解决算法导论之022队列实现(基于链表)

     题目 基于链表的队列实现 解决代码及点评 #include <stdio.h> #include <stdlib.h> #include <time.h> ...

  5. 基于visual Studio2013解决算法导论之020单链表

     题目 单链表操作 解决代码及点评 #include <iostream> using namespace std; struct LinkNode { public: LinkNo ...

  6. 基于visual Studio2013解决算法导论之018栈实现(基于链表)

     题目 用链表实现栈 解决代码及点评 #include <stdio.h> #include <stdlib.h> #include <time.h> #in ...

  7. 【原创】《算法导论》链表一章带星习题试解——附C语言实现

    原题: 双向链表中,需要三个基本数据,一个携带具体数据,一个携带指向上一环节的prev指针,一个携带指向下一环节的next指针.请改写双向链表,仅用一个指针np实现双向链表的功能.定义np为next ...

  8. "《算法导论》之‘线性表’":基于数组实现的单链表

    对于单链表,我们大多时候会用指针来实现(可参考基于指针实现的单链表).现在我们就来看看怎么用数组来实现单链表. 1. 定义单链表中结点的数据结构 typedef int ElementType; cl ...

  9. "《算法导论》之‘线性表’":基于指针实现的单链表

    对于单链表的介绍部分参考自博文数组.单链表和双链表介绍 以及 双向链表的C/C++/Java实现. 1. 单链表介绍 单向链表(单链表)是链表的一种,它由节点组成,每个节点都包含下一个节点的指针.   ...

随机推荐

  1. MYSQL对数据库和表的基本操作

    CREATE DATABASE testdb CHARSET=UTF8 创建一个数据库 名字叫做testdb USE testdb; 选择数据库 CREATE TABLE testTable1( ) ...

  2. Java入门教程一(Java简介)

    什么是Java语言 Java 是由 Sun Microsystems 公司于 1995 年推出的一门面向对象程序设计语言.2010 年 Oracle 公司收购 Sun Microsystems,之后由 ...

  3. sofa-bolt源码阅读(1)-服务端的启动

    Bolt服务器的核心类是RpcServer,启动的时候调用父类AbstractRemotingServer的startup方法. com.alipay.remoting.AbstractRemotin ...

  4. ITT Corporation之“中国战略”

    前言:众所周知,中国已经成为全世界第二大经济体,并且坐拥14亿人口的庞大市场,蕴藏着巨大的市场机遇,海外高科技企业想法获得长足的发展重视和开拓中国市场成为重中之重,诸如特斯拉,google,苹果等,近 ...

  5. k8s环境部署本地.net core web项目

    上一篇文章,我们部署了docker+k8s环境,简单测试通过,但是,还没能将我们自己的项目部署上去,继续记录部署踩坑过程. 一.准备工作 1.当然是docker+k8s环境了,详情请看上一篇文档 ht ...

  6. Python中的BeautifulSoup库简要总结

    一.基本元素 BeautifulSoup库是解析.遍历.维护“标签树”的功能库. 引用 from bs4 import BeautifulSoup import bs4 html文档-标签树-Beau ...

  7. 不一样的ZTree,权限树.js插件

    每一个有趣的创新,都源于苦逼的生活. 在最近的工作中,遇到一个做权限管理筛选的需求.简单总结需求:1展示一个组织中的组织结构2通过点击组织结构中的任意一个节点可以向上向下查询对应的组织结构 如果你不想 ...

  8. React-Hooks 学习概览

     React-Hooks的函数 组件方式代替原来的类继承,简化代码风格,好处是大大的: 1.useState  用来声明状态变量.要从三方面掌握:声明.读取.使用.(注意:useStae是不能进行条件 ...

  9. 【读后感】《Java编程思想》~ 异常

    [读后感]<Java编程思想>~异常 终于拿出压箱底的那本<Java编程思想>.这本书我年轻的时候就买了,但是翻过几页后就放弃了.没想到这两天翻了一下,真的有收获. 看了一下第 ...

  10. 基于springcloud框架搭建项目-Eureka篇(一)

    springcloud项目近年来算是很流行的了,不少公司项目目前都用到了,毕竟优点很多,刚好公司项目用到了,根据自己的理解,简单搭建一下,以便以后学习 这里简单的介绍一下它: SpringCloud, ...