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);
    }
...
}
View Code

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个元素的序列,这应该足够了。

猜你喜欢

转载自www.cnblogs.com/MengX/p/12327253.html