單向鏈表反轉算法——遞歸版和迭代版


  最近在做筆試題時,遇到一道編程題:單向鏈表反轉算法。一時緊張,沒寫出來就提前交卷了,然而交完卷就想出來了。。。

  最初想出來的是遞歸版,遺憾的是沒能做到尾遞歸,后來又琢磨出了迭代版。后來用實際編譯運行測試了一遍,能正常運行。

  遞歸版的靈感來源於《Haskell 趣學指南》中非常簡潔的快速排序算法的實現,其思想是將單向鏈表分割頭部和尾部。其中頭部指是鏈表的第一個節點,尾部是指除去第一個節點后的子鏈表。通過遞歸的方法,將子鏈表繼續分割成頭部和尾部,直至尾部指剩下一個節點,無法繼續分割,然后將頭部和尾部的位置交換,返回尾部,然后將前一個頭部接到返回的尾部后面,繼續返回新的尾部,依此類推,直到整個遞歸完成。

  迭代版的思想是,用三個指針(first, now, next)來協助調整順序。其中每一次迭代(即:每遍歷一個節點),都將單鏈表動態分割成反轉后的和反轉前的兩部分。每一次迭代后,反轉后的鏈表會變長,反轉前的鏈表會變短,直到反轉前的鏈表長度為0,反轉完成,返回反轉后的鏈表。整個思想和冒泡排序有點像,把序列(鏈表)划分成有序區(反轉后的部分)和無序區(反轉前的部分),但是只需對整個單向鏈表進行一趟遍歷。

  值得注意的是,第一次迭代,是從單向鏈表的第二個節點開始的。而第一個節點默認就是反轉后的節點,而且是反轉后的尾節點,為了避免環路導致的死循環,需要將該節點指向的下一個節點的指針設為 NULL 。

  這三個指針的作用如下:

  • first 指針始終指向反轉后的鏈表的首節點;
  • now 指針指向當前遍歷到的反轉前的鏈表的首節點;
  • next 指針指向 now 節點的下一個節點,避免在 now->next 指向反轉后的鏈表首節點時,丟失了反轉前的鏈表的引用。

 

關鍵數據結構如下:

1 typedef struct _Node {
2     int data;
3     struct _Node * next;
4 } Node;

遞歸版的反轉算法代碼如下:

 1 Node * Node_reverse(Node *node) {
 2     if (node == NULL) return NULL;
 3     if (node->next == NULL) return node;
 4     Node * n = Node_reverse(node->next);
 5     if (n != NULL) {
 6         n->next = node;
 7         node->next = NULL;
 8     }
 9     return node;
10 }

其實,這個遞歸版反轉算法有個小缺陷:那就是整個遞歸完成時,返回的節點就是傳入的參數。也就是說,如果傳入的是反轉前的單向鏈表首節點,那么最終返回的也是這個節點,而這個節點卻是反轉后的單向鏈表的尾節點(因為反轉了,首尾交換)。因此,使用時,需要做些微不足道的工作來彌補缺陷。即:在使用前需要先對單向鏈表進行一趟遍歷,找到尾節點並用指針引用它。。。其實還能改善一下這段代碼來修正這個小缺陷,把這些微不足道的工作交給這個函數來完成,也就是加個 if 分支的事,我懶得寫了。迭代版的就沒這個問題,而且效率略高。

 

迭代版的反轉算法代碼如下:

 1 Node * Node_reverse_v2(Node* node) {
 2     if (node == NULL) return NULL;
 3     if (node->next == NULL) return node;
 4 
 5     Node *first = node; //總是指向新鏈表的首部。
 6     Node *now = node->next;
 7     Node *next = now->next;
 8     first->next = NULL; //首節點變成尾節點,尾節點的下一個節點置空,防止環路。
 9     do {
10         next = now->next;
11         now->next = first;
12         first = now;
13         now = next;
14     } while (next != NULL);
15 
16     return first;
17 }

 

測試代碼如下:

 1 void printNode(Node *node) {
 2     if (node != NULL) {
 3         printf("%d ", node->data);
 4     }
 5 }
 6 
 7 int main()
 8 {
 9     LinkedList *l = List_create();
10     for (int i=0; i<20; i++) {
11         List_append(l, i);
12     }
13     List_foreach(l, printNode);
14     printf("\n");
15     List_foreach(List_reverse(l), printNode);
16     printf("\n");
17     List_foreach(List_reverse_v2(l), printNode);
18     printf("\n");
19     return 0;
20 }

輸出如下:

 

完整代碼下載:

reverse.7z

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM