ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

SGI STL双端队列deque

2022-05-13 10:34:10  阅读:195  来源: 互联网

标签:node map deque STL 双端 SGI start 缓冲区 size


目录

deque vs vector

vector是单向开口的连续线性空间,适合在尾端插入、删除元素,O(1);deque是双向开口的连续线性空间,适合在头尾两端分别进行元素的插入和删除操作,O(1)。
vector也可以在头尾插入、删除元素,不过在头部操作的效率非常低,O(n)。

可以指定,deque和vector区别在于:
1)deque允许常数时间在头端插入、删除元素,vector需要O(n)时间;
2)deque没有容量(capacity)概念,因为是动态地以分段连续空间组合而成,随时可以增加一段新空间并链接起来。而vector容量不足时,会重新配置一块更大的连续空间,然后将元素复制到新空间并释放旧空间。
3)deque提供Random Access Iterator,但其迭代器并不是普通指针,访问元素效率比vector低。因此,应尽可能选择用vector。对deque的排序,可以先将deque完整复制到一个vector,排序后再复制回deque。


deque中控器

deque是由一段一段的定量连续空间构成。一旦有必要在deque的首端或尾端增加新空间,就会配置一段连续空间,然后串接在deque的首端或尾端。而deque的最大任务就是,在这些分段的连续空间上,维护其整体连续的假象,并提供随机存取的接口。避免vector那种“重新配置、复制、释放”的扩容步骤,代价是复杂的迭代器结构。

为了维护分段空间整体连续的假象,数据结构和迭代器前进、后退等操作繁琐,必须有中央控制。

deque采用一块所谓map作为主控,不是STL的map容器,而是一小块连续空间,可以看作是一个数组,map中每个元素称为节点(即node),指向一个缓冲区,用于存放用户数据。SGI STL允许用户指定缓冲区大小,默认值0表示使用512byte缓冲区。

旧版的SGI STL deque包含对底层空间的维护;而SGI STL v3.3中,deque已经将对底层空间的维护,包括map,封装到了其基类_Deque_base中。

这里为了方便,采用SGI STL v2.03 进行分析。

// deque.h from SGI STL v2.03
template <class T, class Alloc = alloc, size_t BufSiz = 0>
class deque {
public:                         // Basic types
    // 定义内嵌类型
  typedef T value_type;
  typedef value_type* pointer;
  typedef value_type& reference;
  typedef const value_type& const_reference;
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;
...
protected:                      // Internal typedefs
  typedef pointer* map_pointer;
...
protected:                      // Data members
...
  map_pointer map;
  size_type map_size;
...
};

deque 迭代器

deque是分段连续空间。如何维护“整体连续”的假象?
这个重任落在operator++和operator--身上,而其实现离不开deque迭代器。deque迭代器必须能1)指出分段连续空间(实际存储数据的缓冲区)的地址;2)判断自己是否已经处于其所在缓冲区的边缘,如果是一旦前进或后退,就要跳跃至下一个或上一个缓冲区。而为了能正确跳跃,必须通过中控器(map)。

deque迭代器(__deque_iterator)的数据结构:

// deque.h from SGI STL v2.03

template <class T, class Ref, class Ptr, size_t BufSiz>
struct __deque_iterator { // 没有继承自 std::iterator
  typedef __deque_iterator<T, T&, T*, BufSiz>             iterator;
  typedef __deque_iterator<T, const T&, const T*, BufSiz> const_iterator;
  static size_t buffer_size() {return __deque_buf_size(BufSiz, sizeof(T)); }

    // 未继承自 std::iterator, 须自定义标准迭代器要求的5个迭代器关联类型
  typedef random_access_iterator_tag iterator_category; // 1
  typedef T value_type;              // 2
  typedef Ptr pointer;               // 3
  typedef Ref reference;             // 4
  typedef size_t size_type;
  typedef ptrdiff_t difference_type; // 5
  typedef T** map_pointer;

  typedef __deque_iterator self;

    // 保持与容器的联结
  T* cur;   // 迭代器所指缓冲区的当前(current)元素
  T* first; // 迭代器所指缓冲区的头
  T* last;  // 迭代器所指缓冲区的尾(含备用空间)
  map_pointer node; // 指向中控器
...
};

buffer_size()决定缓冲区大小,其内部调用了全局函数__deque_buf_size():

// deque.h from SGI STL v2.03

// Note: this function is simply a kludge to work around several compilers'
//  bugs in handling constant expressions.
// 如果n不为0, 传回n, 表示buffer size 由用户定义
// 如果n为0, 表示buffer size使用默认值, 那么
// 如果sz(元素大小, sizeof(value_type))小于512, 传回512/sz,
// 如果sz不小于512, 传回1
inline size_t __deque_buf_size(size_t n, size_t sz)
{
  return n != 0 ? n : (sz < 512 ? size_t(512 / sz) : size_t(1));
}

deque的中控器,缓冲区,迭代器的相互关系:

假设我们现在有一个deque,让其缓冲区buffer大小为32byte,那么每个缓冲区可容纳32 / sizeof(int) = 8 个元素。经过一些操作后,deque拥有20个元素,那么begin()(即start),end()(即finish)传回的两个迭代器应该如下图所示。

20个元素,共需要20/3=8个缓冲区(向上取整),所以map内需要3个节点,分别指向这3个缓冲区:迭代器start内的cur指针指向第一个缓冲区的第一个元素,迭代器finish内的cur指针指向最后一个缓冲区的最后一个元素。

每个缓冲区只能提供32byte空间,3个缓冲区就是96byte,20个元素(每个占4byte),共计80byte,也就是说最后会剩16byte,也就是4个元素大小。

deque迭代器内对各种指针运算都做了重载,如各种指针运算(加、减、前进、后退等),特别需要注意的是:一旦移动指针遇到缓冲区边界时,就要特别小心,视前进或后退具体情况而定,可能需要调用set_node()跳跃到下一个缓冲区。

// __deque_iterator<> member function

  void set_node(map_pointer new_node) {
    node = new_node;
    first = *new_node;
    last = first + buffer_size();
  }

    // 下面各个重载函数是__deque_iterator<>成功运作的关键

  reference operator*() const { return *cur; }
  pointer operator->() const { return &(operator*()); }

  difference_type operator-(const self& x) const {
    return buffer_size() * (node - x.node - 1) +
      (cur - first) + (x.last - x.cur);
  }
    
    // prefix increment operator
  self& operator++() {          // 前置式
    ++cur;                      // 切换至下一个元素
    if (cur == last) {          // 如果已经到达缓冲区末尾
      set_node(node + 1);       // 切换至下一个node, 即下一个缓冲区
      cur = first;
    }
    return *this;
  }
    // post increment operator
  self operator++(int)  {       // 后置式
    self tmp = *this;
    ++*this;                    // 注意这里调用前置式递增
    return tmp;
  }
    // prefix decrement operator
  self& operator--() {
    if (cur == first) {         // 如果已经达到所在缓冲区头端
      set_node(node - 1);       // 就切换至前一个node, 即前一个缓冲区
      cur = last;               // 切换至缓冲区最后一个元素(前一个缓冲区的最后一个元素)
    }
    --cur;                      // 切换至前一个元素
    return *this;
  }
    // post decrement operator
  self operator--(int) {
    self tmp = *this;
    --*this;                    // 注意这里调用前置式递减
    return tmp;
  }

    // 以下实现随机存取. 迭代器可以直接跳跃n个距离
    // 前进n个距离
  self& operator+=(difference_type n) { // 形如 x += n
    difference_type offset = n + (cur - first);
    if (offset >= 0 && offset < buffer_size())
      cur += n;
    else {
      difference_type node_offset =
        offset > 0 ? offset / buffer_size()
                   : -difference_type((-offset - 1) / buffer_size()) - 1;
      set_node(node + node_offset);
      cur = first + (offset - node_offset * buffer_size());
    }
    return *this;
  }
    
  self operator+(difference_type n) const { // 形如 x = x + n
    self tmp = *this;
    return tmp += n;
  }
    // 后退n个距离
  self& operator-=(difference_type n) { return *this += -n; } // 形如 x -= n
  self operator-(difference_type n) const { // 形如 x = x - n
    self tmp = *this;
    return tmp -= n;
  }
    
    // 随机存取, 迭代器直接跳跃n个距离, 调用了operator+
  reference operator[](difference_type n) const { return *(*this + n); }

    // 以下实现大小判断
  bool operator==(const self& x) const { return cur == x.cur; }
  bool operator!=(const self& x) const { return !(*this == x); }
  bool operator<(const self& x) const {
    return (node == x.node) ? (cur < x.cur) : (node < x.node);
  }

