單鏈表逆序


 

  題目:給定一個帶附加頭節點的單鏈表,設first為其頭指針,節點的結構為(data,link),data為數據域,link為指針域,試寫出算法:通過遍歷一趟鏈表,將鏈表中所有節點逆序連接

  分析:這是很經典的“單鏈表逆序”問題。很多公司的面試題庫中都有這道題,有的公司明確題目要求不能開辟額外的節點空間(否則可以在遍歷原鏈表的同時使用前插法建立一個逆序鏈表),有的沒有明確說明,但是如果面試者使用了額外的節點存儲空間,會得到一個比較低的分數。如何在不使用額外節點的情況下使一個單鏈表的所有節點逆序?我們先用迭代循環的思想來分析這個問題。

    這種方法需要另外定義一個前驅指針prev和后繼指針latter。初始狀態,p指向當前節點即第一個節點A,prev為NULL,latter指向A的下一個節點B。鏈表的初始狀態如下圖所示:

 

        

     

     從A節點開始逆序,首先將A節點的link指針指向prev(因為prev的當前值是NULL,所以A節點就從鏈表中脫離出來了),然后整體后移,prev指向p,后移p和latter指針。逆向節點A之后,鏈表的狀態如下圖所示:

                 

 

      從圖(1)的初始狀態到圖(2)狀態共做了四個操作,這四個操作的偽代碼如下:

p->link = prev;
prev = p;
p = latter;
latter = p->link;

  

     這四行偽代碼就是循環算法的迭代體了,現在用這個迭代體對圖(2)的狀態再進行一輪迭代,就得到了下圖的狀態:

      

 

     那么循環終止條件呢?現在對圖(3)的狀態再迭代一次得到下圖的狀態:

       

 

     此時可以看出,在圖(4)的基礎上再進行一次迭代就可以完成鏈表的逆序,因此循環迭代的終止條件就是p指針為NULL。

     當然,最后還要注意將附加頭節點放到首節點的前面

  源碼

template<class T>
void List<T>::Inverse()                
{
    if(first == NULL)    return;
    LinkNode<T> *p, *prev, *latter;    
    p = first->link;      // 當前結點
    prev = NULL;          // 前一結點
    latter = p->link;      // 下一結點
    while(p != NULL)
    {
        p->link = prev;      // 當前結點指針指向前一結點
        prev = p;            // 后移
        p = latter;
        if(p != NULL)        // 如果p指針是NULL,已經滿足終止條件
            latter = p->link;
    }
    first->link = prev;;    // 最后連上附加頭結點
}

 

    

   現在,我們用遞歸的思想來分析這個問題。先假設有這樣一個函數reverseLink,可以將以head為頭節點的單鏈表逆序,並返回新的頭節點指針,函數原型如下:

LinkNode<T>* reverseLink(LinkNode<T> *head)

 

   現在利用reverseLink對問題進行求解,將鏈表分為當前頭節點和其余節點,遞歸的思想就是:先將當前的頭節點從鏈表中拆出來(附加頭節點暫時忽略,只要最后連上即可),然后對剩余的節點進行逆序,最后將當前的表頭節點連接到新鏈表的尾部。第一次遞歸調用reverseLink函數時的狀態如下圖所示:

   注意:遞歸過程的狀態圖並不反應程序執行順序的狀態,而是設計遞歸過程邏輯順序的狀態,這一點容易產生誤解。

      

   這里邊的關鍵點是頭節點head的下一個節點head->link將是逆序后的新鏈表的尾節點,也就是說,被摘除的頭接點head需要被連接到head->link->link才能完成整個鏈表的逆序,遞歸算法的核心就是以下幾行代碼:

newHead = reverseLink(head->link); /*遞歸部分*/
head->link->link = head;       /*回朔部分*/
head->link = NULL;

 

   現在順着這個思路再進行一次遞歸,就得到第二次遞歸的狀態圖:

    

   再進行一次遞歸分析,就能清楚地看到遞歸終止條件了:

    

   遞歸終止條件就是鏈表只剩一個節點時直接返回這個節點的指針。可以看出這個算法的核心其實是在回朔部分,遞歸的目的是遍歷到鏈表的尾節點,然后通過逐級回朔將節點的link指針翻轉過來。

   源碼

template<class T>        
LinkNode<T>* List<T>::reverseLink(LinkNode<T> *head)    //遞歸逆置鏈表
{
    if((head == NULL) || (head->link == NULL))    return head;//遞歸終止條件
    LinkNode<T> *newHead;
    newHead = reverseLink(head->link); /*遞歸部分*/
    head->link->link = head;       /*回朔部分*/
    head->link = NULL;
    return newHead;
}

template<class T>
void List<T>::Inverse()                //迭代循環逆置鏈表
{
    first->link = reverseLink(first->link);//調用遞歸函數,返回逆序鏈表新頭節點的指針,並連接到附加頭節點之后
}

 

 

   循環迭代還是遞歸?這是個問題。當面對一個問題的時候,不能一概認為哪種算法好,哪種不好,而是要根據問題的類型和規模作出選擇。對於線性數據結構,比較適合用循環迭代方法,而對於樹狀數據結構,比如二叉樹,遞歸方法則非常簡潔優雅。

 


免責聲明!

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



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