STL List::sort() 解析

关于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。
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 个数据

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,虽然研究多时,无奈本人算法功底不济,本文权当抛砖引玉,望各路高手指点。
代码:
- template <class _Tp, class _Alloc>
- template <class _StrictWeakOrdering>
- void list<_Tp, _Alloc>::sort(_StrictWeakOrdering
__comp) - {
- // Do nothing if the
list has length 0 or 1. - if (_M_node->_M_next != _M_node && _M_node->_M_next->_M_next != _M_node) {
- // 保存下层merge返回的结果
- list<_Tp, _Alloc> __carry;
- // 模拟merge sort使用的堆栈,保存部分有序的list
- // 64应该写作sizeof(size_type) * 8,即最大的递归调用层次。
- list<_Tp, _Alloc> __counter[64];
- // 指示堆栈层次
- int __fill = 0;
- while (!empty()) {
- // 将begin处的元素从list取下,insert到carry中
- __carry.splice(__carry.begin(), *this, begin());
- int __i = 0;
- // 模拟递归时对下层的返回结果进行merge,并将结果交给carry
- while(__i < __fill && !__counter[__i].empty()) {
- __counter[__i].merge(__carry, __comp);
- __carry.swap(__counter[__i++]);
- }
- // carry将结果保存到堆栈
- __carry.swap(__counter[__i]);
- // 更新递归层次
- if (__i == __fill) ++__fill;
- }
- // 逐级获取结果
- for (int __i = 1; __i < __fill; ++__i)
- __counter[__i].merge(__counter[__i-1], __comp);
- // 保存最终结果
- swap(__counter[__fill-1]);
- }
- }
再免费赠送一个正常merge sort的C实现:)
- void merge(int *vector, size_t
size) - {
- // return if there's
0 or 1 element. - if (size <= 1) {
- return ;
- }
- int half = size / 2;
- merge(vector, half);
- merge(vector + half, size - half);
- merge_aux(vector, size);
- return ;
- }
- static void merge_aux(int *vector, size_t
size) - {
- int i, j, k;
- int half = size / 2;
- int clone[half];
- for (i = 0; i < half; ++i) {
- clone[i] = vector[i];
- }
- for (i = 0, j = half, k = 0; (i < half) && (j < size); ++k) {
- if (clone[i] < vector[j]) {
- swap(vector[k], clone[i]);
- ++i;
- }
- else {
- swap(vector[k], vector[j]);
- ++j;
- }
- }
- while (i < half) {
- vector[k++] = clone[i++];
- }
- return ;
- }
以下以对数据(5, 4, 3, 2, 1)排序为例,比较二者的主要差别。
1)递归形式:(圆圈内为本层排序好的数据)
因为递归形式的merge sort在每一层都对数据进行分解,所以递归树是一棵完全二叉树。
2)非递归形式:

图2
因为非递归形式的算法是自底向上来merge数据,原始数据都处在同一层上,所以部分数据缺少中间的层次,最后仍然需要对这些结果再做一次合并(图2虚线引出的那些单元,就是由list::sort中最后那个for循环所处理)。
另外,由list本身来提供sort算法并不符合STL的设计原则,但若要将这样一个严重依赖底层实现的算法抽象出来,又需要做哪些改动呢?欢迎大家讨论。
STL List::sort() 解析的更多相关文章
- 使用STL库sort函数对vector进行排序
使用STL库sort函数对vector进行排序,vector的内容为对象的指针,而不是对象. 代码如下 #include <stdio.h> #include <vector> ...
- STL 源码分析《1》---- list 归并排序的 迭代版本, 神奇的 STL list sort
最近在看 侯捷的 STL源码分析,发现了以下的这个list 排序算法,乍眼看去,实在难以看出它是归并排序. 平常大家写归并排序,通常写的是 递归版本..为了效率的考虑,STL库 给出了如下的 归并排序 ...
- STL vector+sort排序和multiset/multimap排序比较
由 www.169it.com 搜集整理 在C++的STL库中,要实现排序可以通过将所有元素保存到vector中,然后通过sort算法来排序,也可以通过multimap实现在插入元素的时候进行排序.在 ...
- 转:详细解说 STL 排序(Sort)
详细解说 STL 排序(Sort) 详细解说 STL 排序(Sort) 作者Winter 详细解说 STL 排序(Sort) 0 前言: STL,为什么你必须掌握 1 STL提供的Sort 算法 1. ...
- STL源代码分析——STL算法sort排序算法
前言 因为在前文的<STL算法剖析>中,源代码剖析许多,不方便学习,也不方便以后复习.这里把这些算法进行归类,对他们单独的源代码剖析进行解说.本文介绍的STL算法中的sort排序算法,SG ...
- 详细解说 STL 排序(Sort)(转)
作者Winter 详细解说 STL 排序(Sort) 0 前言: STL,为什么你必须掌握 1 STL提供的Sort 算法 1.1 所有sort算法介绍 1.2 sort 中的比较函数 1.3 sor ...
- 详细解说 STL 排序(Sort)
0 前言: STL,为什么你必须掌握 对于程序员来说,数据结构是必修的一门课.从查找到排序,从链表到二叉树,几乎所有的算法和原理都需要理解,理解不了也要死记硬背下来.幸运的是这些理论都已经比较成熟,算 ...
- STL使用sort注意的问题
结构体使用sort算法时,重载operator<(..).如果我们按下面这样写 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ...
- STL之sort函数的用法
说明:本文仅供学习交流,转载请标明出处,欢迎转载! STL封装了一个排序算法,该算法相应的头文件为#include<algorithm>,我们能够依据须要对一个数组进行排序或者降序. so ...
随机推荐
- 机器学习之路: python 实践 word2vec 词向量技术
git: https://github.com/linyi0604/MachineLearning 词向量技术 Word2Vec 每个连续词汇片段都会对后面有一定制约 称为上下文context 找到句 ...
- Web2.0应用程序的7条原则
个人看好Web的发展潜力,本文字摘自<Collective Intelligence 实战> 网络是平台 使用传统许可模式软件的公司或用户必须运行软件.定期更新至最新版本,以及扩展它来满足 ...
- Hibernate之Hibernate环境搭建
Hibernate之Hibernate环境搭建 一.Hibernate环境搭建的步骤 1.添加Hibernate && SQLServer 的Jar antlr-2.7.7.jar d ...
- BZOJ 2333: [SCOI2011]棘手的操作 可并堆 左偏树 set
https://www.lydsy.com/JudgeOnline/problem.php?id=2333 需要两个结构分别维护每个连通块的最大值和所有连通块最大值中的最大值,可以用两个可并堆实现,也 ...
- 洛谷.4383.[八省联考2018]林克卡特树lct(树形DP 带权二分)
题目链接 \(Description\) 给定一棵边带权的树.求删掉K条边.再连上K条权为0的边后,新树的最大直径. \(n,K\leq3\times10^5\). \(Solution\) 题目可以 ...
- 【贪心】【后缀自动机】Gym - 101466E - Text Editor
题意:给你两个串A,B,以及一个整数K,让你找到B的一个尽可能长的前缀,使得其在A串中出现的次数不小于K次. 对A串建立后缀自动机,然后把B串放在上面跑,由于每到一个结点,该结点endpos集合的大小 ...
- loj6300 「CodePlus 2018 3 月赛」博弈论与概率统计
link 题意: A和B玩游戏,每轮A赢的概率为p.现在有T组询问,已知A赢了n轮输了m轮,没有平局,赢一局A得分+1,输一局得分-1,问A得分期望值? $n+m,T\leq 2.5\times 10 ...
- bzoj 3997 Dilworth定理
看到这道题感觉像是网络流,如果没有权值,可以用DAG最小路径覆盖,有权值,感觉可以求一个上下界最小可行流,但内存卡了....时间估计也悬. 正解要用到一些数学知识,这里梳理一下: 定义: 偏序关系: ...
- sgu 261
学习了元根的一些知识,哈哈. 总结一下: 几个概念: 阶:对于模数m和整数a,并且gcd(m,a)==1,那么定义a在模m下的阶r为满足ar=1 mod m的最小正整数. 性质1:r in [1,ph ...
- SPOJ 10232. Distinct Primes
Arithmancy is Draco Malfoy's favorite subject, but what spoils it for him is that Hermione Granger i ...