栈和队列
目录
概念
栈和队列也是线性表,其特殊性在于栈和队列的基本操作是线性表操作的子集(也具有顺序结构和链式结构),它们是操作受限的线性表,因此,可称为限定性的数据结构。
栈是一种只能从表的一端存取数据且遵循 "先进后出" 原则的线性存储结构。
队列中数据的进出要遵循 "先进先出" 的原则
常见操作
进制转换器(栈的方式)
例如,用户提供了一个十进制数:10,要求将此数据以二进制形式转换,则通过进制转换器转换的最终结果应该:1010。
#include <stdio.h>
#include <string.h>
#include <math.h>
int top=-1;//top变量时刻表示栈顶元素所在位置
void push(char * a,char elem){
a[++top]=elem;
}
void pop(char * a){
if (top==-1) {
return ;
}
//输出时要按照正确的格式显示给用户
if (a[top]>=10) {
printf("%c",a[top]+55); //因为是10到35所以,要加55变成A-Z对应的ASCII码值,65-90
}else{
printf("%d",a[top]);
}
top--;
}
//将各进制数转换成十进制数
int scaleFun(char * data,int system){
int k=(int)strlen(data)-1;
int system_10_data=0;
int i;
for (i=k; i>=0; i--) {
int temp;
if (data[i]>=48 && data[i]<=57) { //0-9
temp=data[i]-48;
}else{
temp=data[i]-55; //A-Z的ASCII码值65-90,所以减55变成10-35
}
system_10_data+=temp*pow(system, k-i); // double pow(double x, double y) 返回 x 的 y 次幂,即 xy。
}
return system_10_data;
}
int main() {
char data[100];
int system;
int newSystem;
int system_10_data;
printf("进制转换器,请输入原数据的进制(2-36):");
scanf("%d",&system);
getchar();
printf("请输入要转换的数据:");
scanf("%s",data);
getchar();
system_10_data=scaleFun(data, system);
printf("请输入转换后的数据的进制:");
scanf("%d",&newSystem);
getchar();
while (system_10_data/newSystem) { //十进制转其它进制,除其它进制,倒取余,用商继续除.
push(data,system_10_data%newSystem );//倒取余,所以用栈
system_10_data/=newSystem;
}
push(data,system_10_data%newSystem);
printf("转换后的结果为:\n");
while (top!=-1) {
pop(data);
}
}
栈实现括号匹配
//这次实现中涉及到的括号只包括小中大三种
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MaxSize 100
typedef struct{
char data[MaxSize];
int top;
}SqStack;
//顺序栈的初始化
bool InitStack(SqStack &S)
{
for(int i=0;i<MaxSize;i++)S.data[i]='\0';
S.top=-1;
return true;
}
//判断栈空:
bool StackEmpty(SqStack S)
{
if(S.top==-1)return true;
else return false;
}
//顺序栈入栈操作:
bool Push(SqStack &S,char x)
{
if(S.top==(MaxSize-1))return false;
S.data[++S.top]=x;
return true;
}
//顺序栈出栈操作:
bool Pop(SqStack &S,char &x)
{
if(S.top==-1)return false;
x=S.data[S.top--];
return true;
}
//基于顺序栈的括号匹配算法:
bool bracketCheck(char str[],int length)
{
SqStack S;
InitStack(S);
for(int i=0;i<length;i++)
{
if(str[i]=='('||str[i]=='{'||str[i]=='[')
{
Push(S,str[i]);
}
else{
if((str[i]==')'||str[i]=='}'||str[i]==']')&&StackEmpty(S))return false;
char topElem;
Pop(S,topElem);
if(str[i]==')'&&topElem!='(')return false;
if(str[i]=='}'&&topElem!='{')return false;
if(str[i]==']'&&topElem!='[')return false;
}
}
return StackEmpty(S);
}
int main()
{
int j=0;char x;
char g[MaxSize];
memset(g,'\0',MaxSize);
printf("输入你的括号组合:\n");
scanf("%c",&x);
while(x!='!')
{
g[j]=x;
j++;
scanf(" %c",&x);
}
printf("括号组合是否规范: ");
if(bracketCheck(g,j))printf("是\n");
else printf("否\n");
return 0;
}
算法考题
两个栈实现一个队列
思路
(1) 使用两个栈A,B,其中假定A负责push操作,B负责pop操作。使用一个变量back_elem来存储最后添加的元素。
(2) 实现队列的push操作, 每次进行添加操作,都会相应得对栈A进行添加元素。并对back_elem赋值
(3) 实现队列的pop操作,每次进行删除操作,因为栈B负责pop操作,
首先判断栈B是否为空?
a.如果B为空,则判断A是否为空?
如果A也为空,则输出错误信息,此时队列为空。
如果A不为空,则将栈A中的所有数据存储到B中。执B.push(A.top()), A.pop(). 然后在对栈B 执行B.pop()操作,将队列的头元素删除
b.如果B不为空, 则直接对B执行 B.pop()操作。
(4)实现队列的front()操作,方法如pop操作相同,只是在最后一步使用B.top()返回值。
(5)实现队列的back()操作,因为我们变量back_elem保存着最后一个输入的数据,故直接将其返回。
(6)实现队列的size()操作,和empty()操作,就是对A,B分别执行操作。
代码
#include <iostream>
#include <stack>
#include <string>
using namespace std;
template<typename T>
class Queue {
private:
stack<T>stackA; //栈A
stack<T>stackB; //栈B
T back_elem; //用于存储新添加的元素
public:
void push(T elem); //将新元素压入队列(压入栈A)中
void pop(); //将元素弹出队列(从栈B中弹出)
T front(); //队首元素
T back(); //队尾元素
int size()const; //队列长度
bool empty()const; //队列是否为空
};
/*
入队操作
实现队列的push操作, 每次进行添加操作,都会相应得对栈A进行添加元素。并对back_elem赋值
*/
template<typename T>
void Queue<T>::push(T elem)
{
stackA.push(elem);//将元素压入队列
back_elem = elem; //存储新添加的元素
}
/*
出队操作
实现队列的pop操作,每次进行删除操作,因为栈B负责pop操作。
首先判断栈B是否为空?
a.如果栈B为空,则判断A是否为空?
如果A也为空,则输出错误信息,此时队列为空。
如果A不为空,则将栈A中的所有数据存储到B中。执B.push(A.top()), A.pop().然后在对栈B
执行,B.pop()操作,将队列的头元素删除
b.如果栈B不为空, 则直接对栈B执行 B.pop()操作。
*/
template<typename T>
void Queue<T>::pop()
{
//判断栈B是否为空?
if (!stackB.empty()) //栈B不为空, 则直接对栈B执行 B.pop()操作。
{
stackB.pop();
}
else if (!stackA.empty()) //栈B为空,则判断栈A是否为空?栈A不为空,则将栈A中的所有数据
//存储到B中。执B.push(A.top()), A.pop().然后在对栈B执行,B.pop()操作,将队列的头元素删除
{
stackB.push(stackA.top());
stackA.pop();
}
else
{
std::cout << "error pop(),empty queue!" << std::endl;
}
}
/*
队首元素
*/
template<typename T>
T Queue<T>::front()
{
if (!stackB.empty())
{
return stackB.top();
}
else if (!stackA.empty())
{
while (!stackA.empty())
{
stackB.push(stackA.top());
stackA.pop();
}
return stackB.top();
}
else
{
std::cout << "error front(),empty queue!" << std::endl;
}
}
/*
队尾元素
*/
template<typename T>
T Queue<T>::back()
{
if (!empty())
{
return back_elem;
}
else
{
std::cout << "error back(),empty queue!" << std::endl;
}
}
/*
队列长度
*/
template<typename T>
int Queue<T>::size() const
{
return stackA.size() + stackB.size();
}
/*
队列是否为空
*/
template<typename T>
bool Queue<T>::empty() const {
return stackA.empty() && stackB.empty();
}
int main()
{
Queue<int>queue;
//入队操作
queue.push(1);
queue.push(2);
queue.push(3);
queue.push(4);
cout << "Four times push() After:" << endl;
//队首元素
cout << "The queue front:" << queue.front() << endl;
//队尾元素
cout << "The queue back:" << queue.back() << endl;
//队列size
cout << "The queue size:" << queue.size() << endl;
//出队操作
queue.pop();
queue.pop();
queue.pop();
queue.pop();
cout << "----------------------------" << endl;
cout << "Four times pop() After:" << endl;
//队首元素
cout << "The queue front:" << queue.front() << endl;
//队尾元素
cout << "The queue back:" << queue.back() << endl;
//队列size
cout << "The queue size:" << queue.size() << endl;
//system("pause");
return 0;
}
两个队列实现栈
思路
入栈和出栈,都在 queue1 中完成,而 queue2 作为中转空间。
- 入栈:直接入 queue1 即可。
- 出栈:把 queue1 中除最后一个元素外的所有元素都移动到 queue2 中,再将 queue1 转化为queue2,queue2转化为queue1
代码
#include<iostream>
#include<queue>
using namespace std;
template<typename T>
class CStack
{
public:
CStack(){}
~CStack(){}
void push(const T &val)
{
if(queue1.empty() && queue2.empty()) //两个队列为空,则插入queue1
{
queue1.push(val);
}
if(queue1.empty()) //如果queue1为空,则插入queue2
{
queue2.push(val);
}
else
{
queue1.push(val); //如果queue2为空,则插入queue1
}
}
T pop()
{
if(queue1.empty()) //如果queue1为空
{
if(queue2.empty()) //如果queue2为空,则说明栈为空
{
throw new exception("stack is empty");
}
else
{
if(queue2.size() == 1) //如过queue2只有一个元素,直接退出
{
T result = queue2.front();
queue2.pop();
return result;
}
else
{
while(queue2.size() != 1) //依次退出队中元素,直到队列中只有队尾元素
{
queue1.push(queue2.front());
queue2.pop();
}
T result = queue2.front(); //将队尾元素退出
queue2.pop();
return result;
}
}
}
else //如果queue1不为空
{
if(queue1.size() == 1) //如果queue1中只有队尾元素时,直接退出
{
T result = queue1.front();
queue1.pop();
return result;
}
else
{
while(queue1.size() != 1) //依次退出queue1中的元素到queue2,直到queue1只有队尾元素
{
queue2.push(queue1.front());
queue1.pop();
}
T result = queue1.front(); //将队尾元素弹出
queue1.pop();
return result;
}
}
}
private:
queue<T> queue1;
queue<T> queue2;
};
int main()
{
CStack<char> stack;
//测试用例1:
stack.push('a'); //元素a入栈
stack.push('b'); //元素b入栈
stack.push('c'); //元素c入栈
cout << "第1次出栈元素是:" << stack.pop() << endl;
cout << "第2次出栈元素是:" << stack.pop() << endl;
stack.push('d'); //元素d入栈
cout << "第3次出栈元素是:" << stack.pop() << endl;
cout << "第4次出栈元素是:"<< stack.pop() << endl;
return 0;
}
最小栈
题目描述
设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。
push(x) – 将元素 x 推入栈中。
pop() – 删除栈顶的元素。
top()– 获取栈顶元素。
getMin() – 检索栈中的最小元素。
思路
通过双栈来实现最小栈,因为这样可以在常数时间复杂度中得到栈内元素最小值。这是典型的空间换时间的操作。
入栈时要对两个栈同时进行操作,normal栈正常压栈,对于min栈进行参数条件判定:如果此时
最小栈为空 (说明是第一次压栈)
这个值小于此时最小栈的栈顶元素 (说明是压入了一个小于已入栈全部元素的值)
两条件是任满足其一即可进行最小栈的压栈,否则不满足条件执行最小栈的栈顶元素压栈,说明压入的这个值不是最小值,将目前最小值再次压入最小栈。
代码实现
#include <stdio.h>
#include <stack>
class MinStack {
public:
MinStack() {
}
void push(int x) {
_data.push(x);
if (_min.empty()){
_min.push(x);
}
else{
if (x > _min.top()){
x = _min.top();
}
_min.push(x);
}
}
void pop() {
_data.pop();
_min.pop();
}
int top() {
return _data.top();
}
int getMin() {
return _min.top();
}
private:
std::stack<int> _data;
std::stack<int> _min;
};
每日温度(Medium)
题目描述
根据每日 气温 列表,请重新生成一个列表,对应位置的输入是你需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0 来代替。
例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。
提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。
题目解析
- 维护一个递减栈,若当前元素大于栈顶元素,则说明温度升高,栈顶元素出栈,两者下标的差值即为栈顶元素所在下标的所求天数。
- 直到当前元素小于栈顶元素,当前元素入栈,继续判断。
- 时间复杂度:O(N)。
- 空间复杂度:O(N)。
代码实现
int* dailyTemperatures(int* T, int TSize, int* returnSize) {
int* result = (int*)malloc(sizeof(int)*TSize);
// 用栈记录T的下标。
int* stack_index = malloc(sizeof(int)*TSize);
*returnSize = TSize;
result[TSize-1] = 0;
// 栈顶指针。
int top = 0;
for (int i = 0; i < TSize; i++)
result[i] = 0;
for (int i = 0; i < TSize; i++) {
// 若当前元素大于栈顶元素,栈顶元素出栈。即温度升高了,所求天数为两者下标的差值。
while (top > 0 && T[i] > T[stack_index[top-1]]) {
result[stack_index[top-1]] = i-stack_index[top-1];
top--;
}
// 当前元素入栈。
stack_index[top] = i;
top++;
}
return result;
}
最大的矩形
题目描述
在横轴上放了n个相邻的矩形,每个矩形的宽度是1,而第i(1 ≤ i ≤ n)个矩形的高度是hi。这n个矩形构成了一个直方图。例如,下图中六个矩形的高度就分别是3, 1, 6, 5, 2, 3。
请找出能放在给定直方图里面积最大的矩形,它的边要与坐标轴平行。对于上面给出的例子,最大矩形如下图所示的阴影部分,面积是10。
思路:
我们可以维持一个单调递增的栈,为了便于计算矩形宽度,我们在栈里存放单个矩形的位置。
我们从左到右遍历高度数组,对于每个矩形的高度p,如果p大于等于当前栈顶储存位置的高度q,我们将p的位置也压入栈中;
如果p小于q,我们将栈中元素弹出,纪录高度,并记录当前遍历到的矩形p与新栈顶位置之差(实际上还需要减1),作为宽度,并更新结果。
代码实现
int largestRectangleArea(vector<int>& heights) {
heights.push_back(0); //插入空矩形,弹出栈中剩余矩形
int len = heights.size(), area = 0, pre_index, height, width;
stack<int> indices;
for (int i = 0; i < len; i++) {
while (!indices.empty() && heights[indices.top()] > heights[i]) { //检查栈是否为空
pre_index = indices.top(); //储存栈顶矩形的位置
indices.pop();
height = heights[pre_index]; //储存高度
if (indices.empty()) { //避免操作空栈
width = i; //若弹出至栈为空,因栈的递增性,边界可向左延伸至0
} else {
width = i - indices.top() - 1; //储存宽度
}
area = area > (width * height) ? area : (width * height); //更新结果
}
indices.push(i);
}
return area;
}