您的位置:首页 > 其它

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

2016-05-07 12:10 495 查看

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 algorithms
using 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++操作。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: