用遞歸和非遞歸兩種方法翻轉一個鏈表

用遞歸和非遞歸兩種方法翻轉一個鏈表

先定義一下鏈表:

  1. typedef struct node  
  2. {  
  3. ElemType data;  
  4. struct node * next;  
  5. }ListNode;  
  6. typedef struct  
  7. {  
  8. ListNode *head;  
  9. int size;  
  10. ListNode *tail;  
  11. }List;  
  12. /********************************************************* 
  13. 非遞歸的翻轉實際上就是使用循環,依次後移指針, 
  14. 並將遇到的鏈表指針反轉 
  15. *********************************************************/  
  16. void ReserveList(List * plist)        //非遞歸實現,  
  17. {  
  18. ListNode * phead;   //新鏈表的頭 開始的第一個節點  
  19. ListNode * pt;   //舊鏈表的頭 開始的第二個節點  
  20. ListNode * pn;   //舊鏈表頭的下一個  
  21. phead = plist->head;  
  22. if(phead && phead->next&& phead->next->next)    //首先確定  
  23. {  
  24. phead = plist->head->next;    //新鏈表就是以第一個節點開始,依次在表頭添加節點,添加的節點是舊鏈表的第一個節點  
  25. pt = phead->next;     //舊鏈表,舊鏈表被取走頭結點之後放入新鏈表的表頭,  
  26. pn = pt->next;  
  27. phead->next = 0;  
  28. while(pt)  
  29. {  
  30. pn = pt->next;    //pn是舊鏈表的第二個節點  
  31. pt ->next = phead;   //取舊鏈表的第一個節點插入新鏈表  
  32. phead = pt;  
  33. pt = pn;     //舊鏈表往後移動  
  34. }  
  35. }  
  36. plist->head->next = phead;     //新鏈表重新賦值到整個鏈表  
  37. }  
  38. /********************************************************* 
  39. 遞歸思想,原理也是從就鏈表上依次取元素放入到新鏈表 
  40. 直到原始鏈表被取完,得到新鏈表 
  41. *********************************************************/  
  42. ListNode * ReserveListRe(ListNode * oldlist,ListNode * newlist)  
  43. {  
  44. ListNode * pt;  
  45. pt = oldlist->next;   //取舊鏈表的表頭,pt是現在的舊鏈表  
  46. oldlist->next = newlist; //就舊鏈表插入到新鏈表  
  47. newlist = oldlist;   //如果舊鏈表是空,表示舊鏈表被取完了,新鏈表就是翻轉之後的鏈表  
  48. return (pt == NULL) ? newlist : ReserveListRe(pt,newlist);  
  49. }  

------------------------------------------------------------------------------------------------------------------------------------------------------

 

 

單向鏈表的反轉是一個經常

被問到的一個面試題,也是一個非常基礎的問題。比如一個鏈表是這樣的: 1->2->3->4->5 通過反轉後成爲5->4->3->2->1。最容易想到的方法遍歷一遍鏈表,利用一個輔助指針,存儲遍歷過程中當前指針指向的下一個元素,然後將當前節點元素的指針反轉後,利用已經存儲的指針往後面繼續遍歷。源代碼如下:

struct linka {
     int data;
     linka* next;
};

void reverse(linka*& head)
{
     if(head ==NULL)
          return;
     linka*pre, *cur, *ne;
     pre=head;
     cur=head->next;
     while(cur)
     {
          ne = cur->next;
          cur->next = pre;
          pre = cur;
          cur = ne;
     }
     head->next = NULL;
     head = pre;
}

 

解釋:對於頭結點後的下一個結點cur如果不爲空,則得到它的下一個結點保存在ne結點中,然後把cur的下一個結點賦爲cur的前一個結點pre,這樣就實現了cur和它的前一個結點的交換,然後把cur結點賦給pre結點,把ne結點賦給cur,這就實現了cur結點和pre結點都向後移了一個結點,然後循環下去,一直到cur結點爲空時,說明已到鏈表尾,這時把頭結點的後一個結點指向空,說明頭結點已經成了尾結點了,這個時候把pre結點已經是原鏈表的最尾端了,如果要實現翻轉,則把它賦給頭結點。

 

如上圖所示,它是先翻轉head和p1,使p1指向head,然後pre=cur;cur=ne;到第二次,繼續翻轉p1,p2,使p2指向p1,然後持續下去,一直到第四次,這時cur爲空,而pre已到了原鏈表的最後一個結點,這時如果head的下一個結點爲空,則說明head爲鏈尾了,而pre則變成了鏈頭,然後把它賦給頭結點即可。

 

 

 

還有一種利用遞歸的方法。這種方法的基本思想是在反轉當前節點之前先調用遞歸函數反轉後續節點。源代碼如下。不過這個方法有一個缺點,就是在反轉後的最後一個結點會形成一個環,所以必須將函數的返回的節點的next域置爲NULL。因爲要改變head指針,所以我用了引用。算法的源代碼如下:

linka* reverse(linka* p,linka*& head)
{
     if(p == NULL || p->next == NULL)
     {
          head=p;
          return p;
     }
     else
     {
          linka* tmp = reverse(p->next,head);
          tmp->next = p;
          return p;
     }
}


LinkNode *reverse_link_recursive(LinkNode *head){ if(head == NULL) return NULL; LinkNode *curr , *reverse_head , *temp; if(head->next == NULL)    // 鏈表中只有一個節點,逆轉後的頭指針不變
        return head; else { curr = head; temp = head->next;    // temp爲(a2,...an)的頭指針
        reverse_head = reverse_link_recursive(temp);   // 逆轉鏈表(a2,...an),並返回逆轉後的頭指針
        temp->next = curr;    // 將a1鏈接在a2之後
        curr->next = NULL; } return reverse_head;      // (a2,...an)逆轉鏈表的頭指針即爲(a1,a2,...an)逆轉鏈表的頭指針
}

發佈了434 篇原創文章 · 獲贊 93 · 訪問量 154萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章