- List 是STL 中的链表容器,今天咱们将经过阅读和实现list源码来解决一下问题:
- List内部的内存结构是如何实现的?
- 为何List的插入复杂度为O(1)?
- 为何List的size()函数复杂度为O(n)?
做为一个链表首先要维护数据(模板元素的实例内容),指向前一个节点的指针 和 指向后一个节点的指针。List 的 结点做为list 容器中 元素内容 和 容器组织逻辑的一个中间层。node
- 为何不能搞成单向链表? 若是单向链表的话,请您试想一下要删除迭代器pos指向的元素应该怎么操做?咱们必须知道前驱后继才能正确进行删除
for(auto it = begin(); it != end(); it++) { if(it->next == pos) { //...逻辑代码 } }
这样的单向链表每次寻找一个节点的前驱 后继 都要通过最坏复杂度为O(n)的查询,全部应该实现为双向链表。c++
- 维护一个指向list_node 的指针
template<class T> struct list_iterator : public bidirectional_iterator<T> {}//继承bidirectional_iterator类 便于类型萃取 和 特定算法应用
- 环状链表 >node 表示尾部的一个空白结点,其next 指向list的头节点, pre指向list的尾节点
为何这样设计? 首先list做为双向链表须要提供向前和向后的迭代能力,这样设计能够在O1的时间得到首尾元素,并且能够避免链表为空时的边界检查(只须要看一下node->pre == node,则为空)算法
node_ptr get_node() { return data_allocator.allocate(1); } node_ptr new_node(const T& x) { node_ptr p = get_node(); construct(&p->data, x); p->next = p->pre = nullptr; return p; }
list() { //注意 typedef std::allocator<list_node<T>> Data_allocator; node.ptr = data_allocator.allocate(1); node.ptr->next = node.ptr->pre = node.ptr; } list(const self& rhs) :list(rhs.begin(), rhs.end()) { } list(std::initializer_list<T> li):list(li.begin(),li.end()) { } template<class c> list(const c l,const c r) { node.ptr = data_allocator.allocate(1); node.ptr->next = node.ptr->pre = node.ptr; for (auto it = l; it != r; it++) { push_back((*it));//逐个插入到list的末端 } } list(size_type n, const T& val) { node.ptr = data_allocator.allocate(1);//初始化node node.ptr->next = node.ptr->pre = node.ptr; while (n--) push_back(val); }
遍历列表 销毁数据实例函数
~list() { if (!empty()) { for (iterator it = begin(); it != end(); ) { data_allocator.destroy(&it.ptr->data); it++; } } }
- 注意指针操做的前后顺序
void push_front(const T& x) { node_ptr p = new_node(x); node.ptr->next->pre = p; p->next = node.ptr->next; node.ptr->next = p; p->pre = node.ptr; } void push_back(const T& x) { node_ptr p = new_node(x); node.ptr->pre->next = p; p->pre = node.ptr->pre; p->next = node.ptr; node.ptr->pre = p; } void pop_front() { node_ptr tmp = node.ptr->next; node.ptr->next = node.ptr->next->next; node.ptr->next->pre = node.ptr; data_allocator.deallocate(tmp,sizeof(list_node)); } void pop_back() { node_ptr tmp = node.ptr->pre; node.ptr->pre = tmp->pre; tmp->pre->next = node.ptr; data_allocator.deallocate(tmp, sizeof(list_node)); } iterator erase(iterator pos) { pos.ptr->pre->next = pos.ptr->next; pos.ptr->next->pre = pos.ptr->pre;// 前驱 后继 结点的指针操做 node_ptr tmp = pos.ptr->next; destroy(&pos.ptr->data);//销毁 data_allocator.deallocate(pos.ptr,sizeof(list_node));// 回收内存 return iterator(tmp); } iterator erase(iterator first, iterator last) { first.ptr->pre->next = last.ptr; last.ptr->pre = first.ptr->pre; for (auto it = first; it != last; it++) destroy(&it.ptr->data); return first; } //The list container is extended by inserting new elements before the element at position. iterator insert(iterator pos, const T& x) { node_ptr p = new_node(x); pos.ptr->pre->next = p; p->pre = pos.ptr->pre; p->next = pos.ptr; pos.ptr->pre = p; return pos; } void insert(iterator pos, size_type sz, const T& x) { while (sz--) insert(pos, x); }
/* 将 first到last的元素移动到 pos 以前 */ void transfer(iterator pos, iterator first, iterator last) { if (pos != last) { last.ptr->pre->next = pos.ptr; first.ptr->pre->next = last.ptr; pos.ptr->pre->next = first.ptr; auto tmp = pos.ptr->pre; pos.ptr->pre = last.ptr->pre; last.ptr->pre = first.ptr->pre; first.ptr->pre = tmp; } } void splice(iterator pos, list<T>& rhs) { if (*this != rhs) transfer(pos, rhs.begin(), rhs.end()); } void splice(iterator pos, list<T>& rhs, iterator first, iterator last) { if (*this != rhs) { transfer(pos, first, last); } } void splice(iterator pos, list<T>& rhs, iterator it) { it.ptr->pre->next = it.ptr->next; it.ptr->next->pre = it.ptr->pre; pos.ptr->pre->next = it.ptr; it.ptr->pre = pos.ptr->pre; pos.ptr->pre = it.ptr; it.ptr->next = pos.ptr; }
注意链表的merge函数 和算法库中含义有所不一样,在这里会将参数链表所有合并进来(也就是说在调用完这个函数以后,other 参数应该是空)this
void merge(list<T> &other) { auto p1 = begin(), p2 = other.begin(); while (p1 != end() && p2 != other.end()) { if (*p1 < *p2) p1++; else if (*p1 >= *p2) { auto tmp = p2;// 注意保存迭代器下一个位置,若是不保存,直接再调用splice以后p2++会怎样?首先p2!=other.end()这个条件永远不会触发,由于p2已经到了this的链表中 tmp++; splice(p1, other, p2); p2 = tmp; } } if (!other.empty()) { splice(end(), other); } }
非递归形式的 归并排序,由于List并不支持随机存取迭代器,sort不能用于排序list。 归并的思路以下:先创建64个桶,而后从前向后遍历,每次从要排序的list头取出一个元素插入桶中,第一个桶要存的元素最多为1个,第二个桶要存的最多为2个,第K个桶最多为2^K个元素。在插入元素的时候从前向后插入,若是到达当前桶的上限就向后归并。 具体的算法实现极为巧妙! 我的感受参考了位运算的进位性质spa
void sort() { if (size() <= 1) { return; } self carry; self 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]); }