部分内容来源于 https://www.programmercarl.com/
0.引言
链表是非常基础的一种数据结构,在删除和插入方面比传统的数组结构更加的简单。
1.链表基础结构
1.1 什么是链表
链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
链接的入口节点称为链表的头结点也就是head。
1.2 链表分类
-
单链表。即只有一个指向后一个节点的指针。
-
双链表。每个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。
-
循环链表。链表首尾相连。
1.3 链表存储方式
数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。
链表是通过指针域的指针链接在内存中各个节点。
所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。
2.链表题常用思路
2.1 双指针法
在链表题中,也可以考虑双指针法。
2.2 递归法
链表特别适合使用递归的算法,因为很多链表的算法题都需要对节点进行一个个的判断。遇到这种类型的题目就可以考虑递归的方法。
底下是一个比较通用的递归代码模板,不同于其他的递归教程,这套流程针对性很强,可以很容易套进题目之中,具体流程和代码如下。
首先要明确递归的流程,在链表递归函数运行过程中,主要分为三个部分,1.每个节点依次进入递归函数,2.遇到边界条件后进行返回,3.一直返回到递归起点。
接下来,针对以上流程,进行递归代码的分解讲解。
首先,在编写之前,先确定好,以上3个部分中的13过程中,需要在哪个部分进行逻辑的处理。
- 在1过程需要进行逻辑处理,及从头到尾,每次进入递归函数,都会进行一个逻辑的处理,此时处理逻辑代码要写在递归函数之前。如果要将此次处理的结果传给后续节点,此时可以将变量设置为递归函数的变量进行传递,这样在每次往后递归之前都会先运行逻辑函数,并将得到的结果往后传递。
- 在3过程进行逻辑处理,即从后往前进行逻辑处理,此时逻辑代码要写在递归函数之后,返回值之前。 如果要将每次处理的结果传递给前面的节点,此时可以将节点为返回值,这样在每次递归函数运行结束后,会运行逻辑代码,并将返回值返回。
其次,在确定好逻辑代码过程后,可以实际开始编写代码了,第一步就是要写好退出条件,在链表中即表现为当前节点为空的情况,即已经到达最尾部了。
最后,补全最开始确定的逻辑代码,就可以完成整个递归函数的编写了。
func help(head *ListNode,n int)int{
//停止条件
if head==nil{
return n,false,nil
}
//往下递归前做的事情,n为往下传入的值
num:=help(head.Next,n)
//递归返回时做的事情
if num==0{
//做一些处理
}
return num //向上传递
}
1.1 题目训练
1.1.1 反转链表
Solution
按照以上的逻辑,可以使用两种方法,一种是在第一部分(从前往后)进行逻辑的处理,一种是在第三部分(从后往前)。
第一部分进行逻辑处理的时候,我们在首先要存好当前遍历的节点的下一个节点的地址,因为进行反转之后就找不到地址了。然后进行正常的反转即可,代码如下。
第三部分进行逻辑处理时,比较麻烦,首先反转的时候,我们需要前一个节点的地址,而从前往后传递只能作为递归函数的变量进行传递,所以我们设置递归函数其中一个变量为pre,进行传递。然后我们还需要返回最后一个节点,而从最后一个节点往前传值需要作为返回值传递,所以我们设置一个返回值即可,代码如下。
Code
//从前往后的逻辑处理
func reverseList(head *ListNode) *ListNode {
return help(nil, head)
}
func help(pre, head *ListNode)*ListNode{
if head == nil {
return pre
}
next := head.Next
head.Next = pre
return help(head, next)
}
//从后往前的逻辑处理
func reverseList(head *ListNode) *ListNode {
return help(head,nil)
}
func help(head *ListNode,pre *ListNode)*ListNode{
if head==nil{
return nil
}
result:=help(head.Next,head)
if head.Next==nil{
result=head
}
head.Next=pre
return result
}
2.3 迭代法
利用迭代法,可以很快的对每个节点进行判断和操作。
2.4 虚拟头结点
链表的一大问题就是操作当前节点必须要找前一个节点才能操作。这就造成了,头结点的尴尬,因为头结点没有前一个节点了。每次对应头结点的情况都要单独处理,所以使用虚拟头结点的技巧,就可以解决这个问题。