deque 数据结构

deque维护一个指向map的指针(map),start、finish 两个迭代器:指向第一个缓冲区、最后一个缓冲区。维护当前map大小(map_size)。当map所能容纳的节点数不足时,就需要扩容,为其配置一块更大map,相当于vector扩容。

// See __deque_buf_size().  The only reason that the default value is 0
//  is as a workaround for bugs in the way that some compilers handle
//  constant expressions.
template <class T, class Alloc = alloc, size_t BufSiz = 0>
class deque {
public:                         // Basic types
    // 定义内嵌类型
  typedef T value_type;
  typedef value_type* pointer;
  typedef value_type& reference;
  typedef const value_type& const_reference;
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;

public:                         // Iterators
  typedef __deque_iterator<T, T&, T*, BufSiz>              iterator;
  typedef __deque_iterator<T, const T&, const T&, BufSiz>  const_iterator;
...
protected:                      // Internal typedefs
  typedef pointer* map_pointer;

protected:                      // Data members
  iterator start;     // map中第一个node
  iterator finish;    // map中最后一个node

  map_pointer map;    // 指向map, map可以看做是一个数组, 是一块连续空间, 每个元素都是个指针, 指向一个node
  size_type map_size; // map内有多少个指针, 即对应有多少个缓冲区
...
};

有了上面的结构,再实现下面获取头尾迭代器,元素个数等信息,就很容易了

public:                         // Basic accessors
  iterator begin() { return start; }
  iterator end() { return finish; }
  const_iterator begin() const { return start; }
  const_iterator end() const { return finish; }
    
    // 反向迭代器
  reverse_iterator rbegin() { return reverse_iterator(finish); }
  reverse_iterator rend() { return reverse_iterator(start); }
  const_reverse_iterator rbegin() const {
    return const_reverse_iterator(finish);
  }
  const_reverse_iterator rend() const {
    return const_reverse_iterator(start);
  }

  reference operator[](size_type n) { return start[n]; } // 调用__deque_iterator<>::operator[]
  const_reference operator[](size_type n) const { return start[n]; }

  reference front() { return *start; } // 调用__deque_iterator<>::operator*
  reference back() {
    iterator tmp = finish;
    --tmp;       // 调用__deque_iterator<>::operator--
    return *tmp; // 调用__deque_iterator<>::operator*
  }
  const_reference front() const { return *start; }
  const_reference back() const {
    const_iterator tmp = finish;
    --tmp;
    return *tmp;
  }
    
    // 下面最后有2个';', 奇观但合语法
    // 求deque的元素个数
  size_type size() const { return finish - start;; } // 调用__deque_iterator<>::operator-
    // 求deque理论上最大元素个数0xFFFFFFFF (UINT32_MAX)
  size_type max_size() const { return size_type(-1); }
    // 判断deque是否为空, 即元素个数是否为0
  bool empty() const { return finish == start; }

deque的构造与内存管理

构造deque及插入元素示例

站在客户角度使用deque,观察deque构造方式、大小变化(size()),插入数据方式(push_front, push_back)。

// 客户角度测试deque

int main()
{
    deque<int, alloc, 32> ideq(20, 9); // 构造deque, 20个值为9的元素, 缓冲区大小32byte
    cout << "size = " << ideq.size() << endl; // 20
    
    // 每个元素设新值
    for (int i = 0; i < ideq.size(); ++i) {
        ideq[i] = i;
    }

    for (int i = 0; i < ideq.size(); ++i) {
        cout << ideq[i] << ' '; // 值分别为0 1 2 3 ... 19
    }
    cout << endl;

    // 在尾端增加3个元素, 值0,1,2
    for (int i = 0; i < 3; ++i) {
        ideq.push_back(i);
    }

    for (int i = 0; i < ideq.size(); ++i) {
        cout << ideq[i] << ' '; // 值分别为0 1 2 3 ... 19 0 1 2
    }
    cout << endl;
    cout << "size=" << ideq.size() << endl; // size=23

    // 尾端添加1个元素, 值3
    ideq.push_back(3);
    for (int i = 0; i < ideq.size(); ++i) {
        cout << ideq[i] << ' '; // 值分别为0 1 2 3 ... 19 0 1 2 3
    }
    cout << endl;
    cout << "size=" << ideq.size() << endl; // size=24

    // 在最前端增加1个元素, 值99
    ideq.push_front(99);
    for (int i = 0; i < ideq.size(); ++i) {
        cout << ideq[i] << ' ';  // 值分别为99 0 1 2 3 ... 19 0 1 2 3
    }
    cout << endl;
    cout << "size=" << ideq.size() << endl; // size=25

    // 在最前端增加2个元素, 值分别为98 97
    ideq.push_front(98);
    ideq.push_front(97);
    for (int i = 0; i < ideq.size(); ++i) {
        cout << ideq[i] << ' '; // 值分别为97 98 99 0 1 2 3 ... 19 0 1 2 3
    }
    cout << endl;
    cout << "size=" << ideq.size() << endl; // size=27

    // 查找值为99的元素并打印
    deque<int, alloc, 32>::iterator it;
    it = std::find(ideq.begin(), ideq.end(), 99);
    cout << *it << endl;         // 99
    cout << *(it._M_cur) << endl; // 99
    return 0;
}

deque缓冲区是如何扩充的?

分步讲解。
1)用户程序声明一个deque。
缓冲区大小32byte(第三个模板参数),保留20个元素空间,每个元素初值9。

deque<int, alloc, 32> ideq(20, 9);

2)deque自定义空间配置器。

protected:                      // Internal typedefs
    // deque内嵌的专属空间配置器, 每次配置一个元素大小
  typedef simple_alloc<value_type, Alloc> data_allocator;
    // deque内嵌的专属空间配置器, 每次配置一个指针大小
  typedef simple_alloc<pointer, Alloc> map_allocator;

3)调用构造函数constructor,构造dque

4)调用push_back、push_front,插入数据

下面就构造函数、push_back、push_front等方面进行分析。

constructor

并提供一个constructor:

    // 构造函数
  deque(int n, const value_type& value)
    : start(), finish(), map(0), map_size(0) {
      fill_initialize(n, value);
  }

其内调用的fill_initialize(),负责产生并安排好deque的结构,并设置元素初值

// 负责产生并安排好deque的结构, 并设置元素初值
// @tparam T 元素类型
// @tparam Alloc 内存分配子
// @tparam BufSiz 缓冲区大小
// @param n 元素个数
// @param value 元素初值
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::fill_initialize(size_type n,
                                               const value_type& value) {
  create_map_and_nodes(n); // 把deque的结构都产生并安排好
  map_pointer cur;
  try {
    // 为每个节点的缓冲区设置初值
    for (cur = start.node; cur < finish.node; ++cur)
      uninitialized_fill(*cur, *cur + buffer_size(), value);
    // 最后一个节点的设定稍有不同, 因为尾端可能有备用空间不需要设初值
    uninitialized_fill(finish.first, finish.cur, value);
  }
  catch(...) {
      // 发生异常时rollback
    for (map_pointer n = start.node; n < cur; ++n)
      destroy(*n, *n + buffer_size());
    destroy_map_and_nodes();
    throw;
  }
}

内部调用的create_map_and_nodes(),负责产生并安排好deque的结构。注意和fill_initialize区别,前者并不负责初值填充。

