链表反转一般有两种解法:递归和迭代。首先给出链表的定义
typedef struct tagLNode {//带头结点的单链表
int data;
struct tagLNode *next;//尾结点为0表示结束。若尾结点指向头结点则为循环单链表
}LinkList;
假设链表为head->1->2->3->4->5->NULL。
递归法
LinkList * reverse(LinkList *head) {
if (head == NULL) return 0;
else if (head->next == NULL) return head;
LinkList *temp=reverse(head->next);
//temp->next=head;
head->next->next = head;
head->next = NULL;
return temp;
}
一开始我的想法是,先递归到最后一层,也就是reverse(5),这层的运行结果是返回5号结点给上层的temp,那么我们回到了第四层:head就是4号结点,temp是5号。让temp的后续变成head不行吗?在这一层是可以的。那么我们再回到第三层,head是3号结点,temp是5号结点(请注意,后续为4号结点),再执行temp->next=head时,5号结点的后续又变为3号,问题来了,程序好像在打转。所以问题在于我们返回的temp永远是5号结点,不能更改temp的后继。正确做法是head->next->next = head,每层发生的事情与temp无关,它只负责返回5号结点。
这里想说说递归的缺点:首先递归存在重复计算(参考斐波那契数列),而且每次递归都是一次函数调用,每次调用都需要在内存栈中分配空间来保存参数、返回地址和临时变量,这就导致了弹栈入栈的时间消耗。递归还可能导致调用栈溢出:每个进程栈的容量是有限的,调用层级太多就会超出栈的容量,导致调用栈溢出。
迭代法
LinkList * reverse(LinkList *head) {
if (head == NULL || head->next == NULL) return head;//0个或1个元素时无须反转
LinkList *prev = head;//前续结点
LinkList *p = head->next;//当前结点
prev->next = NULL;//头结点后继应为空
if (p->next == NULL) {//如果只有2个元素,应特殊处理
p->next = prev;
return p;
}
LinkList *next = p->next;//后继结点
while (next){
p->next = prev;//当前结点的后继应为前一结点
prev = p;
p = next;
next = next->next;//这三结点都往移动
}
p->next = prev;//最后的一个结点手动指向prev
return p;//返回最后一个结点,也是新链表的头结点
}
迭代法的好处就是简单易懂:由于每次循环会断开p与原始后继的链子,需要用next指针来保存后继结点,然后就是循环调用p->next=prev,移动这三个结点即可。
总结
我总以为自己理解的链表的操作,可是隔一段时间就忘了,可能是没有深入分析。另外这是我的第一篇博客,也是被一位大佬鼓动,加油吧!