STL 原始碼分析《1》---- list 歸併排序的 迭代版本, 神奇的 STL list sort
阿新 • • 發佈:2019-01-05
最近在看 侯捷的 STL原始碼分析,發現了以下的這個list 排序演算法,乍眼看去,實在難以看出它是歸併排序。
平常大家寫歸併排序,通常寫的是 遞迴版本。。為了效率的考慮,STL庫 給出瞭如下的 歸併排序的迭代版本.
1. MergeSort 的遞迴版本
考慮如下的例子,對一個長度為 8 的陣列進行歸併排序。
2. 迭代版本
而 迭代版本恰恰相反,是 從下往上。
為了便於明白演算法的思想,我模仿 STL 庫的 list sort 重新寫了歸併排序。文章最後給出了 STL 的原始碼。
// copyright @ L.J.SHOU Feb.19, 2014 // my list-sort mimicing STL's list sort #include <iostream> using namespace std; struct ListNode{ int val; ListNode *next; ListNode(int x) :val(x), next(NULL){} }; // merge two sorted lists into a sorted list ListNode* merge(ListNode* &, ListNode* &); void ListSort(ListNode* & list) { if(list == NULL || list->next == NULL) /* 空或者1個元素的連結串列 */ return; ListNode *carry(NULL); ListNode *counter[64] = {NULL}; /* 64個list, 中介資料中轉站,用於模擬遞迴 */ int fill = 0; while(list) { /* insert first node of list into front of carry */ ListNode *node = list; list = list->next; node->next = carry; carry = node; int i = 0; while(i < fill && counter[i]) { counter[i] = merge(counter[i], carry); carry = NULL; /* after merge, carry is now empty */ swap(carry, counter[i++]); } swap(carry, counter[i]); if(i == fill) ++fill; } for(int i = 1; i < fill; ++i){ /* 將 64 個 lists 歸併成一個 list */ counter[i] = merge(counter[i], counter[i-1]); counter[i-1] = NULL; } swap(list, counter[fill-1]); }
程式碼分析:上述程式碼中 開了一個長度為 64 的連結串列陣列。
第 i 個連結串列長度最長是 2^(i+1)。
演算法的執行過程如下:
對 8 1 4 5 進行排序。
比較 遞迴與迭代的演算法過程,可以發現兩者就是互逆的過程。
現在相比大家對演算法有了一個全面的認識。STL 庫正是 利用 長度為 64個連結串列,實現了上圖中的演算法。
這個演算法能夠排序的總數是 2^65-2 個數,應該夠用了。
SGI STL 的原始碼(選自 STL 原始碼分析)如下:
// list 不能使用 STL 演算法 sort () // 因為 STL 演算法 sort() 只接受 RandomAccessIterator // 本函式採用 mergesort template <class T, class Alloc> void list<T, Alloc>::sort () { // 以下判斷,如果是空連結串列, 或僅有一個元素,就不進行任何操作 if (node->next == node || link_type(node->next)->next == node) return; // 一些新的 lists, 作為中介資料存放區 list<T, Alloc> carry; list<T, Alloc> counter[64]; int fill = 0; while (!empty()) { carry.splice(carry.begin(), *this, begin()); in i = 0; while(i < fill && !counter[i].empty()) { counter[i].merge(carry); carry.swap(counter[i++]); } carry.swap(counter[i]); if (i == fill) ++fill; } for(int i = 1; i < fill; ++i) counter[i].merge(counter[i-1]); swap(counter[fill-1]); }
參考:1. STL原始碼分析, 侯捷