全面分析再動手的習慣:鏈表的反轉問題(遞歸和非遞歸方式)


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結點
  • 進行循環...


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM