《STL源码剖析》学习笔记-第4章 序列式容器(二)

来源:互联网 发布:微博系统源码 编辑:程序博客网 时间:2024/05/17 04:29

1、stack

stack是一种先进后出的数据结构,只有一个出口。允许新增元素、移除元素、取得最顶端元素。不允许有遍历行为。

在SGI STL的源码<stl_stack.h>的设计中,它是基于某种容器作为底部结构的,默认容器是deque容器,用户也可以自己指定容器的类型。

stack不提供走访功能,也不提供迭代器。

stack源码如下:

template <class T, class Sequence = deque<T> >class stack {    // 以下的__STL_NULL_TMPL_ARGS 會開展為<>,見1.9.1 節    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() const { 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(); }};template <class T, class Sequence>bool operator==(const stack<T, Sequence>& x, const stack<T, Sequence>& y){    return x.c == y.c;}template <class T, class Sequence>bool operator<(const stack<T, Sequence>& x, const stack<T, Sequence>& y){    return x.c < y.c;}

除了deque外,list也是双向开口的数据结构。若以list作为底部结构并封闭其头端开口,也能轻易形成一个stack。下面是做法示范:

#include <stack>#include <list>#include <iostream>#include <algorithm>using namespace std;int main(){    stack<int,list<int>> istack;    istack.push(1);    istack.push(3);    istack.push(5);    istack.push(7);    cout << istack.size() << endl;  // 4    cout << istack.top() << endl;  // 7    istack.pop(); cout << istack.top() << endl;// 5    istack.pop(); cout << istack.top() << endl;// 3    istack.pop(); cout << istack.top() << endl;// 1    cout << istack.size() << endl;  // 1    return 0;}

2、queue

queue是一种先进先出的数据结构。有两个出口,允许新增、移除元素、从最底端加入、取得最顶端元素。不允许遍历行为。

deque是双向队列,而queue是单向队列。

deque是双向开口的数据结构,若以deque为底部结构并封闭其底端出口和前端入口,便轻而易举的形成了一个queue。因此,SGI STL以deque作为缺省情况下的queue底部结构

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

除了deque外,list也是双向开口的数据结构。若以list作为底部结构并封闭其某些接口,也能轻易形成一个queue。下面是做法示范:

#include <queue>#include <list>#include <iostream>#include <algorithm>using namespace std;int main(){    queue<int,list<int>> iqueue;    iqueue.push(1);    iqueue.push(3);    iqueue.push(5);    iqueue.push(7);    cout << iqueue.size() << endl;  // 4    cout << iqueue.front() << endl;  // 1    iqueue.pop(); cout<<iqueue.front()<<endl;// 3    iqueue.pop(); cout<<iqueue.front()<<endl;// 5    iqueue.pop(); cout<<iqueue.front()<< endl;// 7    cout << iqueue.size() << endl;  // 1    return 0;}

3、优先队列(基于heap)

STL中优先队列priority_queue是基于堆实现的。所谓优先队列即元素具有优先级的队列,在最大优先级队列中,队列最前面的元素具有最高的优先级(数值最高),最大优先级队列基于最大堆(max-heap)实现,适合作为其底层机制。STL供应的是最大堆。

priority_queue是一个拥有权值观念的queue。只允许在底部加入新元素,顶部取出元素,除此之外别无他法。priority_queue中的元素自动依照元素的权值排列,最高者排前面。

缺省情况下利用一个最大堆完成,后者是一个以vector表现的完全二叉树。最大堆可满足priority_queue需要的”依照权值高低自动递减排序”的特性。

1、堆的介绍

二叉堆是一颗完全二叉树,整棵二叉树除了最底层的叶节点之外是填满的,而最底层的叶节点从左至右不得有空隙。

(1)max_heapify 最大堆性质的维护:时间复杂度为O(lgN)

void maxheapify(int a[], int i, int heapsize){    int l = (i<<1);//左孩子    int r = (i<<1) + 1;//右孩子    int largest = i;    if (l <= heapsize && a[l] > a[largest])    {        largest = l;    }    if (r <= heapsize && a[r] > a[largest])    {        largest = r;    }    if (largest != i)    {        swap(a[largest], a[i]);        maxheapify(a, largest, heapsize);    }}

(2)建堆:时间复杂度O(N)

我们可以从后往前扫描数组,对每一个节点都进行maxheapify操作,这样就建立了一个堆。但是对于叶子节点而言,调用maxheapify操作是没有意义的对于拥有n个节点的堆而言,其叶子节点的下标为[n/2]+1, [n/2]+2, …, n。因此,我们可以从n/2开始往前进行maxheapify操作。

void build_heap(int a[], int n){    for (int i = n/2; i >= 1; --i)    {        max_heapify(a, i, n);    }}

(3)堆排序

建好一个堆后,堆排序就比较简单了。每次把第一个节点和最后一个节点的值交换,然后对第一个节点调用maxheapify操作,直到堆的元素个数减小到1。堆排序的时间复杂度为O(NlgN),因为maxheapify中,前面两个if语句(也就是从左右子节点取得最大值节点)的顺序是可以随意安排的,所以堆排序不是稳定排序。

void heap_sort(int a[], int n){    build_heap(a, n);    for (int i = n; i >= 2; --i)    {        swap(a[1], a[i]);        max_heapify(a, 1, i-1);    }}

2、STL heap算法

1)push_heap:将新节点加入到堆的尾端,并调整为新堆。为满足最大堆的条件,执行上溯过程:将新节点与父节点比较,如果键值key比父节点大,就父子对换位置。如此一直上溯,直到不需对换或直到根节点为止。执行该操作之前,要确保新元素已经加入到底部容器(vector)的最尾端。(2)pop_heap:取走根节点,并调整为新堆(此处的取走,只是从堆中取走,并放置在底部容器尾端)。为满足最大堆的条件,执行下溯过程:将空间节点和其较大子节点对调,并持续下放,直至叶节点为止。执行过程即是:将最下层最右边的叶节点拿出,先将其放置在根节点位置,然后不断执行下溯,直至满足最大堆。注:执行pop_heap之后,最大元素只是被放置在底部容器最尾端,尚未被取走。要取其值可使用底部容器(vector)提供的back()函数。要移除它可使用底部容器(vector)提供的pop_back()函数。(3)sort_heap:该算法不断对heap进行pop操作,达到排序的效果。排序过后,原来的堆就不是一个合法的堆了。(4)make_heap:该算法将一段现有数据转化为一个heap。

heap不提供遍历功能,也不提供迭代器。
heap测试实例:

#include <vector>#include <iostream>#include <algorithm> // heap algorithmsusing namespace std;int main(){    //(1)test heap (底層以vector 完成)    int ia1[9] = {0,1,2,3,4,8,9,3,5};    vector<int> ivec(ia1, ia1+9);    make_heap(ivec.begin(), ivec.end());//转化为堆    for(int i=0; i<ivec.size(); ++i)        cout << ivec[i] << ' ';// 9 5 8 3 4 0 2 3 1    cout << endl;    ivec.push_back(7);    push_heap(ivec.begin(), ivec.end());//加入,并调整为新堆    for(int i=0; i<ivec.size(); ++i)        cout<<ivec[i] << ' ';// 9 7 8 3 5 0 2 3 1 4    cout << endl;    pop_heap(ivec.begin(), ivec.end());//取到根节点,并调整为堆    cout<<ivec.back()<<endl;// 9.return but no remove.    ivec.pop_back();//remove last elem and no return    for(int i=0; i<ivec.size(); ++i)        cout << ivec[i] << ' ';// 8 7 4 3 5 0 2 3 1    cout << endl;    sort_heap(ivec.begin(), ivec.end());    for(int i=0; i<ivec.size(); ++i)        cout << ivec[i] << ' ';// 0 1 2 3 3 4 5 7 8    cout << endl;    //(2)test heap (底层以array 完成)    int ia[9] = {0,1,2,3,4,8,9,3,5};    make_heap(ia, ia+9);    // array 无法动态改变大小,因此不可以对满载的array 做push_heap() 操作。    // 因为那得先在array 尾端增加一个元素。    sort_heap(ia, ia+9);    for(int i=0; i<9; ++i)        cout << ia[i] << ' ';// 0 1 2 3 3 4 5 8 9    cout << endl;    // 经过排序之后的heap,不再是个合法的heap    // 重新再做一个heap    make_heap(ia, ia+9);    pop_heap(ia, ia+9);    cout << ia[8] << endl;  // 9    //(3)test heap (底层以array 完成)    int ia2[6] = {4,1,7,6,2,5};    make_heap(ia2, ia2+6);    for(int i=0; i<6; ++i)        cout << ia2[i] << ' ';// 7 6 5 1 2 4    cout << endl;    return 0;}

3、priority_queue

STL priority_queue往往不被归类为container(容器),而被归类为 container adapter。

priority_queue的所有元素,进出都有一定规则,只有最顶端的元素才有机会被外界取用。priority_queue不提供遍历功能,也不提供迭代器。
priority_queue测试实例:

#include <queue>#include <iostream>#include <algorithm>using namespace std;int main(){    // test priority queue...    int ia[9] = {0,1,2,3,4,8,9,3,5};    priority_queue<int> ipq(ia, ia+9);    cout << "size=" << ipq.size() << endl;  // size=9    for(int i=0; i<ipq.size(); ++i)        cout << ipq.top() << ' ';  // 9 9 9 9 9 9 9 9 9    cout << endl;    while(!ipq.empty())    {        cout << ipq.top() << ' ';  // 9 8 5 4 3 3 2 1 0        ipq.pop();    }    cout << endl;    ipq.push(1);    ipq.push(2);    cout<<ipq.size()<<endl;// 2    return 0;}

4、slist

slist与list的区别:

(1)STL list是双向链表,而slist是单向链表。
(2)STL list的迭代器是双向的Bidirectional Iterator,而slist的迭代器是单向的Forward Iterator。
(3)单向链表所耗用的空间更小,某些操作更快。
(4)两者有一个共同特点:插入(insert)、删除(erase)、接合(splice)等操作不会造成原油迭代器的失效。(指向被移除元素的那个迭代器,在移除操作后肯定会失效)。
(5)因为单链表只能往前迭代,所以很多操作都没有提供,即使提供了,也是非常低效的操作,需要从头结点开始遍历。
除了slist起点处附近的区域之外,在其他位置上采用insert或erase操作函数,都属不智之举,这便是slist相较于list的大缺点。
(6)slist迭代器是单向的Forward Iterator,因此除了迭代器的基本操作之外,只实现了operator++操作。

0 0
原创粉丝点击