今天,來看看STL裏面的sort函數,這個排序函數真是讓人眼前一亮啊,有趣的很~·
很奇怪的是裏面居然硬編碼了一個counter[64]??? 一個很naive的懷疑就是,這玩意的長度是硬編碼進去的不會越界嘛????
其實現爲:
template <class T, class Alloc>
void list<T, Alloc>::sort() {
if (node->next == node || link_type(node->next)->next == node) return;
list<T, Alloc> carry;
list<T, Alloc> counter[64];
int fill = 0;
while (!empty()) {
carry.splice(carry.begin(), *this, begin());
int 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]);
}
其中,先看看排序過程中涉及到的函數的實現:
- splice函數:
void splice(iterator position, list&, iterator first, iterator last) {
if (first != last)
transfer(position, first, last);
}
而transfer(position,first,last)函數則是把list雙向循環鏈表裏面由迭代器fist,last確定的一個左閉右開區間[first,last)的元素都掛在position迭代器的前面。
例如一個list: [5,4,2,7,10],position指向4,first 指向2,last指向10。transfer調用後得到:[5,2,7,4,10]。
splice就是對transfer一個簡單的封裝。
- merge
template <class T, class Alloc> template <class StrictWeakOrdering>
void list<T, Alloc>::merge(list<T, Alloc>& x, StrictWeakOrdering comp) {
iterator first1 = begin();
iterator last1 = end();
iterator first2 = x.begin();
iterator last2 = x.end();
while (first1 != last1 && first2 != last2)
if (comp(*first2, *first1)) {
iterator next = first2;
transfer(first1, first2, ++next);
first2 = next;
}
else
++first1;
if (first2 != last2) transfer(last1, first2, last2);
}
merge函數負責把當前的list和參數指定的list進行合併,這兩個list都得是排好序的。merge的結果是把參數 x 鏈表的元素有序的合併到當前的list上。
- swap函數
void swap(list<T, Alloc>& x) { __STD::swap(node, x.node); }
swap函數負責,把當前鏈表的頭節點和目標鏈表的頭節點進行互換。
- STL list的結構
在STL中list就是一個雙向的循環鏈表,每個list有一個表頭節點node ,這個節點的數據域不發揮左右,只用於跟蹤當前的鏈表。
它的結構類似於下圖:反正就是個雙向的循環鏈表。
OK.相關函數就是上面這些,我們分析這個sort函數。
侯捷的書上說它是個快排,然而實際上這個怎麼看也不是快排。它的行爲其實有點像歸併的。
我們先初始化一個list: [5,3,7,8,2,6,9,5],在內部是這麼一個雙向鏈表結構:
OK,我們給源代碼各行標個序,便於分析。我們把上面那個鏈表代入進入調用sort看會里面是怎麼一回事。
template <class T, class Alloc>
void list<T, Alloc>::sort() {
1.if (node->next == node || link_type(node->next)->next == node) return;
2.list<T, Alloc> carry;
3.list<T, Alloc> counter[64];
4.int fill = 0;
5.while (!empty()) {
6. carry.splice(carry.begin(), *this, begin());
7. int i = 0;
8. while(i < fill && !counter[i].empty()) {
9. counter[i].merge(carry);
10. carry.swap(counter[i++]);
11. }
12. carry.swap(counter[i]);
13. if (i == fill) ++fill;
}
14.for (int i = 1; i < fill; ++i) counter[i].merge(counter[i-1]);
15.swap(counter[fill-1]);
}
首先第1行,檢查鏈表是不是空或者只有一個有效的元素。如果空或只有一個元素的話,那就不用排序了,自然而然就是有序的。
第2,3行定義兩個局部變量carry,counter。他們都是同類型的list變量。而且counter還是個硬編碼長度的List數組。
第5-13行是個大循環。循環的進入條件是當前的list不爲空。我們的list包含8個元素,可以進入循環。
第6、7行,把list的第一個元素給carry.此時:
變量 | 內容 | - |
---|---|---|
carry | 5 | |
*this | [3,7,8,2,6,9,5] | |
counter[0] | [] | |
fill | 0 | |
i | 0 |
第8行,i爲0而且counter[0]也爲空,於是不進去內層循環。
第12行,carry和counter[0]交換表頭節點。結果爲:
變量 | 內容 | - |
---|---|---|
carry | [] | |
*this | [3,7,8,2,6,9,5] | |
counter[0] | [5] | |
fill | 0 | |
i | 0 |
第13行,i等於fill,於是fill自增,表示counter數組裏面已經有一個元素被佔坑了。
變量 | 內容 | - |
---|---|---|
carry | [] | |
*this | [3,7,8,2,6,9,5] | |
counter[0] | [5] | |
fill | 1 | |
i | 0 |
又回到第5行,看當前鏈表是否爲空,顯然目前*this
還有7個元素,當然不爲空,進入循環。
第6行,carry得到*this的首元素。
第7行,i爲0.此時:
變量 | 內容 | - |
---|---|---|
carry | [3] | |
*this | [7,8,2,6,9,5] | |
counter[0] | [5] | |
fill | 1 | |
i | 0 |
第8行,此時i<fill而且counter[i]是有元素的,於是進入內層循環。
第9行,把carry包含的元素合併到counter[i]裏面。
變量 | 內容 | - |
---|---|---|
carry | [0] | |
*this | [7,8,2,6,9,5] | |
counter[i] | [3,5] | |
fill | 1 | |
i | 0 |
第10行,再把新合併的counter[i]給carry,同時讓i自增。
變量 | 內容 | - |
---|---|---|
carry | [3,5] | |
*this | [7,8,2,6,9,5] | |
counter[0] | [] | |
counter[1] | [] | |
fill | 1 | |
i | 1 |
在回第8行,發現此時內循環條件不成立,跳出內循環。
第12行,把carry給counter[i]。即:
變量 | 內容 | - |
---|---|---|
carry | [] | |
*this | [7,8,2,6,9,5] | |
counter[0] | [] | |
counter[1] | [3,5] | |
fill | 1 | |
i | 1 |
第13行,更新fill,表示counter數組裏面已經填過2個兩個元素了。
變量 | 內容 | - |
---|---|---|
carry | [] | |
*this | [7,8,2,6,9,5] | |
counter[0] | [] | |
counter[1] | [3,5] | |
fill | 2 | |
i | 1 |
又回到第5行,看當前鏈表是否爲空,顯然目前*this
還有6個元素,當然不爲空,進入循環。
第6行,carry得到*this的首元素。
第7行,i爲0.此時:
變量 | 內容 | - |
---|---|---|
carry | [7] | |
*this | [8,2,6,9,5] | |
counter[0] | [] | |
counter[1] | [3,5] | |
fill | 2 | |
i | 0 |
第8行,此時i<fill,但是counter[i]是沒有元素的,於是不進入內層循環。
第12行,把carry給counter[i]。即:
變量 | 內容 | - |
---|---|---|
carry | [] | |
*this | [8,2,6,9,5] | |
counter[0] | [7] | |
counter[1] | [3,5] | |
fill | 2 | |
i | 0 |
template <class T, class Alloc>
void list<T, Alloc>::sort() {
1.if (node->next == node || link_type(node->next)->next == node) return;
2.list<T, Alloc> carry;
3.list<T, Alloc> counter[64];
4.int fill = 0;
5.while (!empty()) {
6. carry.splice(carry.begin(), *this, begin());
7. int i = 0;
8. while(i < fill && !counter[i].empty()) {
9. counter[i].merge(carry);
10. carry.swap(counter[i++]);
11. }
12. carry.swap(counter[i]);
13. if (i == fill) ++fill;
}
14.for (int i = 1; i < fill; ++i) counter[i].merge(counter[i-1]);
15.swap(counter[fill-1]);
}
又回到第5行,此時當前鏈表還有5個元素,當然進入循環。
第6,7行,把當前鏈表第一個元素給carry。
變量 | 內容 | - |
---|---|---|
carry | [8] | |
*this | [2,6,9,5] | |
counter[0] | [7] | |
counter[1] | [3,5] | |
fill | 2 | |
i | 0 |
第8行,i<fill,而且counter[i]有一個元素,進入內循環。
第9行,把carry合併到counter[i]
變量 | 內容 | - |
---|---|---|
carry | [] | |
*this | [2,6,9,5] | |
counter[0] | [7,8] | |
counter[1] | [3,5] | |
fill | 2 | |
i | 0 |
第10行,再把counter[i]給carry,同時讓i自增。
變量 | 內容 | - |
---|---|---|
carry | [7,8] | |
*this | [2,6,9,5] | |
counter[0] | [] | |
counter[1] | [3,5] | |
fill | 2 | |
i | 1 |
回到第8行,發現此時i<fill,而且counter[i]有兩個元素,於是進入再次進入循環。
第9行,把carry和counter[i]合併,得到:
變量 | 內容 | - |
---|---|---|
carry | [] | |
*this | [2,6,9,5] | |
counter[0] | [] | |
counter[1] | [3,5,7,8] | |
fill | 2 | |
i | 1 |
第10行,再把counter[i]給carry,同時讓i自增。
變量 | 內容 | - |
---|---|---|
carry | [3,5,7,8] | |
*this | [2,6,9,5] | |
counter[0] | [] | |
counter[1] | [] | |
fill | 2 | |
i | 2 |
回到第8行,不滿足條件。
第12行,把carry給counter[2].
變量 | 內容 | - |
---|---|---|
carry | [] | |
*this | [2,6,9,5] | |
counter[0] | [] | |
counter[1] | [] | |
counter[2] | [3,5,7,8] | |
fill | 2 | |
i | 2 |
第13行,把fill增大:
變量 | 內容 | - |
---|---|---|
carry | [] | |
*this | [2,6,9,5] | |
counter[0] | [] | |
counter[1] | [] | |
counter[2] | [3,5,7,8] | |
fill | 3 | |
i | 2 |
OK,分析到這裏,後面就是一樣的套路了。
把首元素給carry:
變量 | 內容 | - |
---|---|---|
carry | [2] | |
*this | [6,9,5] | |
counter[0] | [] | |
counter[1] | [] | |
counter[2] | [3,5,7,8] | |
fill | 3 | |
i | 0 |
把carry給counter[0]
變量 | 內容 | - |
---|---|---|
carry | [] | |
*this | [6,9,5] | |
counter[0] | [2] | |
counter[1] | [] | |
counter[2] | [3,5,7,8] | |
fill | 3 | |
i | 0 |
把首元素給carry:
變量 | 內容 | - |
---|---|---|
carry | [6] | |
*this | [9,5] | |
counter[0] | [2] | |
counter[1] | [] | |
counter[2] | [3,5,7,8] | |
fill | 3 | |
i | 0 |
合併carry和counter[0],把合併結果掛到counter[1]去
變量 | 內容 | - |
---|---|---|
carry | [] | |
*this | [9,5] | |
counter[0] | [] | |
counter[1] | [2,6] | |
counter[2] | [3,5,7,8] | |
fill | 3 | |
i | 0 |
把首元素給carry,
變量 | 內容 | - |
---|---|---|
carry | [9] | |
*this | [5] | |
counter[0] | [] | |
counter[1] | [2,6] | |
counter[2] | [3,5,7,8] | |
fill | 3 | |
i | 0 |
把carry掛到counter[0]
變量 | 內容 | - |
---|---|---|
carry | [] | |
*this | [5] | |
counter[0] | [9] | |
counter[1] | [2,6] | |
counter[2] | [3,5,7,8] | |
fill | 3 | |
i | 0 |
把首元素給carry
變量 | 內容 | - |
---|---|---|
carry | [5] | |
*this | [] | |
counter[0] | [9] | |
counter[1] | [2,6] | |
counter[2] | [3,5,7,8] | |
fill | 3 | |
i | 0 |
把carry和counter[0]合併,
變量 | 內容 | - |
---|---|---|
carry | [5,9] | |
*this | [] | |
counter[0] | [] | |
counter[1] | [2,6] | |
counter[2] | [3,5,7,8] | |
fill | 3 | |
i | 1 |
把carry和counter[1]合併
變量 | 內容 | - |
---|---|---|
carry | [2,5,6,9] | |
*this | [] | |
counter[0] | [] | |
counter[1] | [] | |
counter[2] | [3,5,7,8] | |
fill | 3 | |
i | 2 |
把carry和counter[2]合併
變量 | 內容 | - |
---|---|---|
carry | [2,3,5,5,6,7,8,9] | |
*this | [] | |
counter[0] | [] | |
counter[1] | [] | |
counter[2] | [] | |
fill | 3 | |
i | 3 |
把carry給counter[3],同時自增fill
變量 | 內容 | - |
---|---|---|
carry | [] | |
*this | [] | |
counter[0] | [] | |
counter[1] | [] | |
counter[2] | [] | |
counter[3] | [2,3,5,5,6,7,8,9] | |
fill | 4 | |
i | 3 |
最後,第5行的條件不再滿足。不進入循環。
template <class T, class Alloc>
void list<T, Alloc>::sort() {
1.if (node->next == node || link_type(node->next)->next == node) return;
2.list<T, Alloc> carry;
3.list<T, Alloc> counter[64];
4.int fill = 0;
5.while (!empty()) {
6. carry.splice(carry.begin(), *this, begin());
7. int i = 0;
8. while(i < fill && !counter[i].empty()) {
9. counter[i].merge(carry);
10. carry.swap(counter[i++]);
11. }
12. carry.swap(counter[i]);
13. if (i == fill) ++fill;
}
14.for (int i = 1; i < fill; ++i) counter[i].merge(counter[i-1]);
15.swap(counter[fill-1]);
}
14行,把相鄰的兩個counter[i]合併。
最後,把counter[fiil-1]給*this,
於是得到:
變量 | 內容 | - |
---|---|---|
carry | [] | |
*this | [2,3,5,5,6,7,8,9] | |
counter[0] | [] | |
counter[1] | [] | |
counter[2] | [] | |
counter[3] | [] | |
fill | 4 |
總結
根據上面的分析,我們可以知道,sort函數設計思路是:counter[i] 維護當前最近遇到的元素裏面 個元素有序。當counter[i]需要接收超過 個元素的時候,它就向上合併。遇到新的元素,先把它丟到counter[0],如果發現counter[0]有一個元素,那麼就把整個新的元素和原來的counter[0]裏面的元素共兩個元素合併,插入到counter[1],如果發現counter[1]也滿了,就把counter[1]也一起拿出來合併,向上呈遞。
···
通過這種方式,層層合併的方式,最後可以實現排序。
均攤下來的時間複雜度爲:,猜測。
另外值得一提的是,STL裏面把counter數組長度硬編碼爲64,通過上面的分析是是綽綽有餘的!!
counter[64]可以接收個元素的排序,遠遠足夠了·····