zoukankan      html  css  js  c++  java
  • 全面分析再动手的习惯:链表的反转问题(递归和非递归方式)

    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结点
    • 进行循环...
  • 相关阅读:
    多线程22:线程池
    多线程21:信号灯法
    多线程20:管程法
    多线程19:生产者消费者模式
    多线程18:Lock锁
    多线程17:死锁
    多线程16:CopyOnWriteArrayList
    多线程15:线程同步
    多线程14:三大不安全案例
    业余草 maven异常:Updating Maven Project 的统一解决方案
  • 原文地址:https://www.cnblogs.com/kungfupanda/p/11223477.html
Copyright © 2011-2022 走看看