【數據結構】單鏈表逆序

二、 單鏈表逆序面試

         第二個題目是很經典的「單鏈表逆序」問題。不少公司的面試題庫中都有這道題,有的公司明確題目要求不能使用額外的節點存儲空間,有的沒有明確說明,可是若是面試者使用了額外的節點存儲空間作中轉,會獲得一個比較低的分數。如何在不使用額外存儲節點的狀況下使一個單鏈表的全部節點逆序?咱們先用迭代循環的思想來分析這個問題,鏈表的初始狀態如圖(1)所示:算法

圖(1)初始狀態數據結構

 初始狀態,prevNULLhead指向當前的頭節點Anext指向A節點的下一個節點B。首先從A節點開始逆序,將A節點的next指針指向prev,由於prev的當前值是NULL,因此A節點就從鏈表中脫離出來了,而後移動headnext指針,使它們分別指向B節點和B的下一個節點C(由於當前的next已經指向B節點了,所以修改A節點的next指針不會致使鏈表丟失)。逆向節點A以後,鏈表的狀態如圖(2)所示:函數

圖(2)通過第一次迭代後的狀態spa

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

 

head->next = prev;blog

prev = head;遞歸

head = next;ci

next = head->next;table

 

這四行僞代碼就是循環算法的迭代體了,如今用這個迭代體對圖(2)的狀態再進行一輪迭代,就獲得了圖(3)的狀態:

圖(3)通過第二次迭代後的狀態

         那麼循環終止條件呢?如今對圖(3)的狀態再迭代一次獲得圖(4)的狀態:

圖(4)通過第三次迭代後的狀態

 此時能夠看出,在圖(4)的基礎上再進行一次迭代就能夠完成鏈表的逆序,所以循環迭代的終止條件就是當前的head指針是NULL

        如今來總結一下,循環的初始條件是:

prev = NULL;

 

循環迭代體是:

next = head->next;

head->next = prev;

prev = head;

head = next;

 

循環終止條件是:

head == NULL

 

根據以上分析結果,逆序單鏈表的循環算法以下所示:

   61 LINK_NODE *ReverseLink(LINK_NODE *head)

   62 {

   63     LINK_NODE *next;

   64     LINK_NODE *prev = NULL;

   65 

   66     while(head != NULL)

   67     {

   68         next = head->next;

   69         head->next = prev;

   70         prev = head;

   71         head = next;

   72     }

   73 

   74     return prev;

   75 }

        如今,咱們用遞歸的思想來分析這個問題。先假設有這樣一個函數,能夠將以head爲頭節點的單鏈表逆序,並返回新的頭節點指針,應該是這個樣子:

   77 LINK_NODE *ReverseLink2(LINK_NODE *head)

如今利用ReverseLink2()對問題進行求解,將鏈表分爲當前表頭節點和其他節點,遞歸的思想就是,先將當前的表頭節點從鏈表中拆出來,而後對剩餘的節點進行逆序,最後將當前的表頭節點鏈接到新鏈表的尾部。第一次遞歸調用ReverseLink2(head->next)函數時的狀態如圖(5)所示:

圖(5)第一次遞歸狀態圖

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

   84     newHead = ReverseLink2(head->next); /*遞歸部分*/

   85     head->next->next = head; /*回朔部分*/

   86     head->next = NULL;

如今順着這個思路再進行一次遞歸,就獲得第二次遞歸的狀態圖:

圖(6)第二次遞歸狀態圖

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

圖(7)第三次遞歸狀態圖

 遞歸終止條件就是鏈表只剩一個節點時直接返回這個節點的指針。能夠看出這個算法的核心實際上是在回朔部分,遞歸的目的是遍歷到鏈表的尾節點,而後經過逐級回朔將節點的next指針翻轉過來。遞歸算法的完整代碼以下:

   77 LINK_NODE *ReverseLink2(LINK_NODE *head)

   78 {

   79     LINK_NODE *newHead;

   80 

   81     if((head == NULL) || (head->next == NULL))

   82         return head;

   83 

   84     newHead = ReverseLink2(head->next); /*遞歸部分*/

   85     head->next->next = head; /*回朔部分*/

   86     head->next = NULL;

   87 

   88     return newHead;

   89 }

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