STL List::sort() 解析

轉載地址:http://jiguiyuan.blog.163.com/blog/static/4336137820122133373886/
看侯捷翻譯那本《STL源碼剖析》中list內置sort的算法,書中註釋說是quick sort,看了半天沒看明白,
然後就把代碼敲出來運行看看,上面的源碼如下:
template <class T, class Alloc>
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]);
}
 
 
對應的我的測試代碼是:
typedef list<int> IList;
 
void print(const IList& list)
{
 
IList::const_iterator ite = list.begin();
for (; ite != list.end(); ++ite)
{
cout << *ite << "  ";
}
cout << endl;
 
}
 
int main()
{
IList s;
 
s.push_back(7);
s.push_back(6);
s.push_back(5);
s.push_back(4);
s.push_back(3);
s.push_back(2);
s.push_back(1);
s.push_back(0);
 
IList carry;
IList counter[64];
int fill = 0;
int num = 0;
while (!s.empty())
{
cout << "取第" << num << "個數據: fill = " << fill << endl;
carry.splice(carry.begin(), s, s.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;
//我自己加的計數
num++;
 
//打印每次完的結果
for (int i = 0; i < fill; ++i)
{
cout << i << "==";
print(counter[i]);
}
}
 
for (int i = 1; i < fill; ++i)
counter[i].merge(counter[i - 1]);
s.swap(counter[fill - 1]);
 
getchar();
return 0;
}
 
運行結果如下:
STL List::sort() 解析 - 寒心 - ★★★寒心★★★的領地
通過結果分析才發現明白這個算法是怎麼回事,他是先歸併前兩個元素,然後歸併後兩個,再將這四個元素歸併,
依次進行得到8個、16個……排好序的元素序列,在歸併過程中counter數組裏每個counter[i]中都存放的是2的
i次方個有序元素.其實這應該是一個歸併排序,而不是註釋所說的快速排序。
下面是別人對時間複雜度的分析:
假設總元素個數N=2^n-1。
 
首先merge函數的複雜度爲O(m),因此最後一步的for循環複雜度爲 求和i:2~n{2^i-1}=O(N)的時間。
 
再看while循環,tmp[0]有1個元素,tmp[1]有2個元素,。。。,tmp[n-1]有2^(n-1)個元素,他們都是通過 merge而後再swap(爲常數時間)到最終層次的,因此總複雜度爲:求和i:1~n{i*2^(i-1)}=O((n- 1)*2^n)=O(N*lgN)。
 
因此總時間複雜度爲N*lgN。
  時間複雜度分析源自http://hi.baidu.com/_%C2%B7_%C8%CB_%BC%D7_/blog/item/c7dfc017398ab11a5baf534e.html
 
轉載地址:http://hi.baidu.com/passerryan/item/7c0385356d48dc9fb90c0324

關於stl_list的sort算法

原文地址:http://www.blog.edu.cn/user1/5010/archives/2006/1166534.shtml

stl中的list被實現爲環狀的雙向鏈表,設置一個“哨”node作爲end( )。list沒有使用標準sort算法,而是實現自身的sort,本質上是mergesort(侯捷解釋的是錯的),但是採用了一個特殊的形式:

普通的mergesort直接將待排序的序列一分爲二,然後各自遞歸調用mergesort,再使用Merge算法用O(n)的時間將已排完序的兩個子序列歸併,從而總時間效率爲n*lg(n)。(mergesort是很好的排序算法,絕對時間很小,n*lg(n)之前的係數也很小,但是在內存中的排序算法中並不常見,我想可能主要還是因爲耗空間太多,也是O(n))

list_sort所使用的mergesort形式上大不一樣:將前兩個元素歸併,再將後兩個元素歸併,歸併這兩個小子序列成爲4個元素的有序子序列;重複這一過程,得到8個元素的有序子序列,16個的,32個的。。。,直到全部處理完。主要調用了swap和merge函數,而這些又依賴於內部實現的transfer函數(其時間代價爲O(1))。該mergesort算法時間代價亦爲n*lg(n),計算起來比較複雜。list_sort中預留了64個temp_list,所以最多可以處理2^64-1個元素的序列,這應該足夠了:)