// 负责产生并安排好deque的结构
// @param num_elements 元素个数
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::create_map_and_nodes(size_type num_elements) {
    // 需要的节点数 = 元素个数 / 每个缓冲区可容纳元素个数 + 1
  size_type num_nodes = num_elements / buffer_size() + 1;
    // map要管理的节点数量. 最少是8个, 最多是 所需节点数+2
    // 前后各预留一个, 扩充时可用
  map_size = max(initial_map_size(), num_nodes + 2);
    // 配置一个 具有map_size个节点的map
  map = map_allocator::allocate(map_size);
    
    // 让nstart指向map管理node的第一个(如果开头有额外分配的+1节点, 就忽略)
    // 让nfinish指向map管理node的最后一个(如果末尾有额外分配的+1节点, 就忽略)
    // 总之, [nstart, nfinish] 节点数为num_nodes (而非num_nodes+2)
  map_pointer nstart = map + (map_size - num_nodes) / 2;
  map_pointer nfinish = nstart + num_nodes - 1;
    
  map_pointer cur;
  try {
    // 为map内每个节点配置缓冲区. 所有缓冲区加起来就是deque的可用空间,
    // 最后一个缓冲区可能有剩余空间
    for (cur = nstart; cur <= nfinish; ++cur)
      *cur = allocate_node();
  }
  catch(...) {
    // commit or rollback
    for (map_pointer n = nstart; n < cur; ++n)
      deallocate_node(*n);
    map_allocator::deallocate(map, map_size);
    throw;
  }

    // 为deque内两个迭代器start和end设置正确内容
    // 注意这里的finish跟普通迭代器有区别, finish指向的node是有缓冲区, 是有意义的;
    // 普通迭代器的最后一个(end), 通常是没有对应数据的, 只是一个标记
  start.set_node(nstart);
  finish.set_node(nfinish);
  start.cur = start.first;
  finish.cur = finish.first + num_elements % buffer_size();
}

// map初始大小, 管理的节点数量
static size_type initial_map_size() { return 8; }

// 每个缓冲区可容纳元素个数
static size_t buffer_size() {return __deque_buf_size(BufSiz, sizeof(T)); }

以示例中一段为每个元素重新设置,并在末尾插入3个新元素为例:

// 摘自前面示例程序
for (int i = 0; i < ideq.size(); ++i)
    ideq[i] = i;

