# 4.如何完成线性表结构下的增删查

# 4-1 主要内容

  • 线性表是什么
  • 线性表对于数据的增删查处理
  • 线性表的一些案例

通过之前的学习,我们了解了,数据在代码中处理和加工的最小单位动作是增、删、查

# 4-2 什么是数据结构

数据结构就是数据的组织方式,在数据结构适用的场合中,需要一定量的数据
但计算机要处理大量数据时,同样需要考虑如何去组织这些数据,这就是数据结构(如小朋友站队方式:横排,列,圈)

# 4-3 什么是线性表

线性表是n个数据元素的有限序列,最常用的是链式表达,通常也叫做线性链表或者链表
在链表中存储的数据元素也叫做结点,一个结点存储的就是一条数据记录,每个结点的结构包括两个部分

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

在链表的最前面,通常会有一个头指针来指向第一个结点
对于链表的最后一个结点,由于在它之后没有下一个结点,因此它是空指针
链表结构,和小朋友手拉手站成一排的场景很相似

图示

上图可以看出,链表只能通过上一个结点的指针找到下一个结点,反过来行不通,这种就是单向链表

有时候我们为了弥补这种不足,可以对结点的结构进行改造:

  • 对于一个单向链表,让最后一个元素的指针指向第一个元素,就得到了循环链表;
  • 或者把结点的结构进行改造,再增加一个指向上一个结点的指针,这样就得到一个双向链表
  • 同样,还可以在双向链表的基础上和循环链表进行融合,就得到了双向循环链表

图示

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

# 4-4 线性表对于数据的增删查处理

# 4-4-1 增加

例子:有一个链表,存储了10个同学的考试成绩,现在有一个同学被忘记存储了,要怎么进行?
做法:只需要把待插入的结点的指针指向原指针的目标,把原来的指针指向插入的结点

图示

代码如下:

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

# 4-4-2 删除

删除操作:链表的删除和新增一样,如果待删除的结点为b,只需要把b的指针(p.next),指向b的指针指向的结点(p.next.next)

代码如下:

 p.next = p.next.next;

# 4-4-3 查找

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

这种和数组中的index非常类似,查出学号为5的同学,考试成绩是多少?
链表的查找功能很弱,所以只能一个一个遍历查找

  • 第二种情况按照具体成绩查询

查找出是否有同学成绩为95?
链表的价值在于用指针按照顺序连接了数据结点,但对每个结点的数值没有做任何整合,所以只能按照先后顺序遍历
所以解决办法:

  1、如果是,则返回有人得分为95
  2、如果不是,则需要通过指针去判断下一个结点的值是否为95,以此类推,直到所有结点被访问完

虽然链表在新增删除数据上有优势,但仔细就发现,这个优势不实用
主要是因为,在新增数据时,通常会伴随查找动作;例如:在第五个结点之后新增一个数据结点,就需要执行两个步骤:

 第一步:查找第五个结点
 第二步:新增结点,所以整体的复杂度为O(n) + O(1) = O(n)

如果数据元素个数不确定,删除插入的操作并不多,那么数组可能更适合

# 4-5 线性表案例

# 4-5-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-5-2 第二个例子:查出链表中间位置结点的数值

这个例子,也是利用了链表的长度无法或缺的不足做的文章,解决办法如下:

  • 一个暴力的办法是,先通过一次遍历去计算链表长度,这样我们就知道链表中间位置是第几个。接着再通过一次遍历去查找这个位置的数值
  • 除此之外,还有一个巧妙的办法,就是利用快慢指针进行处理。其中快指针每次循环往后跳转两次,而慢指针每次向后跳转一次
 while(fast && fast.next && fast.next.next){
   fast = fast.next.next;
   slow = fast.next;
 }

# 4-5-3 第三个例子:判断链表是否有环

链表的快慢指针的方法,在很多链表操作的场景下都非常适用
假如链表有环,这个环就像一个跑步赛道,多次循环,快指针和慢指针都会进入到赛道中,相对而言,快指针每循环会多走一步
这就意味着

  • 如果链表存在环,快指针和慢指针一定会在环内相遇,即fast === slow的情况一定会发生
  • 反之,则最终会完成循环,二者从未相遇

# 4-6 总结

  • 本节主要围绕线性链表原理、以及线性表对数据的增删查操作;线性链表结构的每个结点,由数据的数值指向下一个元素的指针构成
  • 链表在增、删方面比较容易实现,可以在O(1)的时间复杂度完成,但对于查找,不论是按位置还是按照数值,都是需要全部遍历
  • 线性表的价值在于,它对数据的存储都是按照顺序存储。当数据元素不确定,且经常对数据做增删操作时,链表会比较合适。