STL 源码剖析读书笔记四:序列式容器之 dequestackqueue

Posted 突然好想晒太阳

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STL 源码剖析读书笔记四:序列式容器之 dequestackqueue相关的知识,希望对你有一定的参考价值。

1. heap

1.1 heap 概述

heap 并不归属 STL 容器组件,它是 priority_queue 的幕后助手。priority_queue 允许用户以任何次序将任何元素推入容器,但取出元素时一定是从优先权最高的元素开始取。binary max heap 正具有这样的性质,适合作为 priority_queue 的底层机制。

所谓 binary heap 就是一种 完全二叉树,即整棵 binary tree 除了最底层的叶节点之外,是填满的,而最底层的叶节点由左至右又不得有空隙。

完全二叉树的整棵树内没有任何节点漏洞,这带来一个极大的好处:我们可以利用 array 来储存所有节点。这种以 array 表述 tree 的方式被称为隐式表述法。

根据上述描述,实现 priority_queue 需要的工具就很简单了,一个 array 和一组 heap 算法(实现插入元素、删除元素、取出元素、将一组整数排列为一个 heap)。由于 array 大小不能扩展,可用 vector 代替。

根据元素排列方式,heap 可分为两类:max heap 和 min heap。前者每个节点键值都大于或等于其子节点键值,后者每个节点键值都小于或等于其节点键值。max heap 的最大值在根节点,并总位于底层 array 或 vector 的起头处。STL 供应的是 max heap。

1.2 heap 算法

push_heap 算法

为满足完全二叉树的条件,新加入的元素一定要放在最下一层作为叶节点,并填补在由左至右的第一个空格,即吧新元素插入在底层 vector 的 end() 处。

新元素一般并不适合其新插入的位置,为满足 max heap 的条件,需要执行一个上溯程序:将新节点与其父节点比较,如果其键值比父节点打,就对换父子位置,如此一直上溯,直到不需要对换或到根节点为止。

以下是push_heap 算法实现细节。该函数接受两个迭代器,用来表现一个 heap 底部容器的头尾,并且新元素已经插入到底部容器的最尾端。如不符合这两个条件,执行结果不可预期。

// 注:此函数被调用时,新元素已处于底部容器最尾端
template <class RandomAccessIterator>
inline void push_heap(RandomAccessIterator first, RandomAccessIterator last) {
  __push_heap_aux(first, last, distance_type(first), value_type(first));
}

// 新值置于底部容器最尾端,洞值为 (last - first) - 1
template <class RandomAccessIterator, class Distance, class T>
inline void __push_heap_aux(RandomAccessIterator first,
                            RandomAccessIterator last, Distance*, T*) {
  __push_heap(first, Distance((last - first) - 1), Distance(0), 
              T(*(last - 1)));
}


template <class RandomAccessIterator, class Distance, class T>
void __push_heap(RandomAccessIterator first, Distance holeIndex,
                 Distance topIndex, T value) {
  // 父节点
  Distance parent = (holeIndex - 1) / 2;
  // 当尚未到达顶端,且父节点小于新值,执行上溯过程
  while (holeIndex > topIndex && *(first + parent) < value) {
    *(first + holeIndex) = *(first + parent);
    holeIndex = parent;              // 调整洞号,上升至父节点
    parent = (holeIndex - 1) / 2;    // 新洞的父节点 
  }
  // 上溯至顶端或已满足 heap 的性质,令洞值为新值
  *(first + holeIndex) = value;
}

pop_heap 算法

在 max heap 中,最大元素必然在根节点。pop 取走根节点(其实是设至底部容器 vector 的尾端节点)后,为使 heap 仍然满足 max heap 的性质,必须取下最下层最右边的节点并将其值重新安插至 max heap。这时,需要执行一个下溯过程:将空间节点和其较大子节点对调,并持续下溯直到叶节点为止。然后将被取下的元素值设给这个已到达叶子层的空洞节点,再对它执行一次上溯过程。

以下是 pop_heap 的实现细节。该函数接受两个迭代器,用来表现一个 heap 的头尾。如不符合这个条件,执行结果不可预期。