爲何list不使用普通的mergesort呢?這比較好理解,因爲每次找到中間元素再一分爲二的代價實在太大了,不適合list這種非RandomAccess的容器。

爲何list不使用標準sort算法呢(標準sort要求RandomAccessIterator)?至少普通的quicksort我覺得應該是可以的,具體原因等查查標準算法實現再來說了。

下面把gcc4.02中list_sort的實現貼上:

template<typename _Tp, typename _Alloc>
    void
    list<_Tp, _Alloc>::
    sort()
    {
      // Do nothing if the list has length 0 or 1.
      if (this->_M_impl._M_node._M_next != &this->_M_impl._M_node
   && this->_M_impl._M_node._M_next->_M_next != &this->_M_impl._M_node)
      {
        list __carry;
        list __tmp[64];
        list * __fill = &__tmp[0];
        list * __counter;

        do
   {
     __carry.splice(__carry.begin(), *this, begin());

     for(__counter = &__tmp[0];
   __counter != __fill && !__counter->empty();
   ++__counter)
       {
   __counter->merge(__carry);
   __carry.swap(*__counter);
       }
     __carry.swap(*__counter);
     if (__counter == __fill)
       ++__fill;
   }
while ( !empty() );

        for (__counter = &__tmp[1]; __counter != __fill; ++__counter)
          __counter->merge(*(__counter - 1));
        swap( *(__fill - 1) );
      }
    }

對它的複雜度分析只能簡單說說了,實際工作在草稿紙上完成:

假設總元素個數N=2^n-1。

首先merge函數的複雜度爲O(m),因此最後一步的for循環複雜度爲 求和i:2~n{2^i-1}=O(N)的時間。

再看do_while循環,tmp[0]有1個元素,tmp[1]有2個元素,。。。,tmp[n-1]有2^(n-1)個元素,他們都是通過merge而後再swap(爲常數時間)到最終層次的,因此總複雜度爲:求和i:1~n{i*2^(i-1)}=O((n-1)*2^n)=O(N*lgN)。

因此總時間複雜度爲N*lgN。

轉載地址:http://hi.baidu.com/oxcjfhxwtsacjnr/item/76346b32ae4368c3392ffad3

SGI STL list::sort()—快速排序(非遞歸實現方式)

頭頭不在,看了點自己喜歡的東東 :) 
遇下面的源碼,簡單掃了下,暈頭轉向~

// list 不能使用STL 算法 sort(),必須使用自己的 sort() member function,
// 因爲STL算法sort() 只接受RamdonAccessIterator.
// 本函式採用 quick sort.
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()); //取出list中的一個數據,存入carry
    int i = 0;
    while(i < fill && !counter[i].empty()) {
      counter[i].merge(carry);                   //將carry中的數據,和 counter[i]鏈中原有數據合併
      carry.swap(counter[i++]);                  //交換carry 和counter[i] 數據
    }
    carry.swap(counter[i]);        
    if (i == fill) ++fill;
}

for (int i = 1; i < fill; ++i)                   // sort之後,善後處理,把數據統一起來
     counter[i].merge(counter[i-1]);
swap(counter[fill-1]);
}



你看出來了嘛? 

詳細道來:
fill ------> 2^fill 表示現在能處理的數據的最大數目
counter[ fill ]---> 將處理完的2^fill個數據存入 counter[ fill ]中
carry---> 就像一個臨時中轉站, 在處理的數據不足 2 ^ fil l時,在counter[ i ] ( 0=<i<fill)之間移動數據(改變指針)

