一、前言
相信许多小可爱在刚接触链表的时候都会接触一个问题——如何不借助辅助链表实现单链表的反转,也许看到这里之前你一定很头疼,但是别急,看完这篇,你会发现链表竟是那么神奇
二、迭代法
1、算法原理
我们进行反转的思路是通过把原链表的头结点的下一个结点拎出来,然后让他成为新的头节点,当原来的头结点成为尾结点的时候就说明反转完成了。
在反转之前,我们假设链表是这样的
没错,我们要实现的是头节点存数据的单链表的反转,习惯头节点不存数据的小可爱也不必担心,基本原理是类似的,到时候根据原理自己写出来的时候相信你一定会感悟颇深的。
好的,回归正题。
我们首先做的一步工作非常的简单——先判断链表是否为空,如果是个空链表,那还反转个啥呀?直接咋来的咋给他返回回去,剩下的时间就可以摸鱼啦!(手动兴奋)
哈哈,开个玩笑,当链表不为空的时候,应该怎样操作呢?
首先,我们用一个flag
记录下原来链表中的头节点。
紧接着,用一个newNode
记录下flag的下一个元素,即flag->next
,这就是我们这次循环要操作的元素了
接下来就到重头戏了,屏住呼吸,我们让flag->next=newNode->next
,也就是把“2”这个结点从链表中删除掉。
将此结点从链表中先除掉是为了让他可以成为新的头节点而不影响其他结点。
之后我们让链表的头节点(head
)指向这个被操作的结点,也就是“2”,既然操作之后此节点成为了新的头结点,那自然是要让头结点指向这个结点了呀。
自此,我们迭代的第一次成功完成了(👏)。
之后的操作就类似于以上操作,让“3”指向“2”,“4”指向“3”,“5”指向“4”……
那么问题来了,我们如何去判断何时我们的反转工作彻底结束呢?那我先反客为主一波,我先问一下各位,有没有想过为啥我要给一个变量命名为“flag”呢?
相信有结论的小可爱一定可以猜到上一个问题的答案了——没错,就是用flag作为循环的条件。
我们每操作一个结点之前,都会让flag的下一个结点指向被操作节点的下一个结点(比如操作“2”就让flag的下一个结点指向“3”),也就是flag->next=newNode->next。当被操作结点是原链表的最后一个结点的时候,就意味着我们的工作进行到最后一次迭代了,本次迭代中,flag->next将会等于NULL(单链表的最后一个结点的next就是NULL呀,不会还有小可爱不知道吧)。所以说我们只要判断是否flag->next==NULL就可以知道知道是否结束循环。
算法原理就是这些,接下来是代码实现:
2、代码实现
struct ListNode* reverseList(struct ListNode* head) {
if (!head) //判断链表是否为空,如果是空链表则直接返回
return head;
struct ListNode* flag = head; //让flag指向当前原链表的结节点,用于之后找到要操作的结点以及判断循环是否结束
while (flag->next) //进入循环,当flag成为最后一个结点的时候退出循环
{
struct ListNode* newNode = flag->next; //用于记录要操作的结点
flag->next = newNode->next; //将要操作的结点先从原链表中删除
newNode->next = head; //让被操作结点的next指向他的上一个结点,也就是当前的头节点
head = newNode; //让头节点指向被操作的结点
}
return head; //返回头结点
}
二、递归法
1、算法原理
递归算法的思想是直接让链表的后一个结点指向他的前一个结点。是不是很惊讶,单链表不是只能向后遍历的吗,怎么能指向他的上一个结点呢?别急,看我的细节。
其实就是用了栈的思想(好像递归就i是栈哈,手动滑稽),让链表的每一个结点依次入栈,当检索到是最后一个结点的时候停止进栈,在出栈的时候就是从最后一个结点向第一个结点进行操作,并且每此都返回原链表的尾结点,最后就会把原链表的尾结点返回到主函数里面,因为反转之后的链表的头节点就是原链表的尾结点嘛。
在递归过程中首先,我们假设n个结点中从第k+1个结点到第n个结点都已经完成反转(意味着最先被操作的结点是最后一个结点)。
现在正处在第k个结点,要让他的第k+1个结点指向他的第k个结点,于是我们让k结点的next
结点(就是k+1)的next
结点指向k结点。没错,就是这么简单,是不是很神奇!
当然,也不是说就做到这一步就可以了,毕竟好事总是多磨的嘛!让我们想一想一些边界情况:比如当k为第一个结点的时候进行完这步操作就会是这样的:
聪明的你一定已经意识到问题的所在了,链表的尾结点本应该是要指向NULL的,但是现在却指向倒数第二个结点了,所以我们在上一步操作之后还要再加一步,让当前结点指向NULL。
这样当当前结点是第一个结点的时候他的下一个结点就是我们想要的NULL了
至此,一次递归完成。不过别忘了返回原链表的尾结点哦。
2、代码实现
struct ListNode* reverseList(struct ListNode* head)
{
if (head == NULL || head->next == NULL) //当当前结点为原链表的尾结点时返回尾结点
{
return head;
}
struct ListNode* newHead = reverseList(head->next); //接收返回的尾结点,传入的参数是当前结点的下一个结点
head->next->next = head; //让当前结点下一个结点的下一个结点指向当前结点
head->next = NULL; //让当前结点指向NULL
return newHead; //返回尾结点
}
四、结束语
好像也没啥好说的了,不过毕竟是人生中第一篇博客,多少有点小激动,总感觉想说点什么。emn……希望各位可以从中有所收获,如果有大佬有建议或者发现文章中有不对的地方,也希望我这块砖可以引来美玉啊。最后的最后,都看到这里了,不如点个关注呗,等我火了,你就是我的老粉啦!