template <class RandomAccessIterator>
inline void pop_heap(RandomAccessIterator first, RandomAccessIterator last) {
  __pop_heap_aux(first, last, value_type(first));
}


template <class RandomAccessIterator, class T>
inline void __pop_heap_aux(RandomAccessIterator first,
                           RandomAccessIterator last, T*) {
  // 首先设定欲调整值为尾值,然后将首值调至尾节点,再重整 [first, last - 1)
  __pop_heap(first, last - 1, last - 1, T(*(last - 1)), distance_type(first));
}

template <class RandomAccessIterator, class T, class Distance>
inline void __pop_heap(RandomAccessIterator first, RandomAccessIterator last,
                       RandomAccessIterator result, T value, Distance*) {
  // 设定尾值为首值
  *result = *first;
  // 重整 heap,洞号为 0(即树根处),欲调整值为 value
  __adjust_heap(first, Distance(0), Distance(last - first), value);
}

template <class RandomAccessIterator, class Distance, class T>
void __adjust_heap(RandomAccessIterator first, Distance holeIndex,
                   Distance len, T value) {
  Distance topIndex = holeIndex;
  // 洞节点的右子节点
  Distance secondChild = 2 * holeIndex + 2;
  while (secondChild < len) {
    // 比较洞节点的两个子节点值,以 secondChild 为较大子节点
    if (*(first + secondChild) < *(first + (secondChild - 1)))
      secondChild--;
    // 令较大值为洞值,再令洞号下移至较大子节点处
    *(first + holeIndex) = *(first + secondChild);
    holeIndex = secondChild;
    secondChild = 2 * (secondChild + 1);
  }
  // 没有右子节点,只有左子节点
  if (secondChild == len) {
    // 令左子节点值为洞值,洞号继续下移至左子节点
    *(first + holeIndex) = *(first + (secondChild - 1));
    holeIndex = secondChild - 1;
  }
  // 此时,可能尚未满足 max heap 的性质,需要执行一次上溯过程
  __push_heap(first, holeIndex, topIndex, value);
}

注意:执行 pop_heap 后,最大元素只是被置于底部容器底端,尚未被取走,如需去其值,调用 back(),如需移除,可调用底部容器的 pop_back()。

sort_heap 算法

每次执行 pop_heap 即将 heap 中最大元素移至底部容器最尾端,若持续对整个 heap 执行 pop_heap 操作,当整个程序执行完毕时,便形成一个递增序列。

以下是 sort_heap 的实现细节。该函数接受两个迭代器,用来表现一个 heap 底部容器的头尾。如不符合这个条件,执行结果不可预期。注意:排序后,原来的 heap 就不在满足 heap 的性质。

template <class RandomAccessIterator>
void sort_heap(RandomAccessIterator first, RandomAccessIterator last) {
  // 每执行一次 pop_heap,操作范围(last)即退缩一格
  while (last - first > 1) pop_heap(first, last--);
}

make_heap 算法

make_heap 算法将一段现有的数据转化为一个 heap。以下是其实现细节。

template <class RandomAccessIterator>
inline void make_heap(RandomAccessIterator first, RandomAccessIterator last) {
  __make_heap(first, last, value_type(first), distance_type(first));
}

template <class RandomAccessIterator, class T, class Distance>
void __make_heap(RandomAccessIterator first, RandomAccessIterator last, T*,
                 Distance*) {
  // 长度为 0 或 1,不必重新调整
  if (last - first < 2) return;
  Distance len = last - first;
  // 第一个需要重新调整的节点,即最后一个非叶节点
  Distance parent = (len - 2)/2;

  while (true) {
    // 调整以 parent 为根的子树
    __adjust_heap(first, parent, len, T(*(first + parent)));
    // 直到根节点即结束调整
    if (parent == 0) return;
    // 已调整子树根节点的前一个节点
    parent--;
  }
}

2. priority_queue

2.1 priority_queue 概述

priority queue 是一个带有优先权的 queue,其内元素并非按照被推入的顺序排列,而是自动依照权值排列。权值最高者排在最前面。