步驟如下: 
1) 讀入一個數據(carry.splice),通過 carry 將數據存入 counter[0] 中;
     隨後處理下一個數據, carry 保存數據
            a. counter[0].merge(carry) , 此時 counter[0] 容納的數據個數 > 2^0 
            b. 將counter[0]鏈中的數據,通過carry,轉移到counter[1]鏈,.... 直至處理的數據個數達到 2 ^ fill 

2) fill++ , 重複 1) 至處理完所有的數據。


非遞歸的快速排序實現方式,很巧妙!!!
counter 數組爲64 --- 所以此算法,一次最多能處理 2 ^ 64 -2 個數據 
轉載地址:http://www.cnblogs.com/qlee/archive/2011/05/10/2042176.html

list::sort() 源碼解釋

Posted on    2011-05-10 15:18    李大嘴 閱讀(   852) 評論(   0  編輯    收藏
複製代碼
 
1 template < class T, class Alloc > 2 3   void list < T, Alloc > ::sort() { 4 5 if (node -> next == node || link_type(node -> next) -> next == node) return ; 6 7 list < T, Alloc > carry; 8 9 list < T, Alloc > counter[ 64 ]; 10 11 int fill = 0 ; 12 13 while ( ! empty()) { 14 15 carry.splice(carry.begin(), * this , begin()); 16 17 int i = 0 ; 18 19 while (i < fill && ! counter[i].empty()) { 20 21 counter[i].merge(carry); 22 23 carry.swap(counter[i ++ ]); 24 25 } 26 27 carry.swap(counter[i]); 28 29 if (i == fill) ++ fill; 30 31 } 32 33 for ( int i = 1 ; i < fill; ++ i) counter[i].merge(counter[i - 1 ]); 34 35 swap(counter[fill - 1 ]); 36 37 }
複製代碼
 

這是sgi stl的 list.sort源碼

侯捷的STL源碼剖析 有對很多源碼進行了解釋,但這個函數切只是翻譯了下原註釋幾乎沒有對核心進行解釋,而且貌似他的解釋還是錯誤的。他說此函數採用quick sort。但沒有發現快速排序的特徵,沒有找基準的位置,沒有劃分爲左右,也不是內部排序..等。

第6章的算法的泛化過程find例子 貌似也錯了。find函數返回比較不應該用數組的end()來比較,而應當用find函數的第2個參數做比較!

 

/* Written     By     MaiK */

    STL中的list被實現爲環狀的雙向鏈表,設置一個“哨兵”node作爲end( )。鑑於list的內存分配模型,list不能使用通用的標準sort算法,而是實現自身的sort,但是list有自己的成員函數sort()可供其自身調用,其實際模型是基於合併排序的。普通的mergesort直接將待排序的序列一分爲二,然後各自遞歸調用mergesort,再使用Merge算法用O(n)的時間將已排完序的兩個子序列歸併,從而總時間效率爲n*lg(n)。(mergesort是很好的排序算法,絕對時間很小,n*lg(n)之前的係數也很小,但是在內存中的排序算法中並不常見,我想可能主要還是因爲耗空間太多,也是O(n)).

    不過list_sort所使用的mergesort形式上大不一樣:將前兩個元素歸併,再將後兩個元素歸併,歸併這兩個小子序列成爲4個元素的有序子序列;重複這一過程,得到8個元素的有序子序列,16個的,32個的。。。,直到全部處理完。主要調用了swap和merge函數,而這些又依賴於內部實現的transfer函數(其時間代價爲O(1))。該mergesort算法時間代價亦爲n*lg(n),計算起來比較複雜。list_sort中預留了 64個temp_list,所以最多可以處理2^64-1個元素的序列,這應該足夠了。

/* Written     By    Lamar */

    類似2進制,每一次進位都是相鄰高位數值的一半,所以是類2進制地。例如8,低位4滿之後會進4個到8的。

轉載地址:http://blog.chinaunix.net/uid-10647744-id-3083049.html 
STL的list容器提供了專有的sort算法,是一個以非遞歸形式的merge sort,雖然研究多時,無奈本人算法功底不濟,本文權當拋磚引玉,望各路高手指點。

代碼:
  1. template <class _Tp, class _Alloc>
  2. template <class _StrictWeakOrdering>
  3. void list<_Tp, _Alloc>::sort(_StrictWeakOrdering __comp)
  4. {
  5.     // Do nothing if the list has length 0 or 1.
  6.     if (_M_node->_M_next != _M_node && _M_node->_M_next->_M_next != _M_node) {
  7.         // 保存下層merge返回的結果
  8.         list<_Tp, _Alloc> __carry;
  9.         // 模擬merge sort使用的堆棧,保存部分有序的list
  10.         // 64應該寫作sizeof(size_type) * 8,即最大的遞歸調用層次。
  11.         list<_Tp, _Alloc> __counter[64];
  12.         // 指示堆棧層次
  13.         int __fill = 0;
  14.         while (!empty()) {
  15.             // 將begin處的元素從list取下,insert到carry中
  16.             __carry.splice(__carry.begin(), *this, begin());
  17.             int __i = 0;
  18.             // 模擬遞歸時對下層的返回結果進行merge,並將結果交給carry
  19.             while(__i < __fill && !__counter[__i].empty()) {
  20.                 __counter[__i].merge(__carry, __comp);
  21.                 __carry.swap(__counter[__i++]);
  22.             }
  23.             // carry將結果保存到堆棧
  24.             __carry.swap(__counter[__i]);
  25.             // 更新遞歸層次
  26.             if (__i == __fill) ++__fill;
  27.         }
  28.  
  29.         // 逐級獲取結果
  30.         for (int __i = 1; __i < __fill; ++__i)
  31.             __counter[__i].merge(__counter[__i-1], __comp);
  32.         // 保存最終結果
  33.         swap(__counter[__fill-1]);
  34.     }
  35. }
再免費贈送一個正常merge sort的C實現:)
  1. void merge(int *vector, size_t size)
  2. {
  3.     // return if there's 0 or 1 element.
  4.     if (size <= 1) {
  5.         return ;
  6.     }
  7.  
  8.     int half = size / 2;
  9.  
  10.     merge(vector, half);
  11.     merge(vector + half, size - half);
  12.  
  13.     merge_aux(vector, size);
  14.  
  15.     return ;
  16. }
  17.  
  18. static void merge_aux(int *vector, size_t size)
  19. {
  20.     int i, j, k;
  21.  
  22.     int half = size / 2;
  23.  
  24.     int clone[half];
  25.  
  26.     for (= 0; i < half; ++i) {
  27.         clone[i] = vector[i];
  28.     }
  29.  
  30.     for (= 0, j = half, k = 0; (< half) && (< size); ++k) {
  31.         if (clone[i] < vector[j]) {
  32.             swap(vector[k], clone[i]);
  33.             ++i;
  34.         }
  35.         else {
  36.             swap(vector[k], vector[j]);
  37.             ++j;
  38.         }
  39.     }
  40.  
  41.     while (< half) {
  42.         vector[k++] = clone[i++];
  43.     }
  44.  
  45.     return ;
  46. }
以下以對數據(5, 4, 3, 2, 1)排序爲例,比較二者的主要差別。

1)遞歸形式:(圓圈內爲本層排序好的數據)
圖1

因爲遞歸形式的merge sort在每一層都對數據進行分解,所以遞歸樹是一棵完全二叉樹。

2)非遞歸形式:

圖2

因爲非遞歸形式的算法是自底向上來merge數據,原始數據都處在同一層上,所以部分數據缺少中間的層次,最後仍然需要對這些結果再做一次合併(圖2虛線引出的那些單元,就是由list::sort中最後那個for循環所處理)。

另外,由list本身來提供sort算法並不符合STL的設計原則,但若要將這樣一個嚴重依賴底層實現的算法抽象出來,又需要做哪些改動呢?歡迎大家討論。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章