STL源码学习——Lists(链表)
今天突然想起来看看开源项目,找了找最后决定好好看看经典的STL喵~
和STL里的代码比起来我突然觉得以前写的代码也太不规范了喵,估计很多ACMer都一样吧喵。
先从简单的看、先挑了一发list的源码来看。总结如下:
欢迎大家一起讨论喵~
1 :list是用双向循环链表实现的,就是说 list.end()+1 == list.begin()
2 :list中有一个关键结点,这个结点是 list.end()
3 :在看了list中的erase函数后,发现这个函数没有对end()结点进行特殊处理,所以很遗憾list在erase(end)后就会出现一些意想不到的情况,比如以下代码会让list容器死循环喵~(虽然这个操作本来就是非法的)
1 int main() 2 { 3 list<int> ls; 4 ls.insert(ls.begin(), 0); 5 ls.insert(ls.begin(), 1); 6 ls.erase(ls.end()); 7 for(list<int>::iterator it=ls.begin(); it!=ls.end(); ++it) 8 printf("it -> %d\n", *it); 9 return 0; 10 }
4 :erase(it)之后会返回应该在原先it位置的元素。同样erase(end)后返回值是不对的。(erase(end)后的返回值为begin喵~)
5 :erase()函数并未检测传入参数是否是本身这个list的。所以如果你有两个list:L1、L2,你这样写也是对的喵~
|
6 :list里居然自己实现了sort算法喵,而且时间复杂度O(n*lg n)、空间复杂度O(1)。以前并没有仔细想list的排序问题,一直很单纯的以为list排序很慢。今天看了一发源码才知道原来链表排序也可以这么优越喵~
这里,《STL源码剖析》中采用的如下解释:解释为快速排序。
大致说一下算法喵:这里采用的是归并排序算法的思想。
先将0、1两元素排序,再将2、3两元素排序。
接下来就是排序前4个元素,
然后,同上前8、16、32 ... 个元素
具体实现用了64个桶,每个依编号存放2^n个元素。然后就像二进制加法一样,每次对个位进行加一操作,通过进位过程可将排好序的桶进行合并了喵。
由于链表的特殊性,无须另开空间存放元素,只需将原来的结点移动位置即可喵~
list::sort()
1 void sort(_StrictWeakOrdering __comp) 2 { 3 // Do nothing if the list has length 0 or 1. 4 if (_M_node._M_data->_M_next != _M_node._M_data && 5 (_M_node._M_data->_M_next)->_M_next != _M_node._M_data) 6 { 7 list<_Tp, _Alloc> __carry; 8 list<_Tp, _Alloc> __counter[64]; 9 int __fill = 0; 10 while (!empty()) 11 { 12 __carry.splice(__carry.begin(), *this, begin()); //个位加一 13 int __i = 0; 14 while(__i < __fill && !__counter[__i].empty()) 15 { 16 __counter[__i].merge(__carry, __comp); 17 __carry.swap(__counter[__i++]); 18 } 19 __carry.swap(__counter[__i]); 20 if (__i == __fill) ++__fill; 21 } 22 23 for (int __i = 1; __i < __fill; ++__i) 24 __counter[__i].merge(__counter[__i-1], __comp); 25 swap(__counter[__fill-1]); 26 } 27 }
7 :关于resize()我以前一直以为list会存放一个size_val,结果它木有存,resize的复杂度是线性的。所以如果size比较大的话,还是少resize()为好喵。
当然现在也可以理解为什么没有存放size_val了喵,因为存在这个时间复杂度为O(1)的insert函数。
|
8 :“operator=” list里这个函数比我想像的要聪明喵。
我以为它会先erase(begin(), end()),然后再一个一个的加进去。
结果它是先将就目标list中已创建的结点先用着,然后采用”多退少补“的原则喵~