文章首发于:My Blog 欢迎大佬们前来逛逛
1. list的节点 众所周知list是链表,因此它一定需要一个节点类型 ,以下是SGI STL list的节点类型:
1 2 3 4 5 6 7 8 template <typename T>struct listNode { listNode<T>* next; listNode<T>* prev; T data; };
2. list的迭代器 由于list不像vector一样所有的节点都存储在一块连续的空间中,相反list的节点存储是不连续的 。
因此list的迭代器应该具有正确的递增,递减,取值,成员取用的操作。
递增:正确的找到其next的地址
递减:正确的找到其prev的地址
取值:当前节点的取值
成员取用:当前节点的成员
因此list的迭代器必须具有双向移动 的功能,他必须是一个 Bidirectional Iterator ,即双向迭代器 。
list在插入与删除的时候,原迭代器仍然有效,只有被删除或者执行操作的迭代器才有可能失效;而vector由于需要重新配置空间,因此原迭代器全部无效
实现过程:
定义迭代器基本数据类型:value_type,differece_type,pointer,reference,iterator_category等,同时定义节点类型,并且创建一个根节点 。
list的构造函数,其中注意const iterator&参数的构造函数 ,我们可能需要执行这样的操作:
1 2 list<int > ls; list<int >::iterator it (ls.begin()) ;
其中it的调用的就是list_iterator的此构造函数。
list的迭代器的基本操作:
== 与 != 的操作
* 运算获取的是某个迭代器的data值
-> 运算获取的是某个迭代器的data值的地址 ,这个不怎么常用。
++操作:前置++,直接相加,返回引用 ;后置++,返回的是一个临时的,因此不能是引用
—操作:前置—,直接相减,返回引用 ;后置—,返回的是一个临时的,因此不能是引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 template <typename T,typename Ref,typename Ptr>class list_iterator {public : using iterator = list_iterator<T, T&, T*>; using self = list_iterator<T, Ref, Ptr>; using value_type = T; using difference_type = ptrdiff_t ; using pointer = Ptr; using reference = Ref; using iterator_category = Bibirectional_Iterator_tag; using size_type = size_t ; using link_type = listNode<T>*; private : link_type node; public : list_iterator () {} list_iterator (link_type x) :node (x) {} list_iterator (const iterator& x) :node (x.node) {} ~list_iterator () {} public : bool operator ==(const self& lhs) { return lhs.node == node; } bool operator !=(const self& lhs) { return lhs.node != node; } reference operator *() { return node->data; } pointer operator ->()const { return &(node->data); } self& operator ++() { node = node->next; return *this ; } self operator ++(int ) { auto temp = *this ; ++* this ; return temp; } self& operator --() { node = node->prev; return *this ; } self operator --(int ) { auto temp = *this ; --* this ; return temp; } };
3. list的数据结构 list是一个双向循环链表 ,所以它只需要一个指针 ,便可以遍历整个链表并且回到原来的位置。
为此我们可以设计一个头节点 为list的起始节点,这个头节点不含任何数据,它只是作为一个空的节点而已,方便我们进行遍历与基本判断操作:
当我们进行begin()的时候:直接返回 head->next即可;同理我们的 end()表示的才是 head
调用size() 统计节点的数量,其实就是两个迭代器之间的 距离,这个函数可以自己遍历 ,也可以调用我们之前完成的distance函数 ,这个函数的作用就是 计算两个迭代器之间的距离,然后根据迭代器的 category会做一些优化
front表示返回头元素节点数据,因此对 begin()进行解引用操作 即可。在begin操作结束后,返回的list的迭代器类型(使用 iterator做别名),然后我们已经在list的迭代器的内部定义了解引用的操作,因此会返回该节点(就是头节点的值) ;end同理,不过要注意end表示一个空节点,end的上一个才是真正的尾元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 template <typename T,typename Alloc=alloc>class list{ protected : using list_node = listNode<T>; using data_allocator = simplae_alloc<list_node, Alloc>; public : using link_type = list_node; using value_type = Alloc; using reference = value_type&; using const_reference = const value_type&; using pointer = value_type*; using const_pointer = const value_type*; using difference_type = ptrdiff_t ; using size_type = size_t ; public : using iterator = list_iterator<value_type, reference, pointer>; using const_iterator = list_iterator<value_type, const_reference, const_pointer>; protected : list_node head; public : inline iterator begin () { return head->next; } inline const_iterator cbegin () const { return head->next; } inline iterator end () { return head; } inline const_iterator cend () const { return head; } inline bool empty () { return head == head->next; } inline size_type size () { return (size_type)distance (begin (), end ()); } inline reference front () { return *begin (); } inline reference back () { return *(--end ()); } };
4. list的构造元素操作 4.1 list的空间配置操作 1 using data_allocator = simplae_alloc<list_node, Alloc>;
list使用第二级空间配置器作为其空间配置器。
list是由一个个的节点连接的,因此我们需要有一个函数来创建一个节点的空间 :
1 2 3 link_type create_node_space () { return (link_type)data_allocator::allocate (); }
对应的销毁某个节点的空间 :
1 2 3 void destroy_node_space (link_type pDel) { data_allocator::deallocate (pDel); }
然后才是创建一个节点对象的行为(创建空间与构造对象 ):
如果中途构造对象的失败了,则rallback,销毁这个节点的空间
1 2 3 4 5 6 7 8 9 10 11 link_type create_node (const value_type& val) { link_type* pNew = create_node_space (); _TRY{ construct (&pNew->data, val); } _CATCH(...){ destroy_node_space (pNew); } return pNew; }
然后是销毁这个节点对象 (析构对象与销毁空间 ):
1 2 3 4 void destroy_node (link_type pDel) { ::destroy (&pDel->data); destroy_node_space (pDel); }
4.2 list的空构造函数 list 有很多构造函数,其中一个可以让我们配置 一个空的节点对象:
注意是配置而不是创建,理解其不同:
1 2 3 4 5 6 7 8 9 list () { empty_initialized (); } void empty_initialized () { head = create_node_space (); head->next = head; head->prev = head; }
相当于完成了list的初始化 ,只有一个空节点,自己指向自己。
4.3 list的push_back list的尾插其实就是完成了往某个位置插入一个元素的操作,只不过这个位置是 在 end的地方
因此我们首先写一个往某个位置创建节点并且插入的操作即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 link_type _insert(iterator position, const value_type& value) { link_type pNew = create_node (value); pNew->next = position.node; pNew->prev = position.node->prev; position.node->prev->next = pNew; position.node->prev = pNew; return pNew; } void push_back (const value_type& value) { _insert(end (), value); }
end()不是返回 head头节点吗, 为什么是尾插?
list是循环链表,因此第一个也就是最后一个 ,只要在 head 的前面就是尾部节点,head节点开始才是头部。
5. list的基本元素操作 5.1 其他插入与删除 头插法:push_front 与 push_back类似,从begin()处插入即可:
1 2 3 4 void push_front (const value_type& value) { _insert(begin (), value); }
erase删除某个位置的节点 :
直接找到其前驱节点与后继节点跳过pDel节点就可以。
1 2 3 4 5 6 7 8 9 10 iterator erase (iterator position) { link_type pDel = position.node; link_type pDelNext = pDel->next; link_type pDelPrev = pDel->prev; pDelPrev->next = pDelNext; pDelNext->prev = pDelPrev; destroy_node (pDel); return pDelNext; }
pop_back与pop_front函数,利用erase,非常简单:
1 2 3 4 5 6 7 8 void pop_front () { erase (begin ()); } void pop_back () { erase (--end ()); }
清空链表: clear
1 2 3 4 5 6 7 8 9 10 11 12 13 void clear () { link_type cur = begin ().node, temp = nullptr ; link_type end = head; while (cur != end) { temp = cur; cur = cur->next; destroy_node (temp); } cur->next = cur; cur->prev = cur; }
5.2 remove remove的作用是移除所有等于val 的值的节点:
1 2 3 4 5 6 7 8 9 10 11 void remove (const value_type& value) { iterator cur = begin (), temp = nullptr ; while (cur != end ()) { temp = cur.node->next; if (cur.node->data == value) { erase (cur); } cur = temp; } }
5.3 unique unique的作用是移除所有连续且相等data的元素节点,只留下一个。
过程:next负责前进,first负责保留每个元素的第一个,last表示尾
每次next移动到下一个位置,first此时在next的上一个位置,则比较这两个位置的值是否一样
如果一样,则需要保留first,erase掉next ,所以直接erase(next),然后还需要检查后面是否还有一样的元素,因此next=first,回来继续往后++ ,重复上面的操作,每次只删除 next 的位置,而不会删除first
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void unique () { iterator first = begin (), last = end (); iterator next = first; while (++next != last) { if (*first == *next) { erase (next); } else { first = next; } next = first; } }
5.4 transfrom* transfrom是一个内部函数,用于将 [first,last)的全部节点转移到position之前 :
其实就是几个指针的连接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void transform (iterator first, iterator last, iterator position) { if (position != last) { iterator end = last.node->prev; iterator pos_prev = position.node->prev; iterator first_prev = first.node->prev; end.node->next = position.node; first_prev.node->next = last.node; pos_prev.node->next = first.node; position.node->prev = last.node->prev; last.node->prev = first.node->prev; first.node->prev = pos_prev.node; } }
5.5 splice 基于transfrom我们便可以写出splice,此函数的功能是 将一个范围的迭代器所指的节点连接到position处:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void splice (iterator position, list& ls) { if (!ls.empty ()) { transform (ls.begin (), ls.end (), position); } } void splice (iterator position,iterator it) { iterator it_ = it; ++it_; if (it == position || it_ == position) { return ; } transform (it, it_, position); } void splice (iterator position, iterator first,iterator last) { if (first != last) { transform (first, last, position); } }
注意:由于在transform中来自不同list的迭代器是把他们连接到新的position,而不是 new出一块内存,因此原始的移动的 [first,last)中的节点会消失,我们通常使用 splice来移动节点,而不是拷贝
5.6 merge 将某个链表合并到 this中,两个链表必须是有序的,*二路归并 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void merge (list& ls) { iterator first1 = begin (); iterator first2 = ls.begin (); iterator end1 = end (); iterator end2 = ls.end (); while (first1 != end1 && first2 != end2) { if (*first1 > *first2) { iterator temp = first2; transform (first2, ++temp, first1); first2 = temp; } else { ++first1; } } if (first2 != end2) { transform (first2, end2, end1); } }
5.7 reverse 翻转整个链表:把每一个元素直接移动到begin()的前面即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 void reverse () { if (head->next == head || head->next->next == head) { return ; } iterator first = begin (),last=end (); ++first; while (first != last) { auto temp = first; transform (temp,++first, begin ()); } }
5.8 Sort* 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 void sort () { if (head->next == head || head->next->next == head) 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 ]); }
原数据:14 13 8 7 6 5 2 1 0
第一次循环
counter[0]
counter[1]
counter[2]
counter[3]
14
14
13
13,14
8
8
13,14
7
7,8,13,14
6
6
7,8,13,14
5
5,6
7,8,13,14
2
2
5,6
7,8,13,14
1
1,2,5,6,7,8,13,14
0
0
1,2,5,6,7,8,13,14
最终再归并起来: 0,1,2,5,6,7,8,13,14
有几个关键函数:
splice:把某个迭代器移到position的位置处。
merge:合并到*this成为一个有序非递减链表
swap:交换两个list容器的所有节点值。
排序过程如下:
首先传入 14:splice把14插入到carry,此时fill为0,不进入内层循环。swap把count和counter[0]容器交换,此时 counter[0]:14;carry是空的,fill 递增为 1
传入13:splice把13插入到carry中,此时fill为1,并且counter[0]不为空,进入内层循环,首先couter[0]与carry合并,合并后结果放到counter[0]中,carry变为空。然后把counter[0]与carry交换,之后counter[0]为空,carry:13,14。跳出循环后counter[1]与carry交换,counter[1]:13,14,carry变为空。
….
一直到传入0:splice把0插入到carry中,此时fill为4,由于counter[0]为空,因此不会进入内层循环。counter[0] 与carry交换,counter[0]:0,carry为空。
然后 this的empty触发,跳出大循环,从 i=1开始一直到fill-1 *闭区间 :
counter[1]与 counter[0] 合并,结果存入counter[1]
counter[2]与 counter[1] 合并,结果存入counter[2]
counter[3]与 counter[2] 合并,结果存入counter[3]
最后counter[fill-1] 为counter[3]里面存储的节点的值,因此结果为0,1,2,5,6,7,8,13,14
综上:
可以看出这基本是一个归并排序,并且这还是个非递归版本的归并排序 。
list的sort排序利用carry存储每次新插入的值 或者当作每次counter[i]与counter[i-1]合并的时候的中转站
counter数组存储每一层归并的排序结果 ,最后所有的 counter[0] 到counter [fill-1]一起自底向上 一路归并过来,最后的 counter[fill-1]存储的就是归并后的整个数组的sort排序结果
按层次归并,层层合并,最后一起合并,这就是list的sort排序思想。
counter的 数组下标表示 counter[0] 这一层只能容纳一个元素;counter[1]可以容纳两个元素;counter[2]可以容纳四个元素;counter[3]可以容纳八个元素;因此counter[i]可以容纳 2^i 个元素。