1. 程式人生 > >STL List::sort() 解析

STL List::sort() 解析

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。