【數據結構】單鏈表反轉

鏈表反轉一般有兩種解法:遞歸和迭代。首先給出鏈表的定義

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,移動這三個結點即可。

總結

我總以爲自己理解的鏈表的操作,可是隔一段時間就忘了,可能是沒有深入分析。另外這是我的第一篇博客,也是被一位大佬鼓動,加油吧!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章