STL源码系列--List::sort算法解析

转载 2015年07月08日 22:50:31

看到《STL源码剖析》中list内置sort的算法,注释说是快速排序,在网上找了快速排序的非递归实现,发现完全不同,后面网上查找,发现了下面两种比较合理的解释,记录如下。

一.解析一

转载自:

http://blog.csdn.net/yihucha166/article/details/6544039

stl中的list 是双向链表结构,最近用到其中的sort方法,文档中有这么两段:

Sorts *this according to operator<. The sort is stable, that is, the relative order of equivalent elements is preserved. All iterators remain valid and continue to point to the same elements. [6] The number of comparisons is approximately N log N, where N is the list's size.

-》[6] The sort algorithm works only for random access iterators. In principle, however, it would be possible to write a sort algorithm that also accepted bidirectional iterators. Even if there were such a version of sort, it would still be useful for list to have a sort member function. That is, sort is provided as a member function not only for the sake of efficiency, but also because of the property that it preserves the values that list iterators point to.

可以看出 stl中的sort算法一般只支持 随机访问的结构,对于list来说,一般使用它自己的sort成员函数,并且时间复杂度在nlogn。

由于对链表结构的排序比较感兴趣,回头在网上搜索了一下list内部的sort算法分析,由源码分析可以看出list内部的sort函数,实际上是通过链表方式实现的非递归的mergesort,时间复杂度在nlogn,并且空间复杂度为O(1),下面贴两篇分析的文章:

---------------------------------------

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) );
      }
    }

--------------------------------------------------------

list::sort() 这个函数使用了非常巧妙的算法,来对list中的元素进行排序,在某些书上看到,指出该算法是快速排序,个人认为应该是归并排序更为确切。。所以提供了以下测试代码以观察整个排序过程。

#include <iostream> 
#include <list> 
#include <iomanip> 
using namespace std;

//the max value is 64 
const int SCALE=10;

template <typename T>  
void print_dbg(list<T>& iList, list<T>& carry, list<T>* counter) 

    typename list<T>::iterator it2; 
     
    cout<<"list: ";  
    it2=iList.begin(); 
    while(it2!=iList.end()) 
    {  
        cout<<setw(3)<<*it2++; 
    }  
    cout<<endl;  
     
    cout<<"carry: "; 
    it2=carry.begin();  
    while(it2!=carry.end()) 
    {  
        cout<<setw(3)<<*it2++; 
    }  
    cout<<endl;  
     
    for(int i=0; i<SCALE; ++i) 
    { 
        if(!counter[i].empty()) 
        { 
            cout<<"counter["<<i<<"]: "; 
            it2=counter[i].begin(); 
            while(it2!=counter[i].end()) 
            { 
                cout<<setw(3)<<*it2++; 
            } 
            cout<<endl; 
        } 
    } 
    cout<<endl<<endl; 

