zoukankan      html  css  js  c++  java
  • 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个元素的序列,这应该足够了。

  • 相关阅读:
    关于《注意力模型--Attention注意力机制》的学习
    神经网络参数计算
    FPN(feature pyramid networks)算法讲解
    RetinaNet-focal loss
    论文阅读: RetinaNet
    CNN+LSTM:看图说话
    非极大值抑制-NMS
    python IO文件操作 file文件操作
    软件测试定义 分类
    软件生命周期
  • 原文地址:https://www.cnblogs.com/MengX/p/12327253.html
Copyright © 2011-2022 走看看