C++ 《STL源码剖析》 List学习
1.list的一些基础概念
1.1 list主要是双向链表实现的。这个我们比较熟悉,在早期写C语言实验的时候 我们写过链式存储数据
1.2 list是一个环形双向链表 使用一个指针node指向环形链表的空白节点 end==nod begin=node.next 这样就形成了一个环了
1.3 关于list的默认构造
/ 默认allocator为alloc template <class T, class Alloc = alloc> class list { ... public: list() { empty_initialize(); } protected: // 专属空间配置器,配置单位为一个节点大小 typedef simple_alloc<list_node, Alloc> list_node_allocator; // 建立空链表 void empty_initialize() { node = get_node(); node->next = node; node->prev = node; } // 配置一个节点,不进行构造 link_type get_node() { return list_node_allocator::allocate(); } // 释放一个节点, 不进行析构 void put_node(link_type p) { list_node_allocator::deallocate(p); } // 配置并构造一个节点 link_type create_node(const T& x) { link_type p = get_node(); construct(&p->data, x); return p; } // 析构并释放节点 void destroy_node(link_type p) { destroy(&p->data); put_node(p); } ... }
list配置了一个空白节点 给node 及 1.2提到的
1.4 list的insert是一个重载函数
iterator insert(iterator position, const T& x) { link_type tmp = create_node(x); // 产生一个节点 // 调整双向指针,使tmp插入 tmp->next = position.node; tmp->prev = position.node->prev; (link_type(position.node->prev))->next = tmp; position.node->prev = tmp; return tmp; }
以上是最简单的一种
1.5 list不同于vector list不像vector那样有可能在空间不足时重新配置,数据移动的操作。所以list处插入部分的迭代器外 其他的都不受影响
2.list的操作
void push_front(const T&x){ insert(begin(),x); } iterator erase(iterator position){ link_type next_node=link_type(position.node->next); link_type prev_node=link_type(position.node->prev_nodext); prev_node->next=next_node; next_node->prev=prev_node; destroy_node(position.node); return iterator(next_node); } void pop_front(){ erase(begin()); } void pop_back(){ iterator i=end(); erase(--i); } //将某连续范围的元素迁移到某个特定位置之前。技术上讲很简单,节点直接的指针移动而已。 void transfer(iterator position, iterator first, iterator last) { if (position != last) { (*(link_type((*last.node).prev))).next = position.node; (*(link_type((*first.node).prev))).next = last.node; (*(link_type((*position.node).prev))).next = first.node; link_type tmp = link_type((*position.node).prev); (*position.node).prev = (*last.node).prev; (*last.node).prev = (*first.node).prev; (*first.node).prev = tmp; } }
其中最重要的是transfer函数
虽然transfer函数并不是公开接口 , 但是STL库提供了splice函数
下面会重点介绍三个结构
list.splice(iterator , list;
list.reverse();
list.sort();
//将x结合于position位置之前,x必须不用于*this
void splice(iterator position, list& x) { if (!x.empty()) transfer(position, x.begin(), x.end()); }
//将i所指元素结合于position所指位置之前。position和i可指向用一个list void splice(iterator position, list&, iterator i) { iterator j = i; ++j; if (position == i || position == j) return; transfer(position, i, j); }
//将[first,last)内的所有元素结合于position所指位置之前
//position和[first,last)所指向同一个list
//position不在[first,last)之间 void splice(iterator position, list&, iterator first, iterator last) { if (first != last) transfer(position, first, last); }
//需要注意 *this和x都必须递增排序 将x合并到*this上
void list<T, Alloc>::merge(list<T, Alloc>& x) { iterator first1 = begin(); iterator last1 = end(); iterator first2 = x.begin(); iterator last2 = x.end(); while (first1 != last1 && first2 != last2) if (*first2 < *first1) { iterator next = first2; transfer(first1, first2, ++next); first2 = next; } else ++first1; if (first2 != last2) transfer(last1, first2, last2); } template <class T, class Alloc> void list<T, Alloc>::reverse() {
//使用size()==0||size()==1 慢 if (node->next == node || link_type(node->next)->next == node) return; iterator first = begin(); ++first; while (first != end()) { iterator old = first; ++first; transfer(begin(), old, first); } } 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]); }
关于sort函数实现counter数组很关键
比如我们的list里有如下几个需要排序的元素:21,45,1,30,52,3,58,47,22,59,0,58。
排序的时候怎么做,我们先定义若干中转list在上述代码中定义了64个元素的数组
list<_Tp, _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]的数据转移给__count[i+1]。
具体过程如下:
取出第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
这便是整个sort函数的过程
简化一下
其实就是我们每次合并2的次方个数
我们在处理时 由carry中元素使用merge函数合并到counter数组里 (这里保持了有序)
所以我们之后每个counter数组都是有序的
主要调用了swap和merge函数,而这些又依赖于内部实现的transfer函数(其时间代价为O(1))。该mergesort算法时间代价亦为n*lg(n),计算起来比较复杂。list_sort中预留了 64个temp_list,所以最多可以处理2^64-1个元素的序列,这应该足够了。