全面分析再动手的习惯:链表的反转问题(递归和非递归方式)
1.
https://www.cnblogs.com/kubixuesheng/p/4394509.html
定义一个方法(函数),实现输入一个链表的头结点,然后可以反转这个链表的方向,并输出反转之后的链表的头结点。
typedef struct Node{ int data; Node *next; } Node, *List;
链表类的问题,涉及到了很多指针的操作,需要严谨的分析,全面的分析问题之后,在开始写代码,磨刀不误砍柴工!反转链表,直接的想法,就是把链表中指针的方向反转就可以了,如图所示:
假设 i 结点之前,我们把所有的结点的指针都已经反转了,那么自然 i 和以后的结点链接发生了断裂!如下图;
这样的话,无法继续遍历 i 以后的结点了,那么自然想到,在断链之前,提前保存之前的状态。那么自然想到定义三个指针,分别指向当前结点 i,i 的后继 j,i 的前驱 h 结点。保存断链之前的三个结点的连接状态。然后,假设没问题了,那么继续反转完毕,最后链表的尾结点就是反正链表的头结点了,也就是 next 为 null 的结点,是原始链表的尾结点。
#include <iostream> using namespace std; typedef struct Node{ int data; Node *next; } Node, *List; Node * reverseList(List head){ //定义三个指针,保存原来的连接的状态 //当前结点指针 Node *pnow = head; //当前结点的前驱指针,初始化是 NULL Node *pre = NULL; //当前结点的后继指针,初始化也是 null Node *pnext = NULL; //定义尾指针 Node *tail = NULL; //开始遍历链表 while(pnow != NULL){ //如果当前结点不是 null,那么初始化 pnext 指针指向当前结点的下一个结点 pnext = pnow->next; //如果找到了尾结点,初始化 tail 指针 if(NULL == pnext){ tail = pnow; } //进行链表的反转,当前结点的 next 指针指向前一个结点,实现链表方向的反转,此时发生了断链 pnow->next = pre; //勿忘断链的情形,需要使用 pre 指针保存状态,pre 等价于是后移一个结点 pre = pnow; //pnow 后移一个结点 pnow = pnext; } return tail; }
定义的这个三个指针,目的就是防止断链之后无法继续遍历链表以后的结点,实现全部的反转。当 pnow 的 next 指向 pnow 的前驱pre(初始化是 null)的时候,已经实现了 pnow 和前驱pre的方向反转,但是 pnow 此时就和后继pnext断链了,那么使用 pre 后移的方式,指向 pnow,同时 pnow 也后移,指向 pnext,而 pnext 继续指向更新之后的 pnow 的 next 结点即可。从而实现了状态的保存,继续遍历全部结点,实现链表反转。
注意关于链表问题的常见注意点的思考:
1、如果输入的头结点是 NULL,或者整个链表只有一个结点的时候
2、链表断裂的考虑
下面看看递归的实现方式
递归的方法其实是非常巧的,它利用递归走到链表的末端,然后再更新每一个node的next 值 ,实现链表的反转。而newhead 的值没有发生改变,为该链表的最后一个结点,所以,反转后,我们可以得到新链表的head。
//递归方式 Node * reverseList(List head) { //如果链表为空或者链表中只有一个元素 if(head == NULL || head->next == NULL) { return head; } else { //先反转后面的链表,走到链表的末端结点 Node *newhead = reverseList(head->next); //再将当前节点设置为后面节点的后续节点 head->next->next = head; head->next = NULL; return newhead; } }
========================
2.
https://blog.csdn.net/u013132035/article/details/80589657
题目:
定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。链表结点定义如下:
struct ListNode{
int m_nKey,
ListNode * m_pNext;
}
思路:
为了正确地反转一个链表,需要调整链表中指针的方向。为了将复杂的过程说清楚,这里借助于下面的这张图片。
上面的图中所示的链表中,h、i和j是3个相邻的结点。假设经过若干操作,我们已经把h结点之前的指针调整完毕,这个结点的m_pNext都指向前面的一个结点。接下来我们把i的m_pNext指向h,此时结构如上图所示。
从上图注意到,由于结点i的m_pNext都指向了它的前一个结点,导致我们无法在链表中遍历到结点j。为了避免链表在i处断裂,我们需要在调整结点i的m_pNext之前,把结点j保存下来。
即在调整结点i的m_pNext指针时,除了需要知道结点i本身之外,还需要i的前一个结点h,因为我们需要把结点i的m_pNext指向结点h。同时,还需要实现保存i的一个结点j,以防止链表断开。故我们需要定义3个指针,分别指向当前遍历到的结点、它的前一个结点及后一个结点。故反转结束后,新链表的头的结点就是原来链表的尾部结点。尾部结点为m_pNext为null的结点。
代码实现:
public class ListNode {
public int data;
public ListNode next;
}
public ListNode reverseList(ListNode pHead){
ListNode pReversedHead = null; //反转过后的单链表存储头结点
ListNode pNode = pHead; //定义pNode指向pHead;
ListNode pPrev = null; //定义存储前一个结点
while(pNode != null){
ListNode pNext = pNode.next; //定义pNext指向pNode的下一个结点
if(pNext==null){ //如果pNode的下一个结点为空,则pNode即为结果
pReversedHead = pNode;
}
pNode.next = pPrev; //修改pNode的指针域指向pPrev
pPrev = pNode; //将pNode结点复制给pPrev
pNode = pNext; //将pNode的下一个结点复制给pNode
}
return pReversedHead;
}
小结:
这道题考查我们是否真正的理解了单链表的结构,以及在反转单链表时对指针的修改。
扩展:
我们知道这个题其实是可以用递归实现使单链表变成反转链表。
代码实现:
public ListNode reverseList3(ListNode pHead){
if(pHead==null || pHead.next == null){ //如果没有结点或者只有一个结点直接返回pHead
return pHead;
}
ListNode pNext = pHead.next; //保存当前结点的下一结点
pHead.next = null; //打断当前结点的指针域
ListNode reverseHead = reverseList3(pNext); //递归结束时reverseHead一定是新链表的头结点
pNext.next = pHead; //修改指针域
return reverseHead;
}
================
3.
https://www.jianshu.com/p/36ed87e1937a
要求很简单,输入一个链表,反转链表后,输出新链表的表头。
反转链表是有2种方法(递归法,遍历法)实现的,面试官最爱考察的算法无非是斐波那契数列和单链表反转,递归方法实现链表反转比较优雅,但是对于不了解递归的同学来说还是有理解难度的。
递归法
总体来说,递归法是从最后一个Node开始,在弹栈的过程中将指针顺序置换的。
为了方便理解,我们以 1->2->3->4这个链表来做演示。输出的效果是4->3->2->1
首先定义Node:
public static class Node {
public int value;
public Node next;
public Node(int data) {
this.value = data;
}
}
反转方法如下:
public Node reverse(Node head) {
if (head == null || head.next == null)
return head;
Node temp = head.next;
Node newHead = reverse(head.next);
temp.next = head;
head.next = null;
return newHead;
}
递归实质上就是系统帮你压栈的过程,系统在压栈的时候会保留现场。
我们来看是怎样的一个递归过程:1->2->3->4
- 程序到达Node newHead = reverse(head.next);时进入递归
- 我们假设此时递归到了3结点,此时head=3结点,temp=3结点.next(实际上是4结点)
- 执行Node newHead = reverse(head.next);传入的head.next是4结点,返回的newHead是4结点。
- 接下来就是弹栈过程了
- 程序继续执行 temp.next = head就相当于4->3
- head.next = null 即把 3结点指向4结点的指针断掉。
- 返回新链表的头结点newHead
注意:当retuen后,系统会恢复2结点压栈时的现场,此时的head=2结点;temp=2结点.next(3结点),再进行上述的操作。最后完成整个链表的翻转。
遍历法
遍历法就是在链表遍历的过程中将指针顺序置换
先上代码:
public static Node reverseList(Node node) {
Node pre = null;
Node next = null;
while (node != null) {
next = node.next;
node.next = pre;
pre = node;
node = next;
}
return pre;
}
依旧是1->2->3->4
- 准备两个空结点 pre用来保存先前结点、next用来做临时变量
- 在头结点node遍历的时候此时为1结点
- next = 1结点.next(2结点)
- 1结点.next=pre(null)
- pre = 1结点
- node = 2结点
- 进行下一次循环node=2结点
- next = 2结点.next(3结点)
- 2结点.next=pre(1结点)=>即完成2->1
- pre = 2结点
- node = 3结点
- 进行循环...