for (int i = 0; i < 3; ++i
    ideq.push_back(i);

运行完这个代码片段,此时,最后一个缓冲区仍有4个备用元素空间,所以不会引起缓冲区的再配置。此时,deque状态如下图:

push_back

push_back在deque的尾端插入一个新元素。

public:                         // push_* and pop_*
    // 向末尾插入一个元素
  void push_back(const value_type& t) {
    if (finish.cur != finish.last - 1) {
        // 最后缓冲区尚有一个以上备用空间
      construct(finish.cur, t); // 直接在备用空间上构造元素
      ++finish.cur;             // 调整最后缓冲区的使用状态
    }
    else // 最后缓冲区已无(或只剩1个)元素备用空间
      push_back_aux(t);
  }

// Called only if finish.cur == finish.last - 1.
// 最后一个缓冲区备用空间不足(为1)时,  才会被调用
// 会配置一整块新的缓冲区, 再设置新元素, 更新相应迭代器状态
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::push_back_aux(const value_type& t) {
  value_type t_copy = t;
  reserve_map_at_back();                // 若符合某种条件则必须重新换一个map, 为map扩容
  *(finish.node + 1) = allocate_node(); // 配置一个新节点(缓冲区)
  try {
    construct(finish.cur, t_copy);      // 调用构造函数, 针对插入元素设值
    finish.set_node(finish.node + 1);   // 改变finish, 令其指向新节点
    finish.cur = finish.first;          // 设定finish的状态
  }
  catch(...) {
      // commit or rollback
    deallocate_node(*(finish.node + 1));
    throw;
  }
}

其中,push_back_aux只有在最后一个缓冲区(迭代器finish所指buffer)的备用空间只剩一个元素剩余时,才会被调用。思路是先配置一整块新的缓冲区,然后再设置新元素的内容,再更新迭代器finish。

也就是说,在上面例子基础上,当备用空间只剩1个元素大小空间时,继续调用push_back()插入新元素3,会导致配置信缓冲区,更新相应的数据结构,备用空间也由原来的1变成新缓冲区的8。deque结构状态变化,如下图所示:

push_front

push_front在deque的头端插入一个新元素。

    // 向头端插入一个元素
  void push_front(const value_type& t) {
    if (start.cur != start.first) {
        // 第一个缓冲区尚有一个以上备用空间
      construct(start.cur - 1, t); // 直接在备用空间上构造元素
      --start.cur;                 // 调整第一个缓冲区的使用状态
    }
    else // 第一个缓冲区无备用空间
      push_front_aux(t);
  }

// Called only if start.cur == start.first.
// 只有当第一个缓冲区没有备用空间时, 才会被调用
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::push_front_aux(const value_type& t) {
  value_type t_copy = t;
  reserve_map_at_front();       // 如果符合某种条件则必须换一个map, 为map扩容
  *(start.node - 1) = allocate_node(); // 配置一个新节点(即缓冲区)
  try {
    start.set_node(start.node - 1); // 跳转到第一个缓冲区的前一个缓冲区(新配置的缓冲区), 新缓冲区成为新的第一个缓冲区
    start.cur = start.last - 1;     // 更新第一个缓冲区的待插入数据位置(start.cur)为第一个缓冲区末尾
    construct(start.cur, t_copy);   // 在第一个缓冲区末尾构造元素
  }
  catch(...) {
      // commit or rollback
    start.set_node(start.node + 1);
    start.cur = start.first;
    deallocate_node(*(start.node - 1));
    throw;
  }
}

在push_back向尾端插入3的例子基础上,接着利用push_front向头端插入99,deque的结构状态变成如下图所示。由于第一个缓冲区前面已无备用空间,故需要调用push_front_aux()申请新的空间,第一个缓冲区备用空间也由原来的0增加为新值7。同时,也需要更新相应的迭代器状态。

如果借着调用push_front,向deque头部插入98,97,那么就可以直接在备用空间上构造新元素,而无需申请新的缓冲区,因为第一个缓冲区备用空间足够(并非为空)。deque的结构状态会变成:

push_back 和push_front中,分别调用了一个很重要的函数reserve_map_at_back(),reserve_map_at_front()。该函数用来在必要的时候,重新配置map,为其扩容。

  // Makes sure the map has space for new nodes.  Does not actually
  //  add the nodes.  Can invalidate map pointers.  (And consequently,
  //  deque iterators.)
    
    // 如果map尾端的节点备用空间不足, 就换一个更大的map
  void reserve_map_at_back (size_type nodes_to_add = 1) {
      // 判断map尾端节点备用空间是否足够容纳nodes_to_add个node
    if (nodes_to_add + 1 > map_size - (finish.node - map))
      reallocate_map(nodes_to_add, false); // 重新配置一个map(配置更大的, 拷贝原来的到新的, 释放原来的)
  }
  
    // 如果map前端的节点备用空间不足, 就换一个更大的map
  void reserve_map_at_front (size_type nodes_to_add = 1) {
      // 判断map头端节点备用空间是否足够容纳nodes_to_add个node
    if (nodes_to_add > start.node - map)
      reallocate_map(nodes_to_add, true); // 重新配置一个map(配置更大的, 拷贝原来的到新的, 释放原来的)
  }


// 满足一定条件时, 为map重新配置新空间, 相当于vector扩容
// 主要工作: 配置更大空间, 拷贝原来元素到新空间, 释放原来空间
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::reallocate_map(size_type nodes_to_add,
                                              bool add_at_front) {
  size_type old_num_nodes = finish.node - start.node + 1; // 已经在使用的node数
  size_type new_num_nodes = old_num_nodes + nodes_to_add; // 旧的node数 + 待添加的node数

  map_pointer new_nstart;
  if (map_size > 2 * new_num_nodes) {
      // map空间足够: map现有大小超过2倍的新node数
    new_nstart = map + (map_size - new_num_nodes) / 2
                     + (add_at_front ? nodes_to_add : 0); // 计算新的第一个缓冲区对应node在map中位置
    // 如果原来node起点更早, 就从左到右将内容拷贝到新区间
    if (new_nstart < start.node)
      copy(start.node, finish.node + 1, new_nstart);
    // 如果原来的node起点更晚, 就从右到左将内容拷贝到新区间
    else
      copy_backward(start.node, finish.node + 1, new_nstart + old_num_nodes);
  }
  else {
    size_type new_map_size = map_size + max(map_size, nodes_to_add) + 2;

    // 配置一块空间, 准备给新map使用
    map_pointer new_map = map_allocator::allocate(new_map_size);
    new_nstart = new_map + (new_map_size - new_num_nodes) / 2
                         + (add_at_front ? nodes_to_add : 0);
    // 把原map内容拷贝过来
    copy(start.node, finish.node + 1, new_nstart);
    // 释放原map
    map_allocator::deallocate(map, map_size);
    
    // 设置新map的起始地址, 大小
    map = new_map;
    map_size = new_map_size;
  }

  // 更新迭代器start和finish
  start.set_node(new_nstart);
  finish.set_node(new_nstart + old_num_nodes - 1);
}

deque的元素操作

关于元素操作,只讲几个典型的member function。

pop_back

pop_back 从末尾弹出一个元素。可能会引起最后一个缓冲区释放。


  // 从末尾弹出一个元素, 缓冲区为空时, 将缓冲区释放掉
  void pop_back() {
    if (finish.cur != finish.first) {
        // 最后一个缓冲区有一个(或更多)元素
      --finish.cur;        // 调整指针, 相当于排除了最后元素
      destroy(finish.cur); // 析构最后元素
    }
    else
        // 最后一个缓冲区没有任何元素, 将缓冲区释放掉
      pop_back_aux();
  }

// Called only if finish.cur == finish.first.
// 只有当finish.cur == finish.first 时才会被调用, 用于释放最后一个缓冲区
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>:: pop_back_aux() {
  deallocate_node(finish.first);     // 释放最后一个缓冲区
  finish.set_node(finish.node - 1);  // 调整finish状态, 使其指向上一个缓冲区的最后一个元素
  finish.cur = finish.last - 1;
  destroy(finish.cur);               // 析构该元素
}

pop_front

pop_front 从头端弹出一个元素。可能会引起第一个缓冲区释放。

  // 从头端弹出一个元素, 缓冲区为空时, 将缓冲区释放掉
  void pop_front() {
    if (start.cur != start.last - 1) {
        // 第一个缓冲区有1个以上元素
      destroy(start.cur);  // 将第一个元素析构
      ++start.cur;         // 调整指针, 相当于排除了第一元素
    }
    else // 第一个缓冲区只有1个元素, 释放缓冲区
      pop_front_aux();
  }

// Called only if start.cur == start.last - 1.  Note that if the deque
//  has at least one element (a necessary precondition for this member
//  function), and if start.cur == start.last, then the deque must have
//  at least two nodes.
// 只有当start.cur == start.last - 1时才会被调用
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::pop_front_aux() {
  destroy(start.cur);             // 将第一缓冲区的第一个元素析构
  deallocate_node(start.first);   // 释放第一个缓冲区
  start.set_node(start.node + 1); // 调整start的状态, 使指向下一个缓冲区的第一个元素
  start.cur = start.first;
}

erase

清除指定位置元素。清除元素后,会导致其他元素的移动,具体是前移,还是后移,取决于前后哪个部分的元素较少。

public:                         // Erase
    // 清除pos所指元素
  void erase(iterator pos) {
    iterator next = pos;
    ++next;
    
    // 如果清除点之前元素比较少( < 现有元素数量一半), 就移动清除点以前的元素
    if (pos - start < size() / 2) {
      copy_backward(start, pos, next); // 从后往前移动元素
      pop_front(); // 移动完毕, 最前一个元素冗余, 去除之
    }
    else { // 清除点之后的元素比较少
      copy(next, finish, pos); // 移动清除点之后的元素
      pop_back();              // 移动完毕, 最后一个元素冗余, 去除之
    }
  }

清除指定区间[first, last)内所有元素。

// 清除[first, last)范围内所有元素
template <class T, class Alloc, size_t BufSize>
void deque<T, Alloc, BufSize>::erase(iterator first, iterator last) {
  if (first == start && last == finish) // 如果清除区间是整个deque, 直接调用clear()
    clear();
  else {
    difference_type n = last - first;             // 清除区间长度
    difference_type elems_before = first - start; // 清除区间前方元素个数
    if (elems_before < (size() - n) / 2) {        // 如果前方的元素较少, 向后移动前方元素(覆盖清除区间)
      copy_backward(start, first, last);          // 向后移动元素, [start, first) => [xxx, last)
      iterator new_start = start + n;             // 标记deque的新起点, new_start = last-(first-start) = start + n
      destroy(start, new_start);                  // 移动完毕, 将冗余的元素析构
       // 以下将冗余的缓冲区[start, new_start)释放
      for (map_pointer cur = start.node; cur < new_start.node; ++cur)
        data_allocator::deallocate(*cur, buffer_size());
      start = new_start; // 设定deque新起点
    }
    else {
      copy(last, finish, first);                   // 向前移动元素, [last, finish) => [first, xxx)
      iterator new_finish = finish - n;            // 标记deque的新尾点
      destroy(new_finish, finish);                 // 移动完毕, 将冗余的元素析构, 要销毁的元素个数n = finish - new_finish
      // 以下将冗余的缓冲区[new_finish+1, finish]释放
      for (map_pointer cur = new_finish.node + 1; cur <= finish.node; ++cur)
        data_allocator::deallocate(*cur, buffer_size());
      finish = new_finish; // 设定deque新起点
    }
  }
}

insert

insert也有很多个版本,不过最基础的是下面这个版本,允许在某个点之前插入一个元素, 并设定其值。

public:                         // Insert
    // 在position出插入一个元素x, 值为x
  iterator insert(iterator position, const value_type& x) {
    if (position.cur == start.cur) {       // 如果插入点是deque最前端, 就交给push_front
      push_front(x);
      return start;
    }
    else if (position.cur == finish.cur) { // 如果插入点是deque最尾端, 就交给push_back
      push_back(x);
      iterator tmp = finish;
      --tmp;
      return tmp;
    }
    else {
      return insert_aux(position, x); // 如果插入点是中间位置, 就交给insert_aux
    }
  }

// 在position处插入一个元素, 其值为x
template <class T, class Alloc, size_t BufSize>
deque<T, Alloc, BufSize>::iterator
deque<T, Alloc, BufSize>::insert_aux(iterator pos, const value_type& x) {
  difference_type index = pos - start; // 插入点之前元素个数
  value_type x_copy = x;
  if (index < size() / 2) {            // 如果插入点之前的元素个数比较少, 不到总元素个数1/2
    push_front(front());               // 在最前端加入与第一个元素同值的元素
    iterator front1 = start;           // 下面标示记号, 然后进行元素移动
    ++front1;
    iterator front2 = front1;
    ++front2;
    pos = start + index;
    iterator pos1 = pos;
    ++pos1;
    copy(front2, pos1, front1);        // 往前移动一个单位, [front2, pos1) => [front1, xxx)
  }
  else { // 如果插入点之前的元素个数比较多, 那就把元素整体往后移动
    push_back(back()); // 在尾端插入一个与最后一个元素同值的元素
    iterator back1 = finish;
    --back1;
    iterator back2 = back1;
    --back2;
    pos = start + index;
    copy_backward(pos, back2, back1);  // 往后移动一个单位, [pos, back2) => [xxx, back1)
  }
  *pos = x_copy;                       // 将x赋值给待插入点pos
  return pos;
}

小结

1)deque是双端队列,依赖于中控器+缓冲区来存取数据,中控器管理缓冲区,缓冲区用于存储数据。从而实现在头尾两端O(1)时间复杂度内插入数据,有效避免了vector在头部插入数据时需要整体移动数据的问题。

2)deque的iterator并非继承自标准的迭代器(iterator<>),内含自定义的cur, fisrt, last, node等域,使用时需要特别小心。新版STL可能已经修改,如C++11 MSVC STL中deque迭代器是继承自标准的迭代器。

3)从deque擦除数据(erase)效率并不高,擦除节点在中间位置时,时间复杂度O(n)。

4)如果可以,优先使用vector。因为vector支持O(1)时间随机访问内部元素,内部数据结构维护简单。

标签:node,map,deque,STL,双端,SGI,start,缓冲区,size
来源: https://www.cnblogs.com/fortunely/p/16265683.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有