STL中list链表的sort算法详解
今天在学习侯捷先生的《STL源码剖析》这本书,在讲到list链表中的sort算法时,书上写的不详细,很难在短时间内搞明白,我也花了好长时间才搞懂这个算法,下面我就来讲一讲这个算法的实现细节。
附代码如下:
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+1]);
}
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]);
}
list不能使用STL算法sort(),必须使用自己的sort() member function.因为STL算法sort()只接受RamdonAccessIterator,在这个函数中采用了快速排序。
详细分析如下:
(1) if(node->next==node || link_type(node->next)->next==node)
这个用来判断是否是空表或者仅有一个元素,如果是其中之一,就不进行任何操作
(2)list<T,Alloc> carry;
list<T,Alloc> counter[64];
这两个是新定义的lists,作为中介数据存放区。
但是list<T,Alloc> counter[64]作为中转是怎么工作的呢?
counter[0]里存放2的(0+1)次方个元素
counter[1]里存放2的(1+1)次方个元素
counter[2]里存放2的(2+1)次方个元素
counter[3]里存放2的(3+1)次方个元素,依次类推
数据中转原则是:当第i个元素即counter[i]的内容个数等于2的(i+1)次方时,就要把counter[i]的数据转移给counter[i+1].
举个例子:假设list里面等待排序的元素为:21,45,1,30,52,3,58,47,22,59,0,58.
具体过程如下:
取出第1个数21,放到__counter[0]里,这时__counter[0]里有一个元素,小于2,继续
counter[0]: 21
counter[1]: NULL
取出第2个数45,放到counter[0]里(不是简单的放,而是排序放,类似两个list做merge),这时counter[0]里有2个元素了,需要把这两个元素转移到counter[1].
counter[0]: NULL
counter[1]: 21,45
取出第3个数1,放到counter[0]里,count[0]与count[1]都小于规定个数
counter[0]: 1
counter[1]: 21,45
取出第4个数30,放到counter[0]里,这时counter[0]的个数等于2了,需要转移到counter[1]里
counter[0]: NULL
counter[1]: 1,21,30,45
但这时counter[1]里的个数又等于4了,所有需要把counter[1]的值转移到counter[2]里,
counter[0]: NULL
counter[1]: NULL
counter[2]: 1,21,30,45
然后取出52,放入counter[0]
counter[0]: 52
counter[1]: NULL
counter[2]: 1,21,30,45
然后取出3,放入counter[0]
counter[0]: 3,52
counter[1]: NULL
counter[2]: 1,21,30,45
这时候需要转移
counter[0]: NULL
counter[1]: 3,52
counter[2]: 1,21,30,45
然后取58
counter[0]: 58
counter[1]: 3,52
counter[2]: 1,21,30,45
然后取47
counter[0]: 47,58
counter[1]: 3,52
counter[2]: 1,21,30,45
需要转移
counter[0]: NULL
counter[1]: 3,47,52,58
counter[2]: 1,21,30,45
还需要转移
counter[0]: NULL
counter[1]: NULL
counter[2]: 1,3,21,30,47,45,52,58
还需要转移
counter[0]: NULL
counter[1]: NULL
counter[2]: NULL
counter[3]: 1,3,21,30,47,45,52,58
然后再取59
counter[0]: 59
counter[1]: NULL
counter[2]: NULL
counter[3]: 1,3,21,30,47,45,52,58
然后取0
counter[0]: 0,59
counter[1]: NULL
counter[2]: NULL
counter[3]: 1,3,21,30,47,45,52,58
需要转移
counter[0]: NULL
counter[1]: 0,59
counter[2]: NULL
counter[3]: 1,3,21,30,47,45,52,58
最后取58
counter[0]: 58
counter[1]: 0,59
counter[2]: NULL
counter[3]: 1,3,21,30,47,45,52,58
其中counter[]数组的数据存放就是这样的。
list<T,Alloc>carry;是作为counter[]中数据排序的交换区域。
(3)carry.splice(carry.begin(),*this,begin())
这个函数就是将begin()所指元素接合与carry.begin()处所指位置之前。也就是将list开始处的元素接合到carry开始位置处。
(4)while循环中的counter[i].merge(carry);carry.swap(counter[i++]);
第一个函数表示将carry中的元素融合到counter[i]中,第二个函数将刚融合进来的数据与先前的counter[i]中的数据进行排序,并在结束时加i加1.
(5)carry.swap(counter[i])也是比较数据
(6)counter[i].merge(counter[i-1]);
这个函数将counter[i-1]中的所有元素结合到counter[i]中。
void list::splice(iterator __position, list&, iterator __i)
splice:把当前列表的__i位置元素删除,保存在__position里。
void list<_Tp, _Alloc>::merge(list<_Tp, _Alloc>& __x)
merge:把参数list的元素合并到当前list,参数list的内容会清空的。
swap函数,交换两个列表的元素,不要担心这个函数有负担,因为对于list来说,交换只是链表头的交换。
大致就这样分析的,应该清楚了吧。