栈是一种先进后出的数据结构,是一种功能受限的线性表。因为这世间存在这后进先出的计算顺序,为简化计算的过程,栈得以应用。好比装水的桶,好比装子弹的弹夹。
栈的C++实现
本文主要是编程实现栈,使用的是顺序储存结构(动态数组)来实现栈,需要注意的是当内存空间不够用,即栈满的时候,应该将重新开辟新的空间(大小为源空间大小+STACK_INCREMENT),然后将数据复制到新空间,在重新释放旧空间。
下面给出头文件定义(Stack.h):
#define STACK_INIT_SIZE 100 ///the size of stack after initialized
#define STACK_INCREMENT 10 ///the increment of stack growth
class Stack{
private:
int *base;///using array to store element
int top;///use index(index of base) to find the top element of stack
int stacksize;
///will be called when push a element to a full stack(top = stacksize)
bool ReallocateMemory();
public:
///apply for memory and form a empty stack
Stack();
///destroy the stack s and s won't exist anymore
void DestroyStack();
///clear the stack s and s become a empty stack
void ClearStack();
///get top element of stack while s is not empty otherwise return false
bool GetTop(int &e);
///insert a element to the stack
bool Push(int e);
///remove the top element and return it
bool Pop(int &e);
///get stack size
int GetSize();
};
不难看出,栈的函数比较少,可以很容易写出其函数定义(Stack.cpp):
#include "Stack.h"
///apply for memory and form a empty stack
Stack::Stack(){
base = new int[STACK_INIT_SIZE];
top = 0;
stacksize = STACK_INIT_SIZE;
}
///will be called when push a element to a full stack(top = stacksize)
bool Stack::ReallocateMemory(){
int * tmp = new int[stacksize+STACK_INCREMENT];
for(int i = 0; i < top; ++i){
tmp[i] = base[i];
}
delete [] base;
base = tmp;
stacksize +=STACK_INCREMENT;
if(base!=nullptr)
return true;
return false;
}
///destroy the stack s and s won't exist anymore
void Stack::DestroyStack()
{
delete [] base;
base = nullptr;
top = 0;
stacksize = 0;
}
///clear the stack s and s become a empty stack
inline void Stack::ClearStack(){
top = 0;
}
///get top element of stack while s is not empty otherwise return false
bool Stack::GetTop(int &e){
if(top==0)
return false;
e=base[top-1];
return true;
}
///insert a element to the stack
bool Stack::Push(int e){
bool flag;
if(top==stacksize)
flag = ReallocateMemory();
base[top++]=e;
return flag;
}
///remove the top element and return it
bool Stack::Pop(int &e){
if(top==0)
return false;
e = base[--top];
return true;
}
///get stack size
inline int Stack::GetSize(){
return top;
}
这里top是动态数组的下标,指向栈顶元素的下一个位置,当top=0时,表示栈空,当top=stacksize时,表示栈满。
测试
下面给出测试代码(main.cpp):
#include <iostream>
using namespace std;
#include "Stack.h"
int main()
{
Stack s;
int e;
for(int i = 0;i<110;++i)
s.Push(i);
for(int i = 0;i<110;++i)
{
if(s.Pop(e))
cout<<e<<' ';
}
cout<<endl;
return 0;
}
上述代码的思路是依次向栈压入0/1/2/3/4…109,那么输出应该是109/108…0。
测试结果:
栈的应用
1、表达式求值
(1)我们知道,对于中缀表达式而言,不好判断计算顺序,于是波兰学者提出了后缀表达式的概念,而后缀表达式非常适合程序的处理。如果不清楚这个算法的话,可以参考博文:后缀表达式。
(2)对算法有所了解以后,我们可以发现,要想计算表达式的值,需要先将中缀表达式转换成后缀表达式(步骤一),然后利用后缀表达式进行求值(步骤二);而将中缀表达式转换成后缀表达式的关键在于算符优先表的构建;上述的两个过程都涉及到栈的应用;
(3)步骤一编程实现:
///算符优先表,1表示>,0表示=,-1表示<,2表示不存在优先关系
int opTable[7][7]={
1,1,-1,-1,-1,1,1,
1,1,-1,-1,-1,1,1,
1,1,1,1,-1,1,1,
1,1,1,1,-1,1,1,
-1,-1,-1,-1,-1,0,2,
1,1,1,1,2,1,1,
-1,-1,-1,-1,-1,2,0
};
char op[7]={'+','-','*','/','(',')','#'};
int indexOf(char c){
int j;
for(j = 0;j<7;++j){
if(op[j]==c)
break;
}
return j;
}
bool convert(char *mExp,char *bExp){
/**<
1、建立符号栈*/
Stack opStack;
/** 2、顺序扫描中序表达式
a) 是数字, 直接输出
b) 是运算符*/
int n = strlen(mExp);
int k = 0;
for(int i=0;i<n;++i){
char c = mExp[i];
if(isdigit(c)){
bExp[k++]=c;
}
else{
int j = indexOf(c);
///不是合法算符
if(j==7)
return false;
///i : “(” 直接入栈
if(c=='('){
///由于我的stack是int型,这里会有类型转换
int x = (int)c;
opStack.Push(x);
}
///ii : “)” 将符号栈中的元素依次出栈并输出, 直到 “(“, “(“只出栈, 不输出
else if(c==')'){
char ch;
int xx = ch;
opStack.GetTop(xx);
ch = (char)xx;
while(ch!='('){
opStack.Pop(xx);
bExp[k++]=(char)xx;
opStack.GetTop(xx);
ch = (char)xx;
}
opStack.Pop(xx);
}
///iii: 其他符号, 将符号栈中的元素依次出栈并输出,
///直到遇到比当前符号优先级更低的符号或者”(“。 将当前符号入栈。
else{
if(opStack.GetSize()!=0){
char ch;
int x = (int)ch;
opStack.GetTop(x);
ch = (char)x;
int column = indexOf(ch);
int tuplE = indexOf(c);
while(opTable[column][tuplE]!=-1 && ch != '('){
bExp[k++]=ch;
opStack.Pop(x);
opStack.GetTop(x);
ch=(char)x;
column=indexOf(ch);
}
}
opStack.Push(c);
}
}
}
///扫描完后, 将栈中剩余符号依次输出
while(opStack.GetSize()!=0){
int x;
opStack.Pop(x);
bExp[k++]=char(x);
}
bExp[k]='\0';
return true;
}
需要注意的是,我这里用的栈是自己写的栈,栈的元素是整型,而处理表达式用的是字符型,在程序运行过程中存在类型转换。因此,这是容易出错的。比较好的做法是在编写栈的实现的时候使用泛型编程,使得栈的数据类型可变,但是对每一种数据类型的操作都是一样的。
(4)步骤二编程实现:
/** 建立一个栈S 。从左到右读表达式,
如果读到操作数就将它压入栈S中,如果
读到n元运算符(即需要参数个数为n的运算符)
则取出由栈顶向下的n项按操作符运算,
再将运算的结果代替原栈顶的n项,
压入栈S中。如果后缀表达式未读完,
则重复上面过程,最后输出栈顶的数值则为结束。
*/
int calculate(char *bExp){
Stack s;
int n = strlen(bExp);
char c;
int x,y,op1,op2;
int result;
for(int i=0;i<n;++i){
c = bExp[i];
x = (int)c;
if(isdigit(c)){
s.Push(x-'0');
}
else{
if(c=='+'){
s.Pop(op2);
s.Pop(op1);
result = op1+op2;
//cout<<op1<<c<<op2<<'='<<result<<endl;
s.Push(result);
}
if(c=='-'){
s.Pop(op2);
s.Pop(op1);
result = op1-op2;
//cout<<op1<<c<<op2<<'='<<result<<endl;
s.Push(result);
}
if(c=='*'){
s.Pop(op2);
s.Pop(op1);
result = op1*op2;
//cout<<op1<<c<<op2<<'='<<result<<endl;
s.Push(result);
}
if(c=='/'){
s.Pop(op2);
s.Pop(op1);
result = op1/op2;
//cout<<op1<<c<<op2<<'='<<result<<endl;
s.Push(result);
}
}
}
s.Pop(x);
return x;
}
(4)测试
int main()
{
char mExp[100],bExp[100];
cout<<"请输入中缀表达式:"<<endl;
cin>>mExp;
if(convert(mExp,bExp)){
cout<<"转换得到的后缀表达式:"<<endl;
for(int i=0;i<strlen(bExp);++i)
cout<<bExp[i]<<' ';
cout<<endl;
}
cout<<"根据后缀表达式计算得到值为:"<<calculate(bExp)<<endl;
return 0;
}
运行结果:
2、利用两个栈实现一个队列
算法原理:我们知道队列是先进先出的线性表,而栈是先进后出的线性表。那么两次先进后出,就会变成先进先出,于是编程的关键在于保证两次先进后出的顺序,比如对于输入1,2,3,4来说,到达第一个栈后输出变成4/3/2/1;而到达第二个栈后输出就变成1/2/3/4了,这就实现了队列的功能。设s1是输入的第一个栈,s2是第二个栈,那么,当s2不为空时,不可以将s1的数据导入s2,否则将打破两次先进后出的顺序。
#include "Stack.h"
///利用两个栈实现一个队列的功能
class Queue{
private:
Stack s1,s2;///s1是输入栈、s2是输出栈
public:
Queue(){}
bool push(int e){
s1.Push(e);
}
bool pop(int &e){
if(s2.GetSize()!=0){
s2.Pop(e);
return true;
}
else if(s1.GetSize()!=0){
int x;
while(s1.GetSize()!=0){
s1.Pop(x);
s2.Push(x);
}
s2.Pop(e);
return true;
}
else{
return false;
}
}
};
我这里的栈都是可以无限增长的,所以不用判断s1是否为满。否则需要判断s1是否为满。
测试:
Queue q;
q.push(1);
q.push(2);
int e;
q.pop(e);
cout<<e<<' ';
q.push(3);
q.push(4);
q.pop(e);
cout<<e<<' ';
q.pop(e);
cout<<e<<' ';
q.pop(e);
cout<<e<<' ';
运行结果: