数据结构之线性表

223 阅读5分钟

链接:mp.weixin.qq.com/s/V2x4HJdzQ… 作者:公瑾

更多干货关注公众号 勾勾的Java宇宙(Javagogo)


什么是线性表

线性表是 n 个数据元素的有限序列,最常用的是链式表达,通常也叫作线性链表或者链表。

在链表中存储的数据元素也叫作结点,一个结点存储的就是一条数据记录。

每个结点的结构包括两个部分:

  • 第一是具体的数据值
  • 第二是指向下一个结点的指针

在链表的最前面,通常会有个头指针用来指向第一个结点。对于链表的最后一个结点,由于在它之后没有下一个结点,因此它的指针是个空指针。

链表结构,和小朋友手拉手站成一排的场景是非常相似的。

例如,你需要处理的数据集是 10 个同学考试的得分。如果用链表进行存储,就会得到如下的数据:

H -> 95 -> 92 -> 91 -> 93 -> 99 -> 90 -> 98 -> 94 -> 97 -> 96 -> null

仔细观察,你会发现这个链表只能通过上一个结点的指针找到下一个结点,反过来则是行不通的。因此,这样的链表也被称作单向链表

有时候为了弥补单向链表的不足,我们可以对结点的结构进行改造——

对于一个单向链表,让最后一个元素的指针指向第一个元素,就得到了循环链表

或者把结点的结构进行改造,除了有指向下一个结点的指针以外,再增加一个指向上一个结点的指针。这样就得到了双向链表

同样的,还可以对双向链表和循环链表进行融合,就得到了双向循环链表,如下图所示:

image.png

这些种类的链表,都是以单向链表为基础进行的变种。在某些场景下能提高线性表的效率。

线性表对于数据的增删查

说完线性表原理之后,我们围绕数据的增删查操作来看看线性表的表现。这里主要说单向链表的增删查操作,其他类型的链表与此雷同,不重复了。

首先看一下增加操作

如果有一个链表,它存储了 10 个同学的考试成绩。现在这个链表中,有一个同学的成绩忘了被存储进去。假设我们要把这个成绩在红色的结点之后插入,该如何进行呢?

其实,链表在执行数据新增的时候非常容易,只需要把待插入结点的指针指向原指针的目标,把原来的指针指向待插入的结点,就可以了。如下图所示:

image.png

代码如下:

s.next = p.next;
p.next = s;

接下来我们看一下删除操作

还是这个存储了同学们考试成绩的链表,假设里面有一个成绩的样本是被误操作放进来的,我们需要把这个样本删除。

链表的删除操作跟新增操作一样,都是非常简单的。如果待删除的结点为 b,那么只需要把指向 b 的指针(p.next),指向 b 的指针指向的结点(p.next.next)。如下图所示:

image.png

代码如下:

p.next = p.next.next;

最后,我们再来看看查找操作,查找操作有两种情况。

  1. 第一种情况是按照位置序号来查找。

它和数组中的 index 是非常类似的。假设一个链表中,按照学号存储了 10 个同学的考试成绩。现在要查找出学号等于 5 的同学,他的考试成绩是多少,该怎么办呢?

其实,链表的查找功能是比较弱的,对于这个查找问题,唯一的办法就是一个一个地遍历去查找。也就是,从头开始,先找到学号为 1 的同学,再经过他跳转到学号为 2 的同学。直到经过多次跳转,找到了学号为 5 的同学,才能取出这个同学的成绩。如下图所示:

  1. 第二种情况是按照具体的成绩来查找。

同样,假设在一个链表中,存储了 10 个同学的考试成绩。现在要查找出是否有人得分为 95 分。链表的价值在于用指针按照顺序连接了数据结点,但对于每个结点的数值则没有任何整合。当需要按照数值的条件进行查找时,除了按照先后顺序进行遍历,别无他法。

因此,解决方案是,判断第一个结点的值是否等于 95。

  • 如果是,则返回有人得分为 95 分

image.png

  • 如果不是,则需要通过指针去判断下一个结点的值是否等于 95

image.png

以此类推,直到把所有结点都访问完。

你不难发现,链表在新增、删除数据都比较容易,可以在 O(1) 的时间复杂度内完成。但对于查找,不管是按照位置的查找还是按照数值条件的查找,都需要对全部数据进行遍历。这显然就是 O(n) 的时间复杂度。

虽然链表在新增和删除数据上有优势,但仔细思考就会发现,这个优势并不实用。这主要是因为,在新增数据时,通常会伴随一个查找的动作。

例如,在第五个结点后,新增一个新的数据结点,那么执行的操作就包含两个步骤:

  1. 查找第五个结点;

  2. 再新增一个数据结点。整体的复杂度就是 O(n)+O(1),这也等同于 O(n) 的时间复杂度。

总结

线性表真正的价值在于,它对数据的存储方式是按照顺序的存储。如果数据的元素个数不确定,且需要经常进行数据的新增和删除时,那么链表会比较合适。如果数据元素大小确定,删除插入的操作并不多,那么数组可能更适合些。


链接:mp.weixin.qq.com/s/V2x4HJdzQ… 作者:公瑾

更多干货关注公众号 勾勾的Java宇宙(Javagogo)