鏈表翻轉【面試經驗】

鏈表的翻轉是程序員面試中出現頻度最高的問題之一,常見的解決方法分爲遞歸和迭代兩種。

  我們知道迭代是從前往後依次處理,直到循環到鏈尾;而遞歸恰恰相反,首先一直迭代到鏈尾也就是遞歸基判斷的準則,然後再逐層返回處理到開頭。

總結來說,鏈表翻轉操作的順序對於迭代來說是從鏈頭往鏈尾,而對於遞歸是從鏈尾往鏈頭。下面我會用詳細的圖文來剖析其中實現的細節。 

1、非遞歸(迭代)方式 

  迭代的方式是從鏈頭開始處理,如下圖給定一個存放5個數的鏈表。

 

  首先對於鏈表設置兩個指針:

 

然後依次將舊鏈表上每一項添加在新鏈表的後面,然後新鏈表的頭指針NewH移向新的鏈表頭,如下圖所示。此處需要注意,不可以上來立即將上圖中P->next直接指向NewH,這樣存放2的地址就會被丟棄,後續鏈表保存的數據也隨之無法訪問。而是應該設置一個臨時指針tmp,先暫時指向P->next指向的地址空間,保存原鏈表後續數據。然後再讓P->next指向NewH,最後P=tmp就可以取回原鏈表的數據了,所有循環訪問也可以繼續展開下去。

 

 

  指針繼續向後移動,直到P指針指向NULL停止迭代。

 

  最後一步:

 

2、非遞歸實現的程序

node* reverseList(node* H)
{
	if (H == NULL || H->next == NULL) //鏈表爲空或者僅1個數直接返回
		return H;
	node* p = H, *newH = NULL;
	while (p != NULL)                 //一直迭代到鏈尾
	{
		node* tmp = p->next;          //暫存p下一個地址,防止變化指針指向後找不到後續的數
		p->next = newH;               //p->next指向前一個空間
		newH = p;                     //新鏈表的頭移動到p,擴長一步鏈表
		p = tmp;                   //p指向原始鏈表p指向的下一個空間
	}
	return newH;
}

3、遞歸方式 

  我們再來看看遞歸實現鏈表翻轉的實現,前面非遞歸方式是從前面數1開始往後依次處理,而遞歸方式則恰恰相反,它先循環找到最後面指向的數5,然後從5開始處理依次翻轉整個鏈表。 

  首先指針H迭代到底如下圖所示,並且設置一個新的指針作爲翻轉後的鏈表的頭。由於整個鏈表翻轉之後的頭就是最後一個數,所以整個過程NewH指針一直指向存放5的地址空間。

  然後H指針逐層返回的時候依次做下圖的處理,將H指向的地址賦值給H->next->next指針,並且一定要記得讓H->next =NULL,也就是斷開現在指針的鏈接,否則新的鏈表形成了環,下一層H->next->next賦值的時候會覆蓋後續的值。

 

  繼續返回操作:

 

  上圖第一次如果沒有將存放4空間的next指針賦值指向NULL,第二次H->next->next=H,就會將存放5的地址空間覆蓋爲3,這樣鏈表一切都大亂了。接着逐層返回下去,直到對存放1的地址空間處理。

 

  返回到頭:

4、迭代實現的程序

node* In_reverseList(node* H)
{
	if (H == NULL || H->next == NULL)       //鏈表爲空直接返回,而H->next爲空是遞歸基
		return H;
	node* newHead = In_reverseList(H->next); //一直循環到鏈尾 
	H->next->next = H;                       //翻轉鏈表的指向
	H->next = NULL;                          //記得賦值NULL,防止鏈表錯亂
	return newHead;                          //新鏈表頭永遠指向的是原鏈表的鏈尾
}

5、整體實現的程序:

#include<iostream>
using namespace std;

struct node{
	int val;
	struct node* next;
	node(int x) :val(x){}
};
/***非遞歸方式***/
node* reverseList(node* H)
{
	if (H == NULL || H->next == NULL) //鏈表爲空或者僅1個數直接返回
		return H;
	node* p = H, *newH = NULL;
	while (p != NULL)                 //一直迭代到鏈尾
	{
		node* tmp = p->next;          //暫存p下一個地址,防止變化指針指向後找不到後續的數
		p->next = newH;               //p->next指向前一個空間
		newH = p;                     //新鏈表的頭移動到p,擴長一步鏈表
		p = tmp;                   //p指向原始鏈表p指向的下一個空間
	}
	return newH;
}
/***遞歸方式***/
node* In_reverseList(node* H)
{
	if (H == NULL || H->next == NULL)       //鏈表爲空直接返回,而H->next爲空是遞歸基
		return H;
	node* newHead = In_reverseList(H->next); //一直循環到鏈尾 
	H->next->next = H;                       //翻轉鏈表的指向
	H->next = NULL;                          //記得賦值NULL,防止鏈表錯亂
	return newHead;                          //新鏈表頭永遠指向的是原鏈表的鏈尾
}
int main()
{
	node* first = new node(1);
	node* second = new node(2);
	node* third = new node(3);
	node* forth = new node(4);
	node* fifth = new node(5);
	first->next = second;
	second->next = third;
	third->next = forth;
	forth->next = fifth;
	fifth->next = NULL;
	//非遞歸實現
	node* H1 = first;
	H1 = reverseList(H1);    //翻轉
	//遞歸實現
	node* H2 = H1;    //請在此設置斷點查看H1變化,否則H2再翻轉,H1已經發生變化
	H2 = In_reverseList(H2); //再翻轉

	return 0;
}

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