鏈表反轉一般有兩種解法:遞歸和迭代。首先給出鏈表的定義
typedef struct tagLNode {//帶頭結點的單鏈表
int data;
struct tagLNode *next;//尾結點爲0表示結束。若尾結點指向頭結點則爲循環單鏈表
}LinkList;
假設鏈表爲head->1->2->3->4->5->NULL。
遞歸法
LinkList * reverse(LinkList *head) {
if (head == NULL) return 0;
else if (head->next == NULL) return head;
LinkList *temp=reverse(head->next);
//temp->next=head;
head->next->next = head;
head->next = NULL;
return temp;
}
一開始我的想法是,先遞歸到最後一層,也就是reverse(5),這層的運行結果是返回5號結點給上層的temp,那麼我們回到了第四層:head就是4號結點,temp是5號。讓temp的後續變成head不行嗎?在這一層是可以的。那麼我們再回到第三層,head是3號結點,temp是5號結點(請注意,後續爲4號結點),再執行temp->next=head時,5號結點的後續又變爲3號,問題來了,程序好像在打轉。所以問題在於我們返回的temp永遠是5號結點,不能更改temp的後繼。正確做法是head->next->next = head,每層發生的事情與temp無關,它只負責返回5號結點。
這裏想說說遞歸的缺點:首先遞歸存在重複計算(參考斐波那契數列),而且每次遞歸都是一次函數調用,每次調用都需要在內存棧中分配空間來保存參數、返回地址和臨時變量,這就導致了彈棧入棧的時間消耗。遞歸還可能導致調用棧溢出:每個進程棧的容量是有限的,調用層級太多就會超出棧的容量,導致調用棧溢出。
迭代法
LinkList * reverse(LinkList *head) {
if (head == NULL || head->next == NULL) return head;//0個或1個元素時無須反轉
LinkList *prev = head;//前續結點
LinkList *p = head->next;//當前結點
prev->next = NULL;//頭結點後繼應爲空
if (p->next == NULL) {//如果只有2個元素,應特殊處理
p->next = prev;
return p;
}
LinkList *next = p->next;//後繼結點
while (next){
p->next = prev;//當前結點的後繼應爲前一結點
prev = p;
p = next;
next = next->next;//這三結點都往移動
}
p->next = prev;//最後的一個結點手動指向prev
return p;//返回最後一個結點,也是新鏈表的頭結點
}
迭代法的好處就是簡單易懂:由於每次循環會斷開p與原始後繼的鏈子,需要用next指針來保存後繼結點,然後就是循環調用p->next=prev,移動這三個結點即可。
總結
我總以爲自己理解的鏈表的操作,可是隔一段時間就忘了,可能是沒有深入分析。另外這是我的第一篇博客,也是被一位大佬鼓動,加油吧!