线性表基本操作

数据在代码中被处理和操作的最小单位动作是增、删、查。他们是深入学习数据结构的根基,通过“增删查”操作,我们可以选择更合适的数据结构来解决实际工作中遇到的问题。例如几个客户端分别分别向服务端发送请求,服务端要采取先到先得的处理方式,应该如何设计数据结构呢?本文介绍数据结构之一,线性表。

1.什么是线性表

数据结构是数据的组织方式。那么线性表就是数据的组织方式之一。线性表是n个数据元素的有限列表,最常用的是链式表达,通常也叫作线性链表或链表
在链表中存储的数据元素叫做结点,一个结点存储的就是一条数据记录。每个结点的结构包括两个部分:一是具体的数据值;二是指向下一结点的指针

  • 在链表最前面通常有个头指针用来指向第一个结点
  • 最后一个结点的指针通常是空指针
    链表通常分为:单向链表、双向链表、循环链表、双向循环链表。

2.链表的基本操作

在这里主要介绍单向链表的增删查操作,其他类型的链表与此雷同,就不再重复介绍了。

2.1 增

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

线性表基本操作_第1张图片
代码如下:

s.next = p.next;
p.next = s;
2.2 删

接下来删除操作。链表的删除操作跟新增操作一样,都是非常简单的。如果待删除的结点为 b,那么只需要把指向 b 的指针 (p.next),指向 b 的指针指向的结点(p.next.next)。如下图所示:
线性表基本操作_第2张图片
代码如下:

p.next = p.next.next;
2.3 查

最后,我们再来看看查找操作。我们在前面的课时中提到过,查找操作有两种情况:

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

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

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

  • 第二种情况是按照具体的值来查找。

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

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

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

如果不是,则需要通过指针去判断下一个结点的值是否等于 95。以此类推,直到把所有结点都访问完。

3.链表的翻转

例 1,链表的翻转。给定一个链表,输出翻转后的链表。例如,输入1 ->2 -> 3 -> 4 ->5,输出 5 -> 4 -> 3 -> 2 -> 1。

我们来仔细看一下这个问题的难点在哪里,这里有两种情况:

如果是数组的翻转,这会非常容易。原因在于,数组在连续的空间进行存储,可以直接求解出数组的长度。而且,数组可以通过索引值去查找元素,然后对相应的数据进行交换操作而完成翻转。

但对于某个单向链表,它的指针结构造成了它的数据通路有去无回,一旦修改了某个指针,后面的数据就会造成失联的状态。为了解决这个问题,我们需要构造三个指针 prev、curr 和 next,对当前结点、以及它之前和之后的结点进行缓存,再完成翻转动作。

while(curr){
    next = curr.next;
    curr.next = prev;
    prev = curr;
    curr = next;
}

4.链表的快慢指针

例 2,给定一个奇数个元素的链表,查找出这个链表中间位置的结点的数值。

这个问题也是利用了链表的长度无法直接获取的不足做文章,解决办法如下:

一个暴力的办法是,先通过一次遍历去计算链表的长度,这样我们就知道了链表中间位置是第几个。接着再通过一次遍历去查找这个位置的数值。

除此之外,还有一个巧妙的办法,就是利用快慢指针进行处理。其中快指针每次循环向后跳转两次,而慢指针每次向后跳转一次。

while(fast && fast.next && fast.next.next){
    fast = fast.next.next;
    slow = slow.next;
}

5. 总结

  • 当数据的元素个数不确定,且需要经常进行数据的新增和删除时,那么链表会比较合适
  • 当数据元素大小不确定,删除插入操作并不多,那么数组比较合适

你可能感兴趣的:(数据结构)