缺省情况下,priority queue 利用 max heap 完成,后者以 vector 作为底部容器。

priority queue 不提供遍历功能,也不提供迭代器。

2.2 priority queue 实现

#ifndef __STL_LIMITED_DEFAULT_TEMPLATES
template <class T, class Sequence = vector<T>, 
          class Compare = less<typename Sequence::value_type> >
#else
template <class T, class Sequence, class Compare>
#endif
class  priority_queue {
public:
  typedef typename Sequence::value_type value_type;
  typedef typename Sequence::size_type size_type;
  typedef typename Sequence::reference reference;
  typedef typename Sequence::const_reference const_reference;
protected:
  Sequence c;
  Compare comp;
public:
  priority_queue() : c() {}
  explicit priority_queue(const Compare& x) :  c(), comp(x) {}

#ifdef __STL_MEMBER_TEMPLATES
  template <class InputIterator>
  priority_queue(InputIterator first, InputIterator last, const Compare& x)
    : c(first, last), comp(x) { make_heap(c.begin(), c.end(), comp); }
  template <class InputIterator>
  priority_queue(InputIterator first, InputIterator last) 
    : c(first, last) { make_heap(c.begin(), c.end(), comp); }
#else /* __STL_MEMBER_TEMPLATES */
  priority_queue(const value_type* first, const value_type* last, 
                 const Compare& x) : c(first, last), comp(x) {
    make_heap(c.begin(), c.end(), comp);
  }
  priority_queue(const value_type* first, const value_type* last) 
    : c(first, last) { make_heap(c.begin(), c.end(), comp); }
#endif /* __STL_MEMBER_TEMPLATES */

  bool empty() const { return c.empty(); }
  size_type size() const { return c.size(); }
  const_reference top() const { return c.front(); }
  void push(const value_type& x) {
    __STL_TRY {
      c.push_back(x); 
      push_heap(c.begin(), c.end(), comp);
    }
    __STL_UNWIND(c.clear());
  }
  void pop() {
    __STL_TRY {
      pop_heap(c.begin(), c.end(), comp);
      c.pop_back();
    }
    __STL_UNWIND(c.clear());
  }
};

3. slist

STL list 是双向链表。SGI STL 另外提供了一个单向链表 slist,该容器并不在 标准规格之内。

slist 与 list 的主要区别在于:slist 的迭代器是单向的 Forward Iterator,而 list 的迭代器是双向的 Bidirectional Iterator。因此 slist 的功能受到限制,同时 slist 所耗空间更小,某些操作更快。

slist 作为单向链表,没有任何方便的办法可以回头定出前一个位置,必须从头找起。换句话说,除了 slist 起点附近的区域之外,在其他位置上采用 insert 或erase 操作函数,都属不智之举。这便是 slist 相较于 list 之下的大缺点。为此,slist 特别提供了 insert_after() 和 erase_after() 供灵活运用。

3.1 slist 的节点

slist 节点及其迭代器的设计,架构上比 list 复杂许多,运用了继承关系,因此在型别转换上有复杂的表现。

// 单向链表的节点基本结构
struct __slist_node_base
{
  __slist_node_base* next;
};
// 单向链表的节点结构
template <class T>
struct __slist_node : public __slist_node_base
{
  T data;
};

inline __slist_node_base* __slist_make_link(__slist_node_base* prev_node,
                                            __slist_node_base* new_node)
{
  new_node->next = prev_node->next;
  prev_node->next = new_node;
  return new_node;
}

inline __slist_node_base* __slist_previous(__slist_node_base* head,
                                           const __slist_node_base* node)
{
  while (head && head->next != node)
    head = head->next;
  return head;
}

inline const __slist_node_base* __slist_previous(const __slist_node_base* head,
                                                 const __slist_node_base* node)
{
  while (head && head->next != node)
    head = head->next;
  return head;
}

inline void __slist_splice_after(__slist_node_base* pos,
                                 __slist_node_base* before_first,
                                 __slist_node_base* before_last)
{
  if (pos != before_first && pos != before_last) {
    __slist_node_base* first = before_first->next;
    __slist_node_base* after = pos->next;
    before_first->next = before_last->next;
    pos->next = first;
    before_last->next = after;
  }
}