template <typename T>  
void LSort(list<T> &iList)  
{  
    if(iList.size()<= 1) 
    { 
        return; 
    } 
      
    list<T> carry;  
    list<T> counter[64];  
    int fill = 0;  

    typename list<T>::iterator it;  
     
    while (!iList.empty()) 
    { 
        carry.splice(carry.begin(), iList, iList.begin());   

        print_dbg(iList, carry, counter); 
      
        int i = 0;  
        while(i < fill && !counter[i].empty())  
        {  
            counter[i].merge(carry);  
            carry.swap(counter[i++]);  
        } 
         
        carry.swap(counter[i]); 
        print_dbg(iList, carry, counter);

        if (i == fill)  
        { 
            ++fill; 
        }  
        cout<<endl<<endl<<endl; 
    } 
       
    for (int i = 1; i < fill; ++i) 
    { 
        counter[i].merge(counter[i-1]);  
    } 
     
    print_dbg(iList, carry, counter); 
     
    iList.swap(counter[fill-1]); 


int main()  
{  
    int arr[]={22,43,1,32,43,3,65,8,56,98,4,23,87,94,37,77,35,36,0};  
    list<int> iList(arr,arr+sizeof(arr)/sizeof(int)); 

    list<int>::iterator it;

    LSort(iList);  
     
    cout<<"Complete! After sort: "<<endl; 
    it=iList.begin();  
    while(it!=iList.end()) 
    {  
        cout<<setw(3)<<*it++;  
    } 
    cout<<endl<<endl; 

    system("pause"); 
    return 0;  

通过函数print_dbg()打印所有中间链表的内容,在以上数组的输入时,可以得到以下输出:

list:  43  1 32 43  3 65  8 56 98  4 23 87 94 37 77 35 36 
carry:  22

list:  43  1 32 43  3 65  8 56 98  4 23 87 94 37 77 35 36 
carry: 
counter[0]:  22

 

list:   1 32 43  3 65  8 56 98  4 23 87 94 37 77 35 36  0 
carry:  43 
counter[0]:  22

list:   1 32 43  3 65  8 56 98  4 23 87 94 37 77 35 36  0 
carry: 
counter[1]:  22 43

 

list:  32 43  3 65  8 56 98  4 23 87 94 37 77 35 36  0 
carry:   1 
counter[1]:  22 43

list:  32 43  3 65  8 56 98  4 23 87 94 37 77 35 36  0 
carry: 
counter[0]:   1 
counter[1]:  22 43

 

list:  43  3 65  8 56 98  4 23 87 94 37 77 35 36  0 
carry:  32 
counter[0]:   1 
counter[1]:  22 43

list:  43  3 65  8 56 98  4 23 87 94 37 77 35 36  0 
carry: 
counter[2]:   1 22 32 43

 

list:   3 65  8 56 98  4 23 87 94 37 77 35 36  0 
carry:  43 
counter[2]:   1 22 32 43

list:   3 65  8 56 98  4 23 87 94 37 77 35 36  0 
carry: 
counter[0]:  43 
counter[2]:   1 22 32 43

 

list:  65  8 56 98  4 23 87 94 37 77 35 36  0 
carry:   3 
counter[0]:  43 
counter[2]:   1 22 32 43

list:  65  8 56 98  4 23 87 94 37 77 35 36  0 
carry: 
counter[1]:   3 43 
counter[2]:   1 22 32 43

 

list:   8 56 98  4 23 87 94 37 77 35 36  0 
carry:  65 
counter[1]:   3 43 
counter[2]:   1 22 32 43

list:   8 56 98  4 23 87 94 37 77 35 36  0 
carry: 
counter[0]:  65 
counter[1]:   3 43 
counter[2]:   1 22 32 43

 

list:  56 98  4 23 87 94 37 77 35 36  0 
carry:   8 
counter[0]:  65 
counter[1]:   3 43 
counter[2]:   1 22 32 43

list:  56 98  4 23 87 94 37 77 35 36  0 
carry: 
counter[3]:   1  3  8 22 32 43 43 65

 

list:  98  4 23 87 94 37 77 35 36  0 
carry:  56 
counter[3]:   1  3  8 22 32 43 43 65

list:  98  4 23 87 94 37 77 35 36  0 
carry: 
counter[0]:  56 
counter[3]:   1  3  8 22 32 43 43 65

 

list:   4 23 87 94 37 77 35 36  0 
carry:  98 
counter[0]:  56 
counter[3]:   1  3  8 22 32 43 43 6

list:   4 23 87 94 37 77 35 36  0 
carry: 
counter[1]:  56 98 
counter[3]:   1  3  8 22 32 43 43 65

 

list:  23 87 94 37 77 35 36  0 
carry:   4 
counter[1]:  56 98 
counter[3]:   1  3  8 22 32 43 43 65

list:  23 87 94 37 77 35 36  0 
carry: 
counter[0]:   4 
counter[1]:  56 98 
counter[3]:   1  3  8 22 32 43 43 65

 

list:  87 94 37 77 35 36  0 
carry:  23 
counter[0]:   4 
counter[1]:  56 98 
counter[3]:   1  3  8 22 32 43 43 65

list:  87 94 37 77 35 36  0 
carry: 
counter[2]:   4 23 56 98 
counter[3]:   1  3  8 22 32 43 43 65

 

list:  94 37 77 35 36  0 
carry:  87 
counter[2]:   4 23 56 98 
counter[3]:   1  3  8 22 32 43 43 65

list:  94 37 77 35 36  0 
carry: 
counter[0]:  87 
counter[2]:   4 23 56 98 
counter[3]:   1  3  8 22 32 43 43 65

 

list:  37 77 35 36  0 
carry:  94 
counter[0]:  87 
counter[2]:   4 23 56 98 
counter[3]:   1  3  8 22 32 43 43 65

list:  37 77 35 36  0 
carry: 
counter[1]:  87 94 
counter[2]:   4 23 56 98 
counter[3]:   1  3  8 22 32 43 43 65

 

list:  77 35 36  0 
carry:  37 
counter[1]:  87 94 
counter[2]:   4 23 56 98 
counter[3]:   1  3  8 22 32 43 43 65

list:  77 35 36  0 
carry: 
counter[0]:  37 
counter[1]:  87 94 
counter[2]:   4 23 56 98 
counter[3]:   1  3  8 22 32 43 43 65

 

list:  35 36  0 
carry:  77 
counter[0]:  37 
counter[1]:  87 94 
counter[2]:   4 23 56 98 
counter[3]:   1  3  8 22 32 43 43 65

list:  35 36  0 
carry: 
counter[4]:   1  3  4  8 22 23 32 37 43 43 56 65 77 87 94

 

list:  36  0 
carry:  35 
counter[4]:   1  3  4  8 22 23 32 37 43 43 56 65 77 87 94

list:  36  0 
carry: 
counter[0]:  35 
counter[4]:   1  3  4  8 22 23 32 37 43 43 56 65 77 87 94

 

list:   0 
carry:  36 
counter[0]:  35 
counter[4]:   1  3  4  8 22 23 32 37 43 43 56 65 77 87 94

list:   0 
carry: 
counter[1]:  35 36 
counter[4]:   1  3  4  8 22 23 32 37 43 43 56 65 77 87 94

 

list: 
carry:   0 
counter[1]:  35 36 
counter[4]:   1  3  4  8 22 23 32 37 43 43 56 65 77 87 94

list: 
carry: 
counter[0]:   0 
counter[1]:  35 36 
counter[4]:   1  3  4  8 22 23 32 37 43 43 56 65 77 87 94

 

list: 
carry: 
counter[4]:   0  1  3  4  8 22 23 32 35 36 37 43 43 56 65


Complete! After sort: 
  0  1  3  4  8 22 23 32 35 36 37 43 43 56 65 77 87 94 98
 

所以个人认为应该算是归并排序。

二.解析二

转载自:
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。

相关文章推荐

STL源码剖析——list容器的排序算法sort()

由于STL本身的排序算法sort接受的输入迭代器是随机访问迭代器,但是双向list链表容器的访问方式是双向迭代器,因此,不能使用STL本身的排序算法sort,必须自己定义属于自己访问的排序算法。我们从...

STL源码剖析——list容器的排序算法sort()

前言     由于STL本身的排序算法sort接受的输入迭代器是随机访问迭代器,但是双向list链表容器的访问方式是双向迭代器,因此,不能使用STL本身的排序算法sort,必须自己定义属于自己访问...

STL源码——list sort:归并排序的非递归实现

由于STL中提供的sort算法是用在RandomAccessIterator上的,而list迭代器不具备随机访问的特性,所以对list进行排序不能使用algorithm中的sort算法,而应该使用li...

STL中的list::sort算法解析

References 《STL源码剖析》http://www.ecjtu.org/thread-30140-1-1.html 《算法导论》http://blog.yangzhe1991.o...

【STL源码剖析】Sort算法

前言:sort算法必须拿出来单独将,因为它是STL所有算法中最复杂最庞大的一个,就像我肯定会把copy算法单独列出来一样,这两个算法太重要了。提示:STL算法特点是,前两个形参变量都是迭代器指针。并且...

STL源码解析 - sort_heap

模板函数sort_heap具有如下两个版本template void sort_heap(_RanIt _First, _RanIt _Last);template void sort_heap(_R...

STL源码解析 - sort

模板函数sort有两个重载版本template inline void sort(_RanIt _First, _RanIt _Last); template inline void sort(_...

STL学习——list中的sort算法

STL源码剖析中给出了list的sort算法的源码 ,感觉是合并排序算法,觉得很经典: Template void list::sort() { if(node->next == node ...

STL_list_sort算法复杂度分析

在看jjhou的STL源码那本书的时候,看到list::sort函数的时候费了一点劲。主要是书中注释居然写的是quick sort。导致我一直在琢磨这怎么就是个quick sort了? list::s...

STL中list链表的sort算法详解

STL中list链表的sort算法详解      今天在学习侯捷先生的《STL源码剖析》这本书,在讲到list链表中的sort算法时,书上写的不详细,很难在短时间内搞明白,我也花了好长时间才搞懂这个...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)