全面分析再动手的习惯:链表的反转问题(递归和非递归方式)

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结点
  • 进行循环...
posted @ 2019-07-21 23:55  功夫 熊猫  阅读(1419)  评论(0编辑  收藏  举报