將一個鏈表翻轉,如 1->2->3->4 變成 4->3->2->1 的鏈表。這是一個非常著名的面試題,看似非常的簡單,但實際上非常的tricky.
實現方法可以有遞歸和迭代兩種方法,這兩個算法也都保證了in-place 和 one-pass. 所以效率還是很高的。這篇文章主要基於http://leetcode.com/2010/04/reversing-linked-list-iteratively-and.html. 他講解的基本已經比較清楚,我就再從比較菜的角度去分析一下。
迭代方法:
首先來看迭代版本的基本思路,先上圖:
上圖就是迭代的第一步,基本就是prev, curr, next三個指針跟蹤着當前點的情況,然后更新后向前移動,直到移到鏈表末尾
看代碼后應該更清晰:
1 void reverse(Node*& head) { 2 if (!head) return; 3 Node* prev = NULL; 4 Node* curr = head; 5 while (curr) { 6 Node* next = curr->next; 7 curr->next = prev; 8 prev = curr; 9 curr = next; 10 } 11 head = prev; 12 }
代碼中要注意 Node*&, 畢竟是改變了鏈表的結構,所以鏈表的頭指針應該傳一個引用。
翻轉鏈表的迭代版本很清晰,且容易實現。而下面的遞歸方法雖然代碼很短,但是比較難理解,且實現起來細節也很多。
遞歸版本:
這個整體過程比較復雜,所以還是先上圖:
然后來看着代碼分析:
void reverse(Node*& p) { if (!p) return; Node* rest = p->next; if (!rest) return; reverse(rest); p->next->next = p; p->next = NULL; p = rest; }
遞歸過程中,首先迅速由遞歸函數進行至第一幅圖的情況(其實下面還有一步,此時rest 指向NULL,返回到第一幅的情況)
然后將當前節點的下一節點的子節點倒指向其父節點(即當前節點),然后將當前節點指向NULL,即當前我們翻轉了從當前節點到末尾節點。
注意這之后的最后一個語句,將p 賦值為 rest,然后返回。 返回到上一層會發生什么呢? 這就要注意每層調用reverse函數時,傳進去的指針式rest, 即修改上一層的rest指針為當前層的rest指針,換句話說,在以后遞歸的過程中,rest指針將一直指向最后一個節點,即新的鏈表根節點。