inline __slist_node_base* __slist_reverse(__slist_node_base* node)
{
  __slist_node_base* result = node;
  node = node->next;
  result->next = 0;
  while(node) {
    __slist_node_base* next = node->next;
    node->next = result;
    result = node;
    node = next;
  }
  return result;
}

3.2 slist 的迭代器

// 单向链表的迭代器基本结构
struct __slist_iterator_base
{
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;
  typedef forward_iterator_tag iterator_category;

  __slist_node_base* node;

  __slist_iterator_base(__slist_node_base* x) : node(x) {}
  // 前进一个节点
  void incr() { node = node->next; }

  // 比较节点是否为同一节点
  bool operator==(const __slist_iterator_base& x) const {
    return node == x.node;
  }
  bool operator!=(const __slist_iterator_base& x) const {
    return node != x.node;
  }
};

// 单向链表的迭代器结构
template <class T, class Ref, class Ptr>
struct __slist_iterator : public __slist_iterator_base
{
  typedef __slist_iterator<T, T&, T*>             iterator;
  typedef __slist_iterator<T, const T&, const T*> const_iterator;
  typedef __slist_iterator<T, Ref, Ptr>           self;

  typedef T value_type;
  typedef Ptr pointer;
  typedef Ref reference;
  typedef __slist_node<T> list_node;

  __slist_iterator(list_node* x) : __slist_iterator_base(x) {}
  __slist_iterator() : __slist_iterator_base(0) {}
  __slist_iterator(const iterator& x) : __slist_iterator_base(x.node) {}

  reference operator*() const { return ((list_node*) node)->data; }
#ifndef __SGI_STL_NO_ARROW_OPERATOR
  pointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */

  self& operator++()
  {
    incr();
    return *this;
  }
  self operator++(int)
  {
    self tmp = *this;
    incr();
    return tmp;
  }
};

3.3 slist 的数据结构

template <class T, class Alloc = alloc>
class slist
{
public:
  typedef T value_type;
  typedef value_type* pointer;
  typedef const value_type* const_pointer;
  typedef value_type& reference;
  typedef const value_type& const_reference;
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;

  typedef __slist_iterator<T, T&, T*>             iterator;
  typedef __slist_iterator<T, const T&, const T*> const_iterator;

private:
  typedef __slist_node<T> list_node;
  typedef __slist_node_base list_node_base;
  typedef __slist_iterator_base iterator_base;
  typedef simple_alloc<list_node, Alloc> list_node_allocator;

  static list_node* create_node(const value_type& x) {
    // 配置空间
    list_node* node = list_node_allocator::allocate();
    __STL_TRY {
      // 构造元素
      construct(&node->data, x);
      node->next = 0;
    }
    __STL_UNWIND(list_node_allocator::deallocate(node));
    return node;
  }

  static void destroy_node(list_node* node) {
    // 析构元素
    destroy(&node->data);
    // 释放空间
    list_node_allocator::deallocate(node);
  }


private:
  list_node_base head;

public:
  slist() { head.next = 0; }

  slist& operator= (const slist& L);

  ~slist() { clear(); }

public:

  iterator begin() { return iterator((list_node*)head.next); }
  const_iterator begin() const { return const_iterator((list_node*)head.next);}

  iterator end() { return iterator(0); }
  const_iterator end() const { return const_iterator(0); }

  size_type size() const { return __slist_size(head.next); }

  size_type max_size() const { return size_type(-1); }

  bool empty() const { return head.next == 0; }

  void swap(slist& L)
  {
    list_node_base* tmp = head.next;
    head.next = L.head.next;
    L.head.next = tmp;
  }

public:
  friend bool operator== __STL_NULL_TMPL_ARGS(const slist<T, Alloc>& L1,
                                              const slist<T, Alloc>& L2);

public:

  reference front() { return ((list_node*) head.next)->data; }
  const_reference front() const { return ((list_node*) head.next)->data; }
  void push_front(const value_type& x)   {
    __slist_make_link(&head, create_node(x));
  }
  void pop_front() {
    list_node* node = (list_node*) head.next;
    head.next = node->next;
    destroy_node(node);
  }

  iterator previous(const_iterator pos) {
    return iterator((list_node*) __slist_previous(&head, pos.node));
  }
  const_iterator previous(const_iterator pos) const {
    return const_iterator((list_node*) __slist_previous(&head, pos.node));
  }

private:
  list_node* _insert_after(list_node_base* pos, const value_type& x) {
    return (list_node*) (__slist_make_link(pos, create_node(x)));
  }

  void _insert_after_fill(list_node_base* pos,
                          size_type n, const value_type& x) {
    for (size_type i = 0; i < n; ++i)
      pos = __slist_make_link(pos, create_node(x));
  }


  list_node_base* erase_after(list_node_base* pos) {
    list_node* next = (list_node*) (pos->next);
    list_node_base* next_next = next->next;
    pos->next = next_next;
    destroy_node(next);
    return next_next;
  }

  list_node_base* erase_after(list_node_base* before_first,
                              list_node_base* last_node) {
    list_node* cur = (list_node*) (before_first->next);
    while (cur != last_node) {
      list_node* tmp = cur;
      cur = (list_node*) cur->next;
      destroy_node(tmp);
    }
    before_first->next = last_node;
    return last_node;
  }


public:

  iterator insert_after(iterator pos, const value_type& x) {
    return iterator(_insert_after(pos.node, x));
  }

  iterator insert_after(iterator pos) {
    return insert_after(pos, value_type());
  }

  void insert_after(iterator pos, size_type n, const value_type& x) {
    _insert_after_fill(pos.node, n, x);
  }
  void insert_after(iterator pos, int n, const value_type& x) {
    _insert_after_fill(pos.node, (size_type) n, x);
  }
  void insert_after(iterator pos, long n, const value_type& x) {
    _insert_after_fill(pos.node, (size_type) n, x);
  }

  iterator insert(iterator pos, const value_type& x) {
    return iterator(_insert_after(__slist_previous(&head, pos.node), x));
  }

  iterator insert(iterator pos) {
    return iterator(_insert_after(__slist_previous(&head, pos.node),
                                  value_type()));
  }

  void insert(iterator pos, size_type n, const value_type& x) {
    _insert_after_fill(__slist_previous(&head, pos.node), n, x);
  } 
  void insert(iterator pos, int n, const value_type& x) {
    _insert_after_fill(__slist_previous(&head, pos.node), (size_type) n, x);
  } 
  void insert(iterator pos, long n, const value_type& x) {
    _insert_after_fill(__slist_previous(&head, pos.node), (size_type) n, x);
  } 


public:
  iterator erase_after(iterator pos) {
    return iterator((list_node*)erase_after(pos.node));
  }
  iterator erase_after(iterator before_first, iterator last) {
    return iterator((list_node*)erase_after(before_first.node, last.node));
  }

  iterator erase(iterator pos) {
    return (list_node*) erase_after(__slist_previous(&head, pos.node));
  }
  iterator erase(iterator first, iterator last) {
    return (list_node*) erase_after(__slist_previous(&head, first.node),
                                    last.node);
  }

  void resize(size_type new_size, const T& x);
  void resize(size_type new_size) { resize(new_size, T()); }
  void clear() { erase_after(&head, 0); }

public:
  void reverse() { if (head.next) head.next = __slist_reverse(head.next); }

  void remove(const T& val); 
  void unique(); 
  void merge(slist& L);
  void sort();
};

以上是关于STL 源码剖析读书笔记四:序列式容器之 dequestackqueue的主要内容,如果未能解决你的问题,请参考以下文章

《STL源码剖析》——第四章序列容器

stl源码剖析-序列式容器 之 list

STL基础序列式容器之forward_list

Effective STL 读书笔记

Stl源码剖析读书笔记之Alloc细节

❤️ STL 序列式容器 vector 超硬核源码剖析 ❤️