今天,来看看STL里面的sort函数,这个排序函数真是让人眼前一亮啊,有趣的很~·
很奇怪的是里面居然硬编码了一个counter[64]??? 一个很naive的怀疑就是,这玩意的长度是硬编码进去的不会越界嘛????
其实现为:
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]);
}
其中,先看看排序过程中涉及到的函数的实现:
- splice函数:
void splice(iterator position, list&, iterator first, iterator last) {
if (first != last)
transfer(position, first, last);
}
而transfer(position,first,last)函数则是把list双向循环链表里面由迭代器fist,last确定的一个左闭右开区间[first,last)的元素都挂在position迭代器的前面。
例如一个list: [5,4,2,7,10],position指向4,first 指向2,last指向10。transfer调用后得到:[5,2,7,4,10]。
splice就是对transfer一个简单的封装。
- merge
template <class T, class Alloc> template <class StrictWeakOrdering>
void list<T, Alloc>::merge(list<T, Alloc>& x, StrictWeakOrdering comp) {
iterator first1 = begin();
iterator last1 = end();
iterator first2 = x.begin();
iterator last2 = x.end();
while (first1 != last1 && first2 != last2)
if (comp(*first2, *first1)) {
iterator next = first2;
transfer(first1, first2, ++next);
first2 = next;
}
else
++first1;
if (first2 != last2) transfer(last1, first2, last2);
}
merge函数负责把当前的list和参数指定的list进行合并,这两个list都得是排好序的。merge的结果是把参数 x 链表的元素有序的合并到当前的list上。
- swap函数
void swap(list<T, Alloc>& x) { __STD::swap(node, x.node); }
swap函数负责,把当前链表的头节点和目标链表的头节点进行互换。
- STL list的结构
在STL中list就是一个双向的循环链表,每个list有一个表头节点node ,这个节点的数据域不发挥左右,只用于跟踪当前的链表。
它的结构类似于下图:反正就是个双向的循环链表。
OK.相关函数就是上面这些,我们分析这个sort函数。
侯捷的书上说它是个快排,然而实际上这个怎么看也不是快排。它的行为其实有点像归并的。
我们先初始化一个list: [5,3,7,8,2,6,9,5],在内部是这么一个双向链表结构:
OK,我们给源代码各行标个序,便于分析。我们把上面那个链表代入进入调用sort看会里面是怎么一回事。
template <class T, class Alloc>
void list<T, Alloc>::sort() {
1.if (node->next == node || link_type(node->next)->next == node) return;
2.list<T, Alloc> carry;
3.list<T, Alloc> counter[64];
4.int fill = 0;
5.while (!empty()) {
6. carry.splice(carry.begin(), *this, begin());
7. int i = 0;
8. while(i < fill && !counter[i].empty()) {
9. counter[i].merge(carry);
10. carry.swap(counter[i++]);
11. }
12. carry.swap(counter[i]);
13. if (i == fill) ++fill;
}
14.for (int i = 1; i < fill; ++i) counter[i].merge(counter[i-1]);
15.swap(counter[fill-1]);
}
首先第1行,检查链表是不是空或者只有一个有效的元素。如果空或只有一个元素的话,那就不用排序了,自然而然就是有序的。
第2,3行定义两个局部变量carry,counter。他们都是同类型的list变量。而且counter还是个硬编码长度的List数组。
第5-13行是个大循环。循环的进入条件是当前的list不为空。我们的list包含8个元素,可以进入循环。
第6、7行,把list的第一个元素给carry.此时:
变量 | 内容 | - |
---|---|---|
carry | 5 | |
*this | [3,7,8,2,6,9,5] | |
counter[0] | [] | |
fill | 0 | |
i | 0 |
第8行,i为0而且counter[0]也为空,于是不进去内层循环。
第12行,carry和counter[0]交换表头节点。结果为:
变量 | 内容 | - |
---|---|---|
carry | [] | |
*this | [3,7,8,2,6,9,5] | |
counter[0] | [5] | |
fill | 0 | |
i | 0 |
第13行,i等于fill,于是fill自增,表示counter数组里面已经有一个元素被占坑了。
变量 | 内容 | - |
---|---|---|
carry | [] | |
*this | [3,7,8,2,6,9,5] | |
counter[0] | [5] | |
fill | 1 | |
i | 0 |
又回到第5行,看当前链表是否为空,显然目前*this
还有7个元素,当然不为空,进入循环。
第6行,carry得到*this的首元素。
第7行,i为0.此时:
变量 | 内容 | - |
---|---|---|
carry | [3] | |
*this | [7,8,2,6,9,5] | |
counter[0] | [5] | |
fill | 1 | |
i | 0 |
第8行,此时i<fill而且counter[i]是有元素的,于是进入内层循环。
第9行,把carry包含的元素合并到counter[i]里面。
变量 | 内容 | - |
---|---|---|
carry | [0] | |
*this | [7,8,2,6,9,5] | |
counter[i] | [3,5] | |
fill | 1 | |
i | 0 |
第10行,再把新合并的counter[i]给carry,同时让i自增。
变量 | 内容 | - |
---|---|---|
carry | [3,5] | |
*this | [7,8,2,6,9,5] | |
counter[0] | [] | |
counter[1] | [] | |
fill | 1 | |
i | 1 |
在回第8行,发现此时内循环条件不成立,跳出内循环。
第12行,把carry给counter[i]。即:
变量 | 内容 | - |
---|---|---|
carry | [] | |
*this | [7,8,2,6,9,5] | |
counter[0] | [] | |
counter[1] | [3,5] | |
fill | 1 | |
i | 1 |
第13行,更新fill,表示counter数组里面已经填过2个两个元素了。
变量 | 内容 | - |
---|---|---|
carry | [] | |
*this | [7,8,2,6,9,5] | |
counter[0] | [] | |
counter[1] | [3,5] | |
fill | 2 | |
i | 1 |
又回到第5行,看当前链表是否为空,显然目前*this
还有6个元素,当然不为空,进入循环。
第6行,carry得到*this的首元素。
第7行,i为0.此时:
变量 | 内容 | - |
---|---|---|
carry | [7] | |
*this | [8,2,6,9,5] | |
counter[0] | [] | |
counter[1] | [3,5] | |
fill | 2 | |
i | 0 |
第8行,此时i<fill,但是counter[i]是没有元素的,于是不进入内层循环。
第12行,把carry给counter[i]。即:
变量 | 内容 | - |
---|---|---|
carry | [] | |
*this | [8,2,6,9,5] | |
counter[0] | [7] | |
counter[1] | [3,5] | |
fill | 2 | |
i | 0 |
template <class T, class Alloc>
void list<T, Alloc>::sort() {
1.if (node->next == node || link_type(node->next)->next == node) return;
2.list<T, Alloc> carry;
3.list<T, Alloc> counter[64];
4.int fill = 0;
5.while (!empty()) {
6. carry.splice(carry.begin(), *this, begin());
7. int i = 0;
8. while(i < fill && !counter[i].empty()) {
9. counter[i].merge(carry);
10. carry.swap(counter[i++]);
11. }
12. carry.swap(counter[i]);
13. if (i == fill) ++fill;
}
14.for (int i = 1; i < fill; ++i) counter[i].merge(counter[i-1]);
15.swap(counter[fill-1]);
}
又回到第5行,此时当前链表还有5个元素,当然进入循环。
第6,7行,把当前链表第一个元素给carry。
变量 | 内容 | - |
---|---|---|
carry | [8] | |
*this | [2,6,9,5] | |
counter[0] | [7] | |
counter[1] | [3,5] | |
fill | 2 | |
i | 0 |
第8行,i<fill,而且counter[i]有一个元素,进入内循环。
第9行,把carry合并到counter[i]
变量 | 内容 | - |
---|---|---|
carry | [] | |
*this | [2,6,9,5] | |
counter[0] | [7,8] | |
counter[1] | [3,5] | |
fill | 2 | |
i | 0 |
第10行,再把counter[i]给carry,同时让i自增。
变量 | 内容 | - |
---|---|---|
carry | [7,8] | |
*this | [2,6,9,5] | |
counter[0] | [] | |
counter[1] | [3,5] | |
fill | 2 | |
i | 1 |
回到第8行,发现此时i<fill,而且counter[i]有两个元素,于是进入再次进入循环。
第9行,把carry和counter[i]合并,得到:
变量 | 内容 | - |
---|---|---|
carry | [] | |
*this | [2,6,9,5] | |
counter[0] | [] | |
counter[1] | [3,5,7,8] | |
fill | 2 | |
i | 1 |
第10行,再把counter[i]给carry,同时让i自增。
变量 | 内容 | - |
---|---|---|
carry | [3,5,7,8] | |
*this | [2,6,9,5] | |
counter[0] | [] | |
counter[1] | [] | |
fill | 2 | |
i | 2 |
回到第8行,不满足条件。
第12行,把carry给counter[2].
变量 | 内容 | - |
---|---|---|
carry | [] | |
*this | [2,6,9,5] | |
counter[0] | [] | |
counter[1] | [] | |
counter[2] | [3,5,7,8] | |
fill | 2 | |
i | 2 |
第13行,把fill增大:
变量 | 内容 | - |
---|---|---|
carry | [] | |
*this | [2,6,9,5] | |
counter[0] | [] | |
counter[1] | [] | |
counter[2] | [3,5,7,8] | |
fill | 3 | |
i | 2 |
OK,分析到这里,后面就是一样的套路了。
把首元素给carry:
变量 | 内容 | - |
---|---|---|
carry | [2] | |
*this | [6,9,5] | |
counter[0] | [] | |
counter[1] | [] | |
counter[2] | [3,5,7,8] | |
fill | 3 | |
i | 0 |
把carry给counter[0]
变量 | 内容 | - |
---|---|---|
carry | [] | |
*this | [6,9,5] | |
counter[0] | [2] | |
counter[1] | [] | |
counter[2] | [3,5,7,8] | |
fill | 3 | |
i | 0 |
把首元素给carry:
变量 | 内容 | - |
---|---|---|
carry | [6] | |
*this | [9,5] | |
counter[0] | [2] | |
counter[1] | [] | |
counter[2] | [3,5,7,8] | |
fill | 3 | |
i | 0 |
合并carry和counter[0],把合并结果挂到counter[1]去
变量 | 内容 | - |
---|---|---|
carry | [] | |
*this | [9,5] | |
counter[0] | [] | |
counter[1] | [2,6] | |
counter[2] | [3,5,7,8] | |
fill | 3 | |
i | 0 |
把首元素给carry,
变量 | 内容 | - |
---|---|---|
carry | [9] | |
*this | [5] | |
counter[0] | [] | |
counter[1] | [2,6] | |
counter[2] | [3,5,7,8] | |
fill | 3 | |
i | 0 |
把carry挂到counter[0]
变量 | 内容 | - |
---|---|---|
carry | [] | |
*this | [5] | |
counter[0] | [9] | |
counter[1] | [2,6] | |
counter[2] | [3,5,7,8] | |
fill | 3 | |
i | 0 |
把首元素给carry
变量 | 内容 | - |
---|---|---|
carry | [5] | |
*this | [] | |
counter[0] | [9] | |
counter[1] | [2,6] | |
counter[2] | [3,5,7,8] | |
fill | 3 | |
i | 0 |
把carry和counter[0]合并,
变量 | 内容 | - |
---|---|---|
carry | [5,9] | |
*this | [] | |
counter[0] | [] | |
counter[1] | [2,6] | |
counter[2] | [3,5,7,8] | |
fill | 3 | |
i | 1 |
把carry和counter[1]合并
变量 | 内容 | - |
---|---|---|
carry | [2,5,6,9] | |
*this | [] | |
counter[0] | [] | |
counter[1] | [] | |
counter[2] | [3,5,7,8] | |
fill | 3 | |
i | 2 |
把carry和counter[2]合并
变量 | 内容 | - |
---|---|---|
carry | [2,3,5,5,6,7,8,9] | |
*this | [] | |
counter[0] | [] | |
counter[1] | [] | |
counter[2] | [] | |
fill | 3 | |
i | 3 |
把carry给counter[3],同时自增fill
变量 | 内容 | - |
---|---|---|
carry | [] | |
*this | [] | |
counter[0] | [] | |
counter[1] | [] | |
counter[2] | [] | |
counter[3] | [2,3,5,5,6,7,8,9] | |
fill | 4 | |
i | 3 |
最后,第5行的条件不再满足。不进入循环。
template <class T, class Alloc>
void list<T, Alloc>::sort() {
1.if (node->next == node || link_type(node->next)->next == node) return;
2.list<T, Alloc> carry;
3.list<T, Alloc> counter[64];
4.int fill = 0;
5.while (!empty()) {
6. carry.splice(carry.begin(), *this, begin());
7. int i = 0;
8. while(i < fill && !counter[i].empty()) {
9. counter[i].merge(carry);
10. carry.swap(counter[i++]);
11. }
12. carry.swap(counter[i]);
13. if (i == fill) ++fill;
}
14.for (int i = 1; i < fill; ++i) counter[i].merge(counter[i-1]);
15.swap(counter[fill-1]);
}
14行,把相邻的两个counter[i]合并。
最后,把counter[fiil-1]给*this,
于是得到:
变量 | 内容 | - |
---|---|---|
carry | [] | |
*this | [2,3,5,5,6,7,8,9] | |
counter[0] | [] | |
counter[1] | [] | |
counter[2] | [] | |
counter[3] | [] | |
fill | 4 |
总结
根据上面的分析,我们可以知道,sort函数设计思路是:counter[i] 维护当前最近遇到的元素里面
个元素有序。当counter[i]需要接收超过
个元素的时候,它就向上合并。遇到新的元素,先把它丢到counter[0],如果发现counter[0]有一个元素,那么就把整个新的元素和原来的counter[0]里面的元素共两个元素合并,插入到counter[1],如果发现counter[1]也满了,就把counter[1]也一起拿出来合并,向上呈递。
···
通过这种方式,层层合并的方式,最后可以实现排序。
均摊下来的时间复杂度为:
,猜测。
另外值得一提的是,STL里面把counter数组长度硬编码为64,通过上面的分析是是绰绰有余的!!
counter[64]可以接收
个元素的排序,远远足够了·····