1.找到链表中间节点
关于中间节点我们最容易想到的办法是先计算链表的长度,然后找到len/2的位置,但是这么找相当于将整链表遍历一次,还要在走一半,比较麻烦,这里介绍一个更加方便快捷的方式,快慢指针。快指针每次走两步,慢指针每次走一步,那么当快指针走到链表的尾结点时,慢指针刚好走到中间节点的位置。

但是这时存在两种情况,当节点个数为奇数时,pfast会指向NULL,当节点个数为偶数个时,pfast会刚好在最后一个节点上,但是此时如果在走两步就相当于第一步走到NULL上,第二步要走到NULL存的地址,但是NULL是不能访问的,如果访问了会出现段错误。为了适应所有链表,所以在这里我们判断有没有走到尾结点有两个条件,当pfast==NULL和pfast->next==NULL都代表指针走到尾节点了,我们要在没走到尾节点的情况下移动,所以循环的条件为pfast!=NULL && pfast->next!=NULL。

快指针每次走两步也就是pfast->next->next,慢指针每次走一步也就是pslow->next。
2.找到倒数第K个节点
这里依旧会有容易想到的遍历链表的方法,但是这种方法执行效率很低,快慢指针能够减少内存访问次数,不需要额外存储链表长度,在单向链表中不需要二次从头扫描;两者时间复杂度同为 O (n),但快慢指针实际执行效率更高。

在这里我们让快指针先走k步,然后两个指针一起向前走,那么当快指针走到NULL的时候,慢指针就到倒数第K个节点了。但是如果链表只有五个数,但是我们要找倒数第6个数这种情况我们就要返回NULL。

3.判断链表有没有环
这块我们就不能通过遍历去算链表长度了,因为在链表里是个循环,一直找不到NULL,所以这里我们依旧使用快慢指针。在这里我举个例子,在操场上跑步时,所有人从同一起点出发,但是当最快的一名超过最后一名时,这时候就是套圈了,那么如果快指针一直没遇到NULL,还与慢指针重合了,那么就说明这个链表是有环的,快指针完成了对慢指针的套圈。

所以这里我们让快指针走2步,慢指针走一步,如果在快指针遇到没遇到NULL,还与慢指针重合了,那么这就是个有环链表,如果快指针走到了NULL或者是pfast->next==NULL,都代表没环。(因为我们还是不知道这个链表的节点数是技术个还是偶数个,所以判断条件是两个,原因在第一个练习解释过了)。

4.链表逆序
上一篇我们写了一个头插的代码,当我们头插的方式插入1,2,3,4,5时打印链表的结果为5,4,3,2,1,所以我们将这个链表拆分为一个头结点和一个无头链表,然后我们再把无头链表的数据一次以头插方式插入头节点,就实现了逆序。

这里要注意的是当我们把数据插入头结点时需要保存一下他的next,因为当这个节点插入之后他的next就会变成NULL或者他下一个节点的地址,如果不保存一下,我们就相当于直接找不到第二个节点在哪了,这个链表就丢失了。
5.排序
(1)选择
这里有两种写法,一种使用while循环一种使用for循环本质上都是一样的。


先回忆一下数组选择排序,核心思想是给合适的位置选择合适的数(找到最小值放在第一个位置......),外层循环控制位置,内层循环控制找最小值。在链表中如果是个空链表或者链表中只有一个值,这些都是不需要排序的情况。我们定义指针p_pos让他指向第一个有效节点,那么当他走到倒数第二个节点(也就是p_pos->next==NULL的时候)时就可以结束了,因为倒数第二个节点的值确定了,最后一个值也去确定了。定义一个指针p指向待确定位置的下一个位置,依次将其与待确定位置的值进行比较,当p指向的值小于待确定位置的值时就交换,当q走到NULL时就代表所有值都和待确定位置的值比较过了。
(2)冒泡
还是写了两种方式,挑选一种你喜欢的即可。

这里的判断条件略有不同,因为每确定一个值end会向前走一步,所以当end走到p_pos的时候也是整个链表全部排完序的时候。

还是来先回忆一下数组的冒泡排序,核心思想是相邻元素两两比较,小的放前,大的放后,外层循环控制趟数,内层循环控制比较次数。需要比较len-1趟,因为确定完倒数第二个数,最后一个数也确定了。没确定一个值,那么这一趟比较的次数就可以减1。在链表中用p_pos来控制趟数,当p_pos走到next为NULL的位置时就全部排完了,q来指向正在比较的值,比较q指向的值和他指向的下一个值,但是在链表中我们无法确定这个节点时第几个,所以我们就需要一个指针end专门指向这一趟比较结束的位置。最开始的时候end==NULL,因为要比较两个值是p当前指向的值和p->next的值,如果p指向这个节点的地址域为NULL那么p->next->data就会到NULL去找值,就会报段错误,所以这里的条件必须是q->next!=end(就是指向最后一个值的前一个值)。
(3)插入
链表的插入排序和数组的插入排序就略有不同了,但是我们还是回顾一下输出的插入排序吧。数组的插入排序是从没排好序的区域拿出来一个值,和排好序区域的最后一个值进行比较,直到找到比这个值小的值,将其插入到比他小的这个值后面。

但是在链表中不同,虽然我们可以找到尾节点,但是我们不能通过尾结点找到上一个节点,所以就要从头结点开始,和第一个有效节点进行比较,直到找到一个比要插入的值大的数据,然后将其插入到这个值之前。但是还是那个问题,如果我们直接让指针指向和要插入的值比较的节点,那当我们要插入时就找不到这个节点的上一个节点了,所以问你要让p_insert指向比较节点的上一个节点。最后要注意的就是里我们在无序区取完数字就得保存一下下一个节点的地址,不然这个节点的地址域以被覆盖,就找不到无序区了。
