序列式容器
vector
线性的动态分配存储空间。定义如下
template <class T, class Alloc = alloc> class vector { public: // 类型相关定义 typedef T value_type; typedef value_type* pointer; typedef value_type* iterator; typedef value_type& reference; typedef size_t size_type; typedef ptrdiff_t difference_type; protected: //定义配置器 typedef simple_alloc<value_type, Alloc> data_allocator; iterator start; // 内存起始地址 iterator finish; // 当时使用内存的末尾地址,每次插入和删除都会修改 iterator end_of_storage; // 内存的结束地址 // 关键函数,在某个位置插入一个数据 void insert_aux(iterator position, const T& x); // 使用配置器释放内存 void deallocate() { if (start) data_allocator::deallocate(start, end_of_storage - start); } // 申请并初始化一块大小为n的内存,并初始化为value void fill_initialize(size_type n, const T& value) { start = allocate_and_fill(n, value); finish = start + n; end_of_storage = finish; } public: // 迭代器起始位置 iterator begin() { return start; } // 迭代器结束位置 iterator end() { return finish; } // 容器大小,即真实的数据个数 size_type size() const { return size_type(end() - begin()); } // 容器容量,即申请的内存大小 size_type capacity() const { return size_type(end_of_storage - begin()); } // 容器是否为空 bool empty() const { return begin() == end(); } // 重载[]运算符,取出对应position的数据,下标从0开始 reference operator[](size_type n) { return *(begin() + n); } // 构造函数 vector() : start(0), finish(0), end_of_storage(0) {} vector(size_type n, const T& value) { fill_initialize(n, value); } vector(int n, const T& value) { fill_initialize(n, value); } vector(long n, const T& value) { fill_initialize(n, value); } explicit vector(size_type n) { fill_initialize(n, T()); } // 析构函数 ~vector() { destroy(start, finish); deallocate(); } // 取出起始数据 reference front() { return *begin(); } // 取出末尾数据 reference back() { return *(end() - 1); } // 从尾部插入一个数据 void push_back(const T& x) { if (finish != end_of_storage) { // 内存没有满,直接插入 construct(finish, x); ++finish; } else { // 内存满了,需要扩容内存,然后插入数据 insert_aux(end(), x); } } // 弹出最后一个数据 void pop_back() { --finish; destroy(finish); } // 删除时,将后面的数据覆盖前面的数据,然后释放最后一个数据;如果删除的数据是最后一个数据,那么直接 // 释放即可 iterator erase(iterator position) { if (position + 1 != end()) // 将position + 1到finish的数据,拷贝到position开始的地方 copy(position + 1, finish, position); --finish; destroy(finish); return position; } // 修改vector的大小,新的size比老的size小,直接删除多余的数据;新的size比老的size大,直接插入 void resize(size_type new_size, const T& x) { if (new_size < size()) erase(begin() + new_size, end()); else insert(end(), new_size - size(), x); } // 外部统一调用接口,一层封装 void resize(size_type new_size) { resize(new_size, T()); } // 删除容器中所有数据,不会释放内存 void clear() { erase(begin(), end()); } protected: // 申请并初始化一块内存 iterator allocate_and_fill(size_type n, const T& x) { iterator result = data_allocator::allocate(n); uninitialized_fill_n(result, n, x); return result; } }
vector迭代器
vector的迭代器是普通指针,支持随机存取,提供的是Random Access Iterators。只有random access iterator在遍历数组时才支持first<last操作。
template<class T, class Alloc = alloc> class vector{ public: typedef T value_type; typedef value_type* iterator;//vector的迭代器是普通指针 ... };
vector的数据结构
vector采用的数据结构是线性连续空间。以两个迭代器start和finish分别指向配置得来的连续空间中目前已被使用的范围,并以迭代器end_of_storage指向整块连续空间(含备用空间)的尾端。
template<class T,class Alloc = alloc> class vector{ ... protected : iterator start ; //表示目前使用空间的头 iterator finish ; // 表示目前使用空间的尾 iterator end_of_storage ; //表示目前可用空间的尾 };
一个vector的容量永远大于或等于其大小,当容量等于大小时,再增加新元素,便要进行重新配置,移动数据和释放原空间3个过程。
vector的内存构造与管理
vector内存构造
//vector提供的许多constructor vector() : start(0), finish(0), end_of_storage(0) {} vector(size_type n, const T& value) { fill_initialize(n, value); } vector(int n, const T& value) { fill_initialize(n, value); } vector(long n, const T& value) { fill_initialize(n, value); } explicit vector(size_type n) { fill_initialize(n, T()); } .... //填充并予以初始化 void fill_initialize(size_type n, const T& value) { start = allocate_and_fill(n, value); finish = start + n; end_of_storage = finish; } .... //配置而后填充 iterator allocate_and_fill(size_type n, const T& x) { iterator result = data_allocator::allocate(n); __STL_TRY { uninitialized_fill_n(result, n, x); return result; } __STL_UNWIND(data_allocator::deallocate(result, n)); } //uninitialized_fill_n会根据第一参数的型别来决定算法使用fill_n()或者反复调用construct()来完成任务
用push_back插入元素到尾端时,该函数检查是否还有备用空间,如果有备用空间就在备用空间上构造元素,并调整迭代器finish,使vector变大,如果没有就扩充空间。以原大小的两倍扩充空间,然后将原内容拷贝过来,释放原空间。所以对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器都失效。
void push_back() { if (finish != end_of_storage) {//还有备用空间 construct(finish); ++finish; //调整迭代器finish } else//没有备用空间 insert_aux(end(), x); } template<class T, class Alloc> void vector<T, Alloc>::insert_aux(iterator position, const T&x){ if (finish != end_of_storage){//还有备用空间 construct(finish, *(finish - 1)); //在备用空间起始处构造一个元素,以vector最后一个元素值为其初值 ++finish; //调整finish迭代器 T x_copy = x; copy_backward(position, finish - 2, finish - 1); *position = x_copy; } else{//没有备用空间 const size_type old_size = size(); const size_type new_size = old_size != 0 ? 2 * old_size : 1; iterator new_start = data_allocator::allocate(new_size); iterator new_finish = new_start; try{ new_finish = uninitialized_copy(start, position, new_start);//将原vector的内容拷贝到新vector construct(new_finish, x); ++new_finish; new_finish = uninitialzed_copy(position, finish, new_finish);//将安插点的原内容也拷贝过来 } catch (excetion e){ destroy(new_start, new_finish);//如果发生异常,析构移动的元素,释放新空间 data_allocator::deallocate(new_start, new_size); }//析构并释放原空间 destroy(begin(), end()); deallocator(); start = new_start; //调整迭代器 finish = new_finish; end_of_storage = new_start + new_size;//调整迭代器 } }
vector元素操作:pop_back,erase,clear,insert
pop_back:finish前移放弃尾端元素,然后析构。
void pop_back(){ --finish; destory(finish); }
erase:从position到finish中的元素向前移动,然后删除finish处元素。
iterator erase(iterator first,iterator last){//清除区间[first,last)的元素 iterator i=copy(last,finish,first); destroy(i,finish); finish=finish-(last-first); return first; } iterator erase(iterator position){ //清除某个位置上的元素 if(position +1!=end()) copy(position+1,finish,position); --finish; destroy(finish); return position; }
clear:
void clear() { erase(begin(), end()); }
insert:插入完成后,新节点将位于哨兵迭代器(position)所指节点的前方
template <class T, class Alloc> void vector<T, Alloc>::insert(iterator position, size_type n, const T& x) { if (n != 0) { //当 n != 0 才进行一下操作 if (size_type(end_of_storage - finish) >= n) { //备用空间大于新增元素个数 T x_copy = x; //计算插入点之后的现有元素个数 const size_type elems_after = finish - position; iterator old_finish = finish; if (elems_after > n) { //插入点之后的现有元素个数大于新增元素个数 uninitialized_copy(finish - n, finish, finish); finish += n; copy_backward(position, old_finish - n, old_finish); fill(position, position + n, x_copy); } else { //插入点之后的现有元素个数小于等于新增元素个数 uninitialized_fill_n(finish, n - elems_after, x_copy); finish += n - elems_after; uninitialized_copy(position, old_finish, finish); finish += elems_after; fill(position, old_finish, x_copy); } } else { //备用空间小于新增元素个数(那就必须配置额外的内存) //首先决定新长度:旧长度的两倍,或旧长度 + 新增元素个数 const size_type old_size = size(); const size_type len = old_size + max(old_size, n); //以下配置新的vector空间 iterator new_start = data_allocator::allocate(len); iterator new_finish = new_start; __STL_TRY { //旧vector插入点之前的元素复制到新空间 new_finish = uninitialized_copy(start, position, new_start); //新增元素填入新空间 new_finish = uninitialized_fill_n(new_finish, n, x); //旧vector插入点之后的元素复制到新空间 new_finish = uninitialized_copy(position, finish, new_finish); } catch(...) { //如果发生异常,实现commit和rollback操作 destroy(new_start, new_finish); data_allocator::deallocate(new_start, len); throw; } //以下清除并释放旧的vector destroy(start, finish); deallocate(); //以下调整水位 start = new_start; finish = new_finish; end_of_storage = new_start + len; } } }
list
选用list还是vector要看元素构造函数的复杂度和元素的存取特性。
template <class T> struct __list_node { typedef void* void_pointer; void_pointer next; void_pointer prev; T data; };
list的迭代器
由于STL list是一个双向链表,迭代器必须具备前移、后移的能力,所以list提供的是Bidirectional Iterators (双向迭代器),在vector中,对其进行插入操作有可能会使其满载并且溢出,这样vector便需要重新配置空间,这样原有的迭代器会全部失效。而list在进行插入和接合操作都不会造成原有的list迭代器失效,即便是其删除操作,也只有“指向被删除元素”的迭代器失效,其他迭代器不受影响。
template<class T,class Ref,class Ptr> struct _list_iterator{ typedef _list_iterator<T,T&,T*> iterator; typedef _list_iterator<T,T&,T*> iterator; typedef bidirectional_iterator_tag iterator_category; typedef T value_type; typedef Ptr pointer; typedef Ref reference; typedef _list_node<T>* link_type; typedef size_t size_type; typedef ptrdiff_t difference_type; link_type node; _list_iterator(link_type x):node(x){} _list_iterator(){} _list_iterator(const iterator& x):node(x.node){} bool operator==(const self& x) const {return node==x.node;} bool operator!=(const self& x) const {return node!=x.node;} reference operator*() const {return (*node).data;} reference operator->() const {return &(operator*());} self& operator++(){ node=(link_type)((*node).next); return *this; } self operator++(int){ self tmp=*this; ++*this; return tmp; } self& operator--(){ node=(link_type)((*node).prev); return *this; } self operator--(int){ self tmp=*this; --*this; return tmp; } }
list的数据结构
list是一个环状的双向链表。
template<class T,class Alloc = alloc> //缺省使用alloc为配置器 class list{ protected : typedef __list_node<T> list_node ; public : typedef list_node* link_type ; protected : link_type node ; //只要一个指针,便可以表示整个环状双向链表 ... };
如果让指针node指向刻意置于尾端的一个空白节点,node就能符合stl对于前闭后开区间的要求,成为last迭代器。这样以下函数便能轻易完成
iterator begin() { return (link_type)((*node).next); } iterator end() { return node; } bool empty() const { return node->next == node; } size_type size() const { size_type result = 0; distance(begin(), end(), result);//SGI里面的distance函数作用就是遍历链表,十分影响性能 return result; } reference front() { return *begin(); } reference back() { return *(--end()); }
list的构造与内存管理:constructor,push_back,insert
缺省参数构造函数
//默认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); } ... }
insert是一个重载函数,同样有很多种形式,其中最简单如下,首先配置并构造一个节点,然后在尾端进行适当指针操作插入新节点(push_back和push_front插入新元素时都会调用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; }
插入前所有的迭代器在插入后仍然有效。
list元素的操作
由于list是一个双向链表,所以在头部插入和在尾部插入操作几乎是一样的。
list内部提供一个迁移操作:将某连续范围的元素迁移到某个特定位置之前,他所接受的first和last可以在同一个list中。
//将某连续范围的元素迁移到某个特定位置之前。技术上讲很简单,节点直接的指针移动而已。 void transfer(iterator position, iterator first, iterator last) { if (position != last) { (*(link_type((*last.node).prev))).next = position.node; //1 (*(link_type((*first.node).prev))).next = last.node; //2 (*(link_type((*position.node).prev))).next = first.node; //3 link_type tmp = link_type((*position.node).prev); //4 (*position.node).prev = (*last.node).prev; //5 (*last.node).prev = (*first.node).prev; //6 (*first.node).prev = tmp; //7 } }
deque
deque是一种双向开口的连续线性空间,可以在头尾两端分别做元素插入和删除操作。它的迭代器也是Random Access Iterator。它与vector的差别是:
- deque没有“容量”(capacity)的观念。
- vector也可以在头部操作但是效率太差,而deque在常数时间对头部进行操作。
deque的中控器
deque是由一段段的定量连续空间组成。一旦需要在deque的前端或者尾端增加新空间,便配置一段定量连续空间,串接在整个deque的头端或者尾端。
deque采用一块所谓的map(不是stl的map容器)作为主控。这里的map是一块连续空间,其中每个元素都是一个指针,指向另一段较大的线性连续空间,这段空间称为缓冲区。缓冲区是deque存储空间的主体。sgi stl允许指定缓冲区的大小,默认值0表示使用512bytes缓冲区。
template <class T,class Alloc=alloc,size_t BufSiz=0> class deque{ public: typedef T value_type; typedef value_type* pointer; ... protected: typedef pointer* map_pointer; protected: map_pointer map; //指向map,map是连续空间,其内的每个元素都是一个指针,指向一块缓冲区 size_type map_size; //map可容纳多少指针 ... };
map是一个T**
deque的迭代器
deque是分段的连续空间,维持其整体连续假象的任务,落在了迭代器身上。它应该具有以下结构:
- 指出缓冲区的位置。
- 判断是否处于缓冲区边缘,如果是,一旦前进或者后退必须能够跳跃到下一个或上一个缓冲区。
template <class T, class Ref, class Ptr, size_t BufSiz> struct __deque_iterator { // 基本型别的定义 typedef __deque_iterator<T, T&, T*, BufSiz> iterator; typedef random_access_iterator_tag iterator_category; typedef T value_type; typedef Ptr pointer; typedef Ref reference; typedef size_t size_type; typedef ptrdiff_t difference_type; typedef T** map_pointer; typedef __deque_iterator self; // 缓冲区的大小 tatic size_t buffer_size() { ... } // 保持与容器的连接 T* cur; // 指向当前缓冲区的现行元素 T* first; // 指向当前缓冲区的头 T* last; // 指向当前缓冲区的尾 map_pointer node; // 指向管控重心map // ... };
buff_size中如果n不为0,表示用户自定义buff_size否则使用默认值。
inline size_t __deque_buf_size(size_t n,size_t sz)//se代表元素大小sizeof { return n!=0?n:(sz<512?size_t(512/sz):size_t(1)); }
//用于跳转缓冲区 void set_node(map_pointer new_node) { node = new_node; first = *new_node; last = first + difference_type(buffer_size()); } // 迭代器之间的距离(相隔多少个元素) difference_type operator-(const self& x) const { return difference_type(buffer_size()) * (node - x.node - 1) + (cur - first) + (x.last - x.cur); } reference operator*() const { return *cur; } pointer operator->() const { return &(operator*()); } self& operator++() { ++cur; if (cur == last) { // 到达缓冲区尾端 set_node(node + 1); cur = first; } return *this; } self& operator++(int){ self tmp = *this; ++*this; return tmp; } self& operator--() { if (cur == first) { // 已到达缓冲区头端 set_node(node - 1); cur = last; } --cur; return *this; } self& operator--(int){ self tmp = *this; --*this; return tmp; } self& operator+=(difference_type n) {//支持ite+=n difference_type offset = n + (cur - first); if (offset >= 0 && offset < difference_type(buffer_size())) cur += n;//如果还在当前节点,直接加 else {//否则跳到下个节点 difference_type node_offset = offset > 0 ? offset / difference_type(buffer_size()) : -difference_type((-offset - 1) / buffer_size()) - 1; set_node(node + node_offset); cur = first + (offset - node_offset * difference_type(buffer_size())); } return *this;//返回当前对象引用 } self operator+(difference_type n) const {//重载const重载+号。 self tmp = *this; return tmp += n; } self& operator-=(difference_type n) { return *this += -n; }//ite -=n通过+ -n实现。 self operator-(difference_type n) const {//重载- self tmp = *this; return tmp -= n; } //实现随机存储,迭代器调用operator* 和 operator+ reference operator[](difference_type n) const { return *(*this + n); }//重载ite[]操作,通过+实现 bool operator==(const self& x) const { return cur == x.cur; }//重载ite1 == ite2 bool operator!=(const self& x) const { return !(*this == x); }//重载ite1 != ite2 bool operator<(const self& x) const { return (node == x.node) ? (cur < x.cur) : (node < x.node); }
deque的数据结构
deque除了维护一个指向map的指针,也维护start和finish两个迭代器,分别指向第一缓冲区的第一个元素和最后一个缓冲区最后一个元素的下一个位置。此外它也需要记住map的大小,一旦map提供的节点不足,急需要重新配置一块更大的map。
template<class T, class Alloc = alloc, size_t BufSiz = 0> class deque{ public : typedef T value_type ; typedef value_type* pointer ; typedef size_t size_type ; public : typedef __deque_iterator<T,T&,T*,BufSiz> iterator ; protected : //元素的指针的指针(pointer of pointer of T) typedef pointer* map_pointer ; protected: iterator start ; //表现第一节点 iterator finish ; //表现最后一个节点 map_pointer map ; //指向map,map是块连续空间,其每个元素都是个指针,指向一个节点(缓冲区) size_type map_size ; //map内有多少指针 ... } ;
deque的构造与内存管理:ctor,push_back,push_front
我们可以自定义缓冲区大小
deque<int,alloc,32> d;
一个map所需管理的结点数是:最少8个最多是所需结点数+2(前后预留一个扩充时可用)
在尾端新增元素时缓冲区尾端只剩一个备用空间可用时,会先配置一块缓冲区再设置新元素的内容,然后更改迭代器finish。
在头部插入元素时,如果第一个缓冲区没有任何备用空间,会调用push_front_aux分配新缓冲区。
如果map尾端结点和前端结点不足时需要重新整治map分配其一个更大的空间,把原map的内容拷贝到新map中。
deque元素的操作:pop_back,pop_front,clear,erase,insert
在pop_back和pop_front时,如果前或尾端缓冲区只有一个元素,则会把这个缓冲区释放掉,更改start或finish的值。
clear时会清除整个deque,只保留一个缓冲区。
stack
缺省情况下stack底层由deque实现,不过也可以用list来实现。
template<class T, class Sequence = deque<T> > class stack{ friend bool operator== __STL_NULL_TMPL_ARGS(const stack& , const stack&) ; friend bool operator< __STL_NULL_TMPL_ARGS(const stack& , const stack&) ; 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 ; //底层容器 public : //以下完全利用Sequence c 的操作,完成stack的操作 bool empty() const {return c.empty() ;} size_type size() {return c.size();} reference top() {return c.back();} const_reference top() const {return c.back();} //deque是两头可进出,stack是末端进,末端出。 void push(const value_type& x) {c.push_back(x) ;} void pop() {c.pop_back() ;} } ;
用list实现
stack<int, list<int>> istack;
queue
SGI STL以deque作为缺省情况下的queue底部结构。
template<class T, class Sequence = deque<T> > class 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 ; //底层容器 public : //以下完全利用Sequence c的操作,完成queue的操作 bool empty() const {return c.empty();} size_type size() const {return c.size();} reference front() const {return c.front();} const_reference front() const {return c.front();} //deque是两头可进出,queue是末端进,前端出。 void push(const value_type &x) {c.push_back(x) ;} void pop() {c.pop_front();} } ;
但是其也可以用list来实现
queue<int, list<int>> iqueue;
heap
heap并不属于STL容器,但它是其中一个容器priority queue必不可少的一部分。priority queue就是优先级队列,允许用户以任何次序将任何元素加入容器内,但取出时是从优先权最高的元素开始取。而优先权有两种,可以是容器内数值最低的,也可以是数值最高的。而priority queue是选择了数值高作为评判优先级的标准。对应实现方法就是binary max heap,其作为priority queue的底层机制。
STL所提供的为大根堆。但是heap的每个算法的接口都提供自定义比较大小操作。
heap的所有元素都必须遵循特别的(complete binary tree)排列规则,所以heap不提供遍历功能,也不提供迭代器
完全二叉树整棵树内没有任何节点漏洞,这使得我们可以使用数组来存储一棵完全二叉树上的所有结点。另外,如果我们数组的索引1开始记录节点,那么父节点与子节点在数组中的关系就一般为:父节点在 i 处,其左子节点必位于数组的 2i 处,其右子节点必位于数组的 2i+1 处。这种以数组表示树的方式,称为隐式表述法。我们的heap需要动态改变节点数,所以用vector是更好的选择。由于priority queue选择的是binary max heap做为自己的底层机制,所以也只提供了max heap的实现,所以我们接下里讨论的都是大根堆(max heap)。每个节点的键值都大于或等于其子节点的键值。
push_heap算法
为了满足完全二叉树的条件,最新加入的元素一定要放在最下一层做为叶子节点,并填补在从左至右的第一个空格(vector的end处)。新加入的元素并不一定适合于现有的位置,为了满足max-heap的条件,我们需要对刚加入的元素进行一个上浮(percolate up)的操作:将新节点与其父节点比较,如果其键值比父节点的大,就父子交换位置,交换后新加的元素成为了父节点,此时再与它的父节点比较,如此一直上溯,直到不需对换或直到根节点为止。
push_heap函数接受两个随机迭代器,用来表示一个heap底部容器(vector)的头尾,并且新元素已经插入到底部容器的最尾端,才会进入到该函数。如果参数不符合,或并无新元素插入,该函数的执行结果不可预期。
template <class RandomAccessIterator> inline void push_heap(RandomAccessIterator first, RandomAccessIterator last) { // 注意,此函数被调用时,新元素应已置于底层容器的最尾端。 //即是新元素被加入到数组(原本最后元素的下一位置)后,才调用该函数 __push_heap_aux(first, last, distance_type(first), value_type(first)); } 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))); //于上个函数获取到的迭代器所指对象类型T,用于强制转换 //Distance(0)为指出最大值的索引值 //Distance((last - first) - 1)为指出新添值的索引值 } template <class RandomAccessIterator, class Distance, class T> void __push_heap(RandomAccessIterator first, Distance holeIndex, Distance topIndex, T value) { //holeIndex为指出新添值的索引值 //topIndex最大值的索引值 //value为新添值内容 Distance parent = (holeIndex - 1) / 2; // 找出新元素的父节点 while (holeIndex > topIndex && *(first + parent) < value) { // 当尚未到达顶端,且父节点小于新值(不符合 heap 的次序特性),继续上浮 // 由于以上使用 operator<,可知 STL heap 是一种 max-heap(大根堆)。 *(first + holeIndex) = *(first + parent); // 子位置设父值 holeIndex = parent; // percolate up:調整新添值的索引值,向上提升至父节点。 parent = (holeIndex - 1) / 2; // 获取新索引值的父节点 } // 持续到顶端,或满足 heap 的次序特性为止。 *(first + holeIndex) = value; // 令最后的索引值为新值,完成插入。 }
pop_heap
最大值在根节点处,pop操作取走根节点(其实是把它转移到vector的尾端节点上),为了满足完全二叉树的条件,必须割舍最底层最右边的节点,把其值拿出来放至一临时变量里,然后该位置放根节点的值(最后会被pop_back()给移除)。
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*) { __pop_heap(first, last - 1, last - 1, T(*(last - 1)), distance_type(first)); // 设定准条调整的值为尾值,然后将首值调至 // 尾节点(所以以上将迭代器 result 设为 last-1)。然后重整 [first, last-1), // 使之重新成一個合格的 heap。 } template <class RandomAccessIterator, class T, class Distance> inline void __pop_heap(RandomAccessIterator first, RandomAccessIterator last, RandomAccessIterator result, T value, Distance*) { *result = *first; // 设定尾值为首值,于是尾值即为要求的结果, // 可由客端稍后再调用 pop_back() 取出尾值。 __adjust_heap(first, Distance(0), Distance(last - first), value); // 以上重新调整 heap,要调整的索引值为 0(亦即树根处),欲调整值为 value(原尾值)。 } template <class RandomAccessIterator, class Distance, class T> void __adjust_heap(RandomAccessIterator first, Distance holeIndex, Distance len, T value) { //holeIndex:要调整的索引值,从树根处出发 //len:所有元素个数(不包括被调整到尾节点的首节点),即[0, len) //value:记录了原最底层最右边的节点的值 Distance topIndex = holeIndex; Distance secondChild = 2 * holeIndex + 2; // 调整的索引值的右子节点 while (secondChild < len) { // 比较这左右两个子值,然后以 secondChild 代表较大子节点。 if (*(first + secondChild) < *(first + (secondChild - 1))) secondChild--; // Percolate down:令较大子值代替要调整索引值处的值,再令调整的索引值下移至较大子节点处。 *(first + holeIndex) = *(first + secondChild); holeIndex = secondChild; // 继续找出要调整的索引值的右子节点 secondChild = 2 * (secondChild + 1); } if (secondChild == len) { // 相等,说明沒有右子节点(不是说没有,而是被准备pop掉的元素占用了,见*result = *first;),只有左子节点 // Percolate down:令左子值代替要调整索引值处的值,再令要调整的索引值下移至左子节点处。 *(first + holeIndex) = *(first + (secondChild - 1)); holeIndex = secondChild - 1; } // 此时可能尚未满足次序特性,再执行一次上浮操作 __push_heap(first, holeIndex, topIndex, value); }
注意,pop_heap之后,最大元素只是被放置于底部容器的最尾端,尚未被取走。如果要取其值,可使用底部容器的back()函数。如果要移除它,可使用底部容器所提供的pop_back()函数。
sort_heap
既然每次调用pop_heap可获得heap中键值最大的元素,如果持续对整个heap做pop_heap操作,且每次的操作范围都从后向前缩减一个元素,那么当整个程序执行完毕,我们便有了一个递增序列。sort_heap便是如此做的。该函数接受两个迭代器,用来表示heap的首尾。如果并非首尾,该函数的执行结果不可预期。注意,排序过后,底层容器里的就不再是一个合法的heap了。
template <class RandomAccessIterator> void sort_heap(RandomAccessIterator first, RandomAccessIterator last) { // 以下,每执行一次 pop_heap(),极大值即被放在尾端。 // 尾端自减后(往左移动一格)再执行一次 pop_heap(),次极值又被放在新尾端。一直下去,最后即得 // 排序结果。 while (last - first > 1) pop_heap(first, last--); // 每执行 pop_heap() 一次,操作范围即退缩一格。 }
make_heap
// 将 [first,last) 排列为一个 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*) { if (last - first < 2) return; // 如果長度為 0 或 1,不必重新排列。 Distance len = last - first; // 找出第一个需要重排的子树头部,以 parent 标示出。由于任何叶子节点都不需执行 // perlocate down(下沉),所以有以下计算。 Distance parent = (len - 2) / 2; while (true) { // 重排以 parent 为首的子树。len 是为了让 __adjust_heap() 判断操作范围 __adjust_heap(first, parent, len, T(*(first + parent))); if (parent == 0) return; // 直至根节点,就结束。 parent--; // 未到根节点,就将(即将重排的子树的)索引值向前一個节点 } }
is_heap
//版本一:用operator <比较元素 template <class RandomAccessIterator> void is_heap(RandomAccessIterator first,RandomAccessIterator last); //版本二:用自定义的function object比较元素 template <class RandomAccessIterator,class StrictWeakOrdering> void is_heap(RandomAccessIterator first,RandomAccessIterator last,StrictWeakOrdering cmp);
priority_queue
priority_queue带有权值观念,其内的元素并非依照被推入的顺序排列,而是自动依照元素的权值排列(通常权值以实值标识)。权值最高者,排在最前面。缺省情况下priority_queue系利用一个max-heap完成,后者是一个以vector表现的complete binary tree。max-heap可以满足priority_queue所需要的“依权值高低自动递增排序”的特性。
priority_queue没有迭代器。
priority_queue缺省情况下是以vector为底层容器,再加上heap处理规则,STL priority_queue往往不被归类为container(容器),而被归类为containeradapter。
#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()); } };
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() 供灵活运用。
slist的结点
//单向链表的节点结构 struct _Slist_node_base { _Slist_node_base* next; }; //使用继承来实现单链表的节点结构:指针+数据 template <class _T> struct _Slist_node : public _Slist_node_base { T data; }; //全局函数:单链表节点数,其实就是简单的遍历计数 inline size_t __slist_size(_Slist_node_base* __node) { size_t __result = 0;//不等于0和不等于NULL一样 for ( ; __node != 0; __node = __node->next) ++__result; return __result; } //全局函数:已知某一节点,插入新节点于其后 //返回插入节点之后的指针。 inline _Slist_node_base* __slist_make_link(_Slist_node_base* __prev_node,_Slist_node_base* __new_node) {//让新节点的下一个节点为prev节点的下一个节点 __new_node->next = __prev_node->next; //让prev指向新节点。 __prev_node->next = __new_node; return __new_node; }
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 _Tp, 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; } //这里没有--的重载,因为Forward Iterator的特性不支持双向操作 };
因为迭代器并未重载==,所以判断==是调用的基类的==,相当于比较_slist_node_base*node是否相等,两个slist迭代器是否相同,视其__Slist_iterator_base* node是否相等。
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(){clear() ;} public : iterator begin() {return iterator((list_node*)head.next) ;} iterator end() {return iteator(0) ;} iterator size() {const __slist_size(head.next) ;} bool empty() const {return head.next == 0 ;} //两个slist互换:只要将head交换互指即可 void swap(slist &L) { list_node_base* tmp = head.next; head.next = L.head.next ; L.head.next = tmp ; } public : //取头部元素 reference front() {return ((list_node*)head.next)->data ;} //从头部插入元素(新元素成为slist的第一个元素) void push_front(const value_type& x) { __slist_make_link(&head,create_node(x)) ; } //注意,没有push_back() ,因为考虑到效率。 //从头部取走元素(删除之)。修改head void pop_front() { list_node* node = (list_node*)head.next ; head.next = node->next ; destroy_node(node); } ..... };
当我们遍历slist时都以slist::end()来表示循环结束。
iterator end() { return iterator(0); } //因为源码中如下定义 typedef __slist_iterator<T,T&,T*> iterator; //会形成如下结果 __slist_iterator<T,T&,T*>(0); //从而因为源码中如下定义 __slist_iterator(list_node* x):__slist_iterator_base(x){} //导致基类构造函数 __slist_iterator_base(0); //并因为源码如下定义 struct __slist_iterator_base { __slist_iterator_base* node; __slist_iterator_base(__slist_iterator_base* x):node(x){} }; //导致 node(x);
上图皆以下图左侧表示表现end(),它和右侧表示方式的迭代器不同。