前言
注意区别,这里的栈与内存管理中的栈不是同一个概念,关于堆栈的概念请参考其他资料,本文介绍数据结构层面上的栈。
栈
栈是一种线性数据结构,存储以及查找数据时只能访问栈的一端(这是与另外一种线性结构数组的明显区别之一),栈类似于餐厅中的一叠盘子,最后放在顶端,却可以最先被取出,因此栈被称为LIFO(last in/first out)。栈是有空间限制的,有盘子的时候才能取出盘子,放满了以后就不能再添加盘子。对于栈有如下常用操作
- clear() ------------清空
- isEmpty() --------是否为空
- push(el) ----------弹出栈顶元素
- pop() --------------弹出栈顶元素
- topEI() ------------获取栈顶元素,但不删除该元素
最基本的操作包括出栈和入栈操作:
操作 | push10 | push5 | pop | push7 | push15 |
---|---|---|---|---|---|
15 | |||||
5 | 7 | 7 | |||
栈底 | 10 | 10 | 10 | 10 | 10 |
一般来说栈适用于数据存储后以相反方式检索的情形。
栈有两种较为常见的实现方式,第一种是可变数组<vector>
,第二种是<list>
。
由于vector中常常需要空间数大于元素数,链表被视为更匹配抽象栈的数据结构。此外由于数列常常需要额外内存而将自身复制,所以最好情况下出栈入栈的复杂度都为O(1),而在最坏情况下为O(n)。
数组实现代码:
#include <vector>
template<class T, int capacity = 30>
class Stack {
public:
Stack() {
pool.(capacity);
} //构造函数,翻转数组顺序。
void clear() {
pool.clear();
} //清空栈。
bool isEmpty() const {
return pool.empty();
}
T& topEl() {
return pool.back();
} //返回栈顶元素。
T pop() {
T el = pool.back();
pool.pop_back();
return el;
} //弹出栈顶元素。
void push(const T& el) {
pool.push_back(el);
} //插入新元素。
private:
vector<T> pool;
};
对于链表的实现方式与之类似且更加简单。
队列
队列是一个简单的队列。在尾部增加元素队列加长,在前端删除元素时队列缩短,栈是后进先出,那么队列是先进先出(first in/first out, FIFO)。
队列的操作如下:
- clear() ------------清空
- isEmpty() --------是否为空
- enqueue(el) -----在队列尾部加入元素el
- dequeue() -------取出队列的第一个元素
- firstEI() ------------返回队列的第一个元素,但不删除
操作 | enqueue10 | enqueue5 | dequeue | enqueue15 | enqueue7 |
---|---|---|---|---|---|
5 | |||||
10 | 5 | 15 | |||
队尾 | 10 | 5 | 5 | 15 | 7 |
与栈相同,队列也可以通过数组还有链表的形式实现。
template<class T, int size = 100>
class ArrayQueue
{
public:
ArrayQueue()
{
first = last = -1;
}
void enqueue(T);
T dequeue();
bool isFull()
{
return first == 0 && last == size - 1 || first == last + 1;
}
bool isEmpty()
{
return first == -1;
}
private:
int first, last;
T storage[size];
}
template <class T, int size>
void ArrayQueue<T,size>::enqueue(T el)
{
if(!isFull())
if(last == size - 1 || last == -1)
{
storage[0] = el;
last = 0;
if(first == -1)
first = 0;
}
else storage[++last] = el;
else cout << "Full queue.\n";
}
template <class T, int size>
T ArrayQueue<T,size>::dequeue()
{
T tmp;
tmp = storage[first];
if(first == last)
last = first = -1;
else if (first == size - 1)
{
first = 0;
}
return first++;
return tmp;
}
以上代码是队列的数组实现形式,链表实现形式同理。
著名的排队论(queuing theory)就是队列用法得一种基本体现,例如一个银行需要处理客户的各种业务,就需要对开通服务窗口的数量进行权衡,数量太多,效率低下,数量太少,超过负荷。通常根据每分钟涌进的客户量以及每个客户所处理的时间长度来决定 开放的工作窗口数量,以对堆积的负荷进行消化。而开放几个窗口(队列)会影响银行工作的效率,这就是排队论思想的具体实现之一。
优先队列
在道路上,警车,消防车,私家车,救护车会有不同的优先级,即等待队列中,P1可能会比P2优先级更高,就算P2排在队列前边,也会优先执行P1。此类情况需要一种修正队列,就是优先队列(priority queue)。
优先队列的关键在于如何实现一种有效的方法,使得出队入队操作更加快速。
STL中的栈和队列
STL中的栈容器不是重新创建的,而是对已有容器做调整。
栈:
默认底层为deque容器,也可以自选链表或向量。
stack<int> stack1; //双端队列
stack<int, vector<int>> stack2; //向量
stack<int, list<int>> stack3; //链表
队列:
默认底层为deque容器,也可以自选以链表的形式实现。
注意vector可以实现,但STL不支持这种用法,会导致编译错误。
queue<int> q1;
queue<int, list<int>> q2;
优先队列:
优先队列(priority queue)默认使用vector容器实现,用户也可以使用deque。
priority_queue<int> pq1; //大元素优先
priority_queue<int, vector<int>, greater<int>> pq2; //小元素优先
int a[]={4,5,6};
priority_queue<int> pq3(a,a+3); //大元素优先,且用[a,a+3)中的元素填充
双端队列:
双端队列(double-ended queue)是允许在两端访问的线性表。因此可以使用双向链表实现。该链表具有数据成员head以及tail。因此这种结构的成员函数与链表的基本相似,只有少许不同。
deque<int> dq1;
有趣的是STL并没有使用双向链表,而是使用指针数组,指向块或者数据数组。块的数量随需求动态变化,指针数组的大小也随之变化。
该结构由指针数组,指向块或者数据数组,可以e1为起始点,往上往下均可添加新元素,同理可以同时取出head和tail节点的数据。如果一个小块向上或向下溢出,就分配新的结构。在内存上,块间不一定连续,但在map上,它们是连续的。与之前的队列结构不同的是,双端队列重载了[ ]运算符,这意味着可以向vector一样直接访问其中的某一个元素。