目录
栈与队列理论基础
栈和队列的基本概念
栈stack是一种先进后出的数据结构,它只有一个出口,栈中只有顶端的元素才可以被外界使用,因此栈不允许有遍历行为。栈中进入数据称为入栈push,栈中弹出元素称为出栈pop。
队列queue是一种先进先出的数据结构,它有两个出口。队列允许从一端新增元素,从另一端移除元素。队列中只有队头和队尾才可被外界使用,因此队列不允许有遍历行为。队列中进数据被称为入队push,出数据被称为出队pop。
栈和队列常用接口
栈和队列共有的接口:
- push(elem); //向栈顶/队尾添加元素
- pop(); //从栈顶/队首弹出元素
- empty(); //判断堆栈或队列是否为空
- size() //返回栈或队列的大小
在获取数据上有一些差异,堆栈获取栈顶元素使用top(),获取队首第一个元素用front(),而获取队尾第一个元素用back()。
栈和队列底层实现
栈和队列是STL(C++标准库)里面的两个数据结构,STL有三个较为普遍的版本:
- HP STL 其他版本的C++ STL,一般是以HP STL为蓝本实现出来的,HP STL是C++ STL的第一个实现版本,而且开放源代码。
-
P.J.Plauger STL 由P.J.Plauger参照HP STL实现出来的,被Visual C++编译器所采用,不是开源的。
-
SGI STL 由Silicon Graphics Computer Systems公司参照HP STL实现,被Linux的C++编译器GCC所采用,SGI STL是开源软件,源码可读性甚高。
接下来介绍的栈和队列也是SGI STL里面的数据结构。栈是以底层容器完成其所有的工作,对外提供统一的接口,底层容器是可插拔的(也就是说我们可以控制使用哪种容器来实现栈的功能)。所以STL中栈往往不被归类为容器,而被归类为container adapter(容器适配器)。
栈的内部结构,栈的底层实现可以是vector,deque,list 都是可以的, 主要就是数组和链表的底层实现。我们常用的SGI STL,如果没有指定底层实现的话,默认是以deque为缺省情况下栈的底层结构。deque是一个双向队列,只要封住一段,只开通另一端就可以实现栈的逻辑了。SGI STL中 队列底层实现缺省情况下一样使用deque实现的。
队列中先进先出的数据结构,同样不允许有遍历行为,不提供迭代器, SGI STL中队列一样是以deque为缺省情况下的底部结构。所以STL 队列也不被归类为容器,而被归类为container adapter( 容器适配器)。
232.用栈实现队列
前言:
本题通过栈的一系列接口实现队列的功能,考察对栈数据结构原理的理解和接口的掌握。
算法实现:
class MyQueue {
public:
stack<int>stIn;
stack<int>stOut;
MyQueue() {
}
void push(int x) {
stIn.push(x);
}
int pop() {
//向stOut中存放数据时得先确保为空栈
if (stOut.empty()){
while (!stIn.empty()){
stOut.push(stIn.top());
stIn.pop();
}
}
int result = stOut.top();
stOut.pop();
return result;
}
int peek() {
int res = this->pop();
stOut.push(res); //把弹出的数据重新加回栈中
return res;
}
bool empty() {
return stIn.empty() && stOut.empty();
}
};
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue* obj = new MyQueue();
* obj->push(x);
* int param_2 = obj->pop();
* int param_3 = obj->peek();
* bool param_4 = obj->empty();
*/
算法分析:
因为栈的数据先进后出,队列的数据先进先出,可以使用两个栈,实现栈数据的反转,使得在栈底的元素来到栈顶从而实现队列先进先出的效果。
225. 用队列实现栈
前言:
与上题相反,本题要求利用队列的一系列接口实现栈的功能,依然考察的是栈和队列原理和接口的掌握。具体实现可以与上题类似,使用两个队列进行实现,也可以只用一个队列实现。
方法一:两个队列实现
class MyStack {
public:
queue<int> que1;
queue<int> que2;
MyStack() {
}
void push(int x) {
que1.push(x);
}
int pop() {
int size = que1.size();
size--;
while (size--){
que2.push(que1.front());
que1.pop();
}
int result = que1.front();
que1.pop();
que1 = que2; //将que2中的元素返还给que1,并将que2置空
while (!que2.empty()){
que2.pop();
}
return result;
}
int top() {
return que1.back();
}
bool empty() {
return que1.empty();
}
};
/**
* Your MyStack object will be instantiated and called as such:
* MyStack* obj = new MyStack();
* obj->push(x);
* int param_2 = obj->pop();
* int param_3 = obj->top();
* bool param_4 = obj->empty();
*/
方法二:一个队列实现
class MyStack {
public:
queue<int> que;
MyStack() {
}
void push(int x) {
que.push(x);
}
int pop() {
int size = que.size();
size--;
while (size--){
que.push(que.front());
que.pop();
}
int result = que.front();
que.pop();
return result;
}
int top() {
return que.back();
}
bool empty() {
return que.empty();
}
};
/**
* Your MyStack object will be instantiated and called as such:
* MyStack* obj = new MyStack();
* obj->push(x);
* int param_2 = obj->pop();
* int param_3 = obj->top();
* bool param_4 = obj->empty();
*/
方法对比:
两种方法在弹出元素的处理上殊途同归,都是将队尾之前的元素全部移走,使得原先队尾的元素处于队首位置,方便对其进行弹出处理,两队列是利用另一个队列接受第一个队列队尾之前的元素,在将队尾数据处理之后再将另一个队列中的元素返还并置空,而用一个队列实现则是将队尾的元素取出逐个重新入队实现队尾元素变成队首。
总结:
今天的题目难度不大,但也加深了我对栈和队列数据结构的理解,对于栈和队列接口的使用也更加熟练!