STL中關於list容器的sort函式詳解
寫在前面
因為在stl中stl_algo中提供有sort函式,他的函式原型:
template <class RandomAccessIterator> inline void sort(RandomAccessIterator first, RandomAccessIterator last); template <class RandomAccessIterator, class Compare> inline void sort(RandomAccessIterator first, RandomAccessIterator last, Compare comp);
我們看到關於迭代器都是 RandomAccessIterator型別的,就是最高層次的指標,但是我們list中迭代器型別為:
typedef bidirectional_iterator_tag iterator_category;
由於bidirectional_iterator_tag是random_access_iterator_tag的子類,不會傳遞呼叫random_access_iterator_tag版本的排序函式,那麼關於list中的sort函式的實現就放在了stl_list中來進行實現了
struct random_access_iterator_tag : public bidirectional_iterator_tag {};
前提了解
splice函式:在sort函式中使用到了這種過載型別
//將list &中i位置上的節點,插入到this的position位置之前,list &中i位置上的節點消失 void splice(iterator position, list&, iterator i) { //將i所指元素結合與position所指位置之前,position和i可指向同一個list iterator j = i; ++j; if (position == i || position == j) return; //位置發生重合或者要移動的位置已經在哦position前面 transfer(position, i, j); }
swap函式,我們這裡使用一個栗子來看一下swap函式,因為我最後沒有找到swap的原函式在哪裡,函式可以實現將兩個list容器裡的所有內容互換(感覺只交換一下最後尾部的節點就好)
#include <iostream>
#include <list>
using namespace std;
int main()
{
list<int> first(3, 100);
list<int> second(5, 200);
first.swap(second);
list<int>::iterator iter = first.begin();
while (iter != first.end())
cout << *iter++ << " ";
cout << endl;
list<int>::iterator iter_ = second.begin();
while (iter_ != second.end())
cout << *iter_++ << " ";
cout << endl;
system("pause");
}
merge函式:
//將list x的內容全部新增到當前操作的list中,並且按照順序進行插入
//前提是兩個list都是遞增排序的
template <class T, class Alloc>
void list<T, Alloc>::merge(list<T, Alloc>& x) {
iterator first1 = begin();
iterator last1 = end();
iterator first2 = x.begin();
iterator last2 = x.end();
while (first1 != last1 && first2 != last2) //只有兩個都不為空的情況下
if (*first2 < *first1) {
iterator next = first2;
transfer(first1, first2, ++next);
first2 = next;
}
else
++first1;
if (first2 != last2) transfer(last1, first2, last2);
}
這裡要著重說一下,因為在list中進行轉移操作,都是指標的移動,而不知重新建造空間,使用建構函式,所以在merge函式執行完之後,list x中的內容全都會消失,只剩最後的指向自身的節點。
這裡實現的sort函式有點類似於歸併排序,但是很神奇的是,表面上看不出什麼限制,但是他的list陣列counter,每個裡面的元素最多都是2^i個,這裡我覺得也是理解的難點。
sort函式程式
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].empty(),是用來對第i列最多2^i個元素控制的
counter[i].merge(carry); //清空carry,將它合併到counter[i]中
carry.swap(counter[i++]); //因為可能還會向下傳遞,要將內容在此放到carry中,清空counter[i],因為此時carry內容為空
}
carry.swap(counter[i]); //carry不為空的話,那麼就將所有內容放到後面一個counter中
if (i == fill) ++fill; //說明前面的所有counter都進行了更新,那麼之前第fill中存在的元素個數為2^(fill+1),所以上面一句話中開一個新的counter來儲存,這裡來更新迴圈次數
}
for (int i = 1; i < fill; ++i) counter[i].merge(counter[i-1]); //最後合併所有的counter
swap(counter[fill-1]);
}
為什麼counter[i]最多儲存2^i個內容?
我認為這裡關鍵的位置就是!counter[i].empty()以及if(i==fill) ++fill,這裡因為每次carry都是攜帶一個節點來,第一次迴圈不再陳述,假設為第二次迴圈,首先和counter[0]合併,然後counter[0]中內容為2個,執行一次swap,那麼carry中內容為2個,counter[0]為空,但是上次counter[0]的大小為1,那麼第一次迴圈的時候會滿足i==fill條件,fill會變為1,但是counter[1] 為空,所以後面會將carry中的兩個元素放入counter[1]中,依次這樣向上推進,向counter[2]中存放資料的時候一定是counter[0]含有一個元素,counter[1]中含有兩個元素,當carry再次攜帶一個元素來的時候,會將所有元素放入counter[2]中,counter[2]中含有4個元素
因為函式merge執行完後,元素都是有序的,那麼在最後進行合併的時候會非常省時,基本上就是歸併排序的思想。
我們可以來模擬一下這個過程,使用連結串列:8 ,2 ,5, 1, 9, 7
第一次:carry = 8,但是counter[0]為空,那麼直接將carry放入到counter[0]中,i == fill,此時fill++
第二次:carry = 2,首先與counter[0]合併,counter[0] = {2,8},carry = NULL,此時counter[1] = fill,直接將所有元素放入counter[1]中,counter[0] = NULL,counter[1] = {2,8}
第三次:carry = 5,直接將5放入counter[0]中,因為counter[0],為空
第四次:carry = 1,與counter[0]合併,生成序列{1,5},再與counter[1]合併,生成序列{1,2,5,8},將序列放入counter[2]中,前面全部為空
第五次:carry = 9,直接放入到counter[0]中
第六次:carry = 7,與counter[0]合併,且因為counter[1] = NULL,所以將合併的序列放入counter[1]中,形成{7,9}
最後將counter[0]-counter[3]合併到counter[3]中,得到正確的序列
使用陣列簡化的歸併排序
參考部落格
《STL原始碼分析》侯捷著