栈的概念以及基本操作
基本概念
栈是只允许在一端进行插入或删除操作的线性表,即是一种操作受限的线性表,栈中的数据元素具有先进后出的特性。
栈的基本操作
栈的基本操作包括创建一个空栈,将元素入栈,将元素出栈,取栈顶元素,判断栈是否为空的操作。首先需要定义一个栈类,里面的私有成员包括一个静态数组,一个栈顶指针。其成员函数包括栈的构造函数,入栈操作函数,出栈操作函数,取栈顶元素函数,判断栈是否为空的函数,栈的析构函数。
栈类
template<typename datatype>
class seqstack
{
public:
seqstack();//栈的构造函数
void push_stack(datatype);//入栈操作函数
void pop_stack();//出栈操作函数
bool stack_empty();//判断栈是否为空的函数
datatype stack_top();//返回栈顶元素的函数
~seqstack();//栈的析构函数
private:
datatype* data;//栈的空间大小
int top;//栈顶指针
};
栈的构造函数
在堆区开辟一块空间作为栈的空间,让栈顶指针top置为-1.
//栈的初始化构造函数(构建一个空栈)
template<typename datatype>
seqstack<datatype>::seqstack()
{
top = -1;
data = new datatype[Maxsize];
}
栈的析构函数
销毁一个栈
//栈的析构函数
template<typename datatype>
seqstack<datatype>::~seqstack()
{
delete[]data;
}
入栈操作函数
先让栈顶指针指向要压入元素的位置,再把元素赋值到这个位置。
//入栈操作函数
template<typename datatype>
void seqstack<datatype>::push_stack(datatype e)
{
if (top == Maxsize - 1)
{
cout << "栈已满,无法继续压入栈中" << endl;
return;
}
top++;
*(data + top) = e;
}
出栈操作函数
先将这个元素置为0,在让top指针减1.
//出栈操作函数
template<typename datatype>
void seqstack<datatype>::pop_stack()
{
if (top == -1)
{
cout << "栈已空,无法继续出栈" << endl;
return;
}
*(data + top) = 0;
top--;
}
判断栈空的函数
//判断栈是否为空的函数
template<typename datatype>
bool seqstack<datatype>::stack_empty()
{
if (top == -1) return true;
else return false;
}
取栈顶元素的函数
//返回栈顶元素的函数
template<typename datatype>
datatype seqstack<datatype>::stack_top()
{
if (top == -1) throw"栈空";
datatype x = *(data + top);
return x;
}
逆波兰表达式
日常生活中使用的表达式都属于中缀表达式,即将操作运算符放在两个操作数的中间。而逆波兰表达式实际就是后缀表达式,即将两个操作数放在前面,而将操作符放在后面。这样的式子就称为后缀表达式。
中缀表达式转换为后缀表达式的步骤:
1.确定中缀表达式中各个运算符的运算顺序
2.选择下一个运算符,按照【左操作数 右操作数 运算符】的格式组合成一个新的操作数
这里值得注意的是,由于两个运算符可能先算哪个都可以,由此可能会有不同的后缀表达式的转换结果,这里为了统一,采用左优先原则,也就是只要左边的运算符能先计算,就优先算左边的。
用栈实现中缀转后缀
下面用栈来实现中缀表达式转后缀表达式
首先初始化一个栈,用于保存暂时还不能确定运算顺序的运算符。接下来从左到右依次扫描中缀表达式,可能会遇到以下三种情况:
1.遇到操作数:如果遇到操作数,则直接将其加入后缀表达式
2.遇到操作符:如果遇到操作符,则需要判断当前栈中栈顶元素的优先级是否比这个操作符的优先级高或相等。即依次弹出栈中优先级高于或等于这个操作符的所有元素,并加入到后缀表达式,碰到栈空或左括号则要停止(栈中不会出现右括号,因为右括号压根不会进栈),最后再把这个操作符压入栈中。
3.遇到界限符:如果遇到界限符左括号或右括号,左括号直接入栈,如果是右括号,则要一直执行出栈操作直到其碰到左括号。(注意左右括号都不加入后缀表达式当中)
按照这个逻辑,实现的代码如下:
//中缀转后缀
char* reverse_polish(string str,char ans[100])
{
char tmp;
seqstack<char> st;
int j = -1;
for (int i = 0; i < str.size(); i++)//从左到右遍历
{
if (str[i] != '+' && str[i] != '-' && str[i] != '*' && str[i] != '/' && str[i] != '(' && str[i] != ')')
{
ans[++j] = str[i];
}
else
{
switch (str[i])
{
case '+':
case '-':
if (st.stack_empty())
{
st.push_stack(str[i]);
}
else
{
while (!st.stack_empty())
{
tmp = st.stack_top();
if (tmp == '+' || tmp == '-' || tmp == '*' || tmp == '/' )
{
ans[++j] = tmp;
st.pop_stack();
}
else if (tmp == '(')
{
break;
}
}
st.push_stack(str[i]);
}
break;
case '*':
case '/':
if (st.stack_empty())
{
st.push_stack(str[i]);
}
else
{
while (!st.stack_empty())
{
tmp = st.stack_top();
if (tmp == '*' || tmp == '/')
{
ans[++j] = tmp;
st.pop_stack();
}
else if (tmp == '+' || tmp == '-'||tmp=='(')
{
break;
}
}
st.push_stack(str[i]);
}
break;
case ')':
tmp = st.stack_top();
while (tmp!='(')
{
ans[++j] = tmp;
st.pop_stack();
tmp = st.stack_top();
}
st.pop_stack();
break;
case '(':
st.push_stack(str[i]);
break;
}
}
}
while (!st.stack_empty())//把栈中剩余的运算符压入后缀表达式
{
tmp = st.stack_top();
ans[++j] = tmp;
st.pop_stack();
}
return ans;
}
测试用例
//中缀表达式转后缀表达式
//测试用例:A+B*(C-D)-E/F
string str;
cin >> str;
char ans[100] = { 0 };
char* p = reverse_polish(str,ans);
int i = 0;
while (p[i] != '\0')
{
cout << p[i];
i++;
}
后缀表达式的计算
后缀表达式手算步骤:
从左往右扫,每遇到一个运算符,就让运算符前面最近的两个操作数进行相应的运算操作,合体为一个操作数后,再进行下一步的运算。
下面用栈来实现后缀表达式的计算。
定义一个栈,存储操作数。步骤为
从左向右扫描元素,只要扫描到操作数,就将其压入栈中。如果扫描到操作运算符,那么弹出两个栈顶元素,执行相应操作,运算结果压回栈顶,再继续扫描下一个元素,依次类推。栈中剩下的最后一个元素,就是后缀表达式运算得到的结果。这里注意先弹出的是右操作数,后弹出的是左操作数。
实现代码如下:
//后缀表达式计算
int calculate_reverse_polish(string str)
{
seqstack<int> st;
for (int i = 0; i < str.size(); i++)
{
if (str[i] != '+' && str[i] != '-' && str[i] != '*' && str[i] != '/')
{
st.push_stack(str[i]-'0');
}
else if (str[i] == '+' || str[i] == '-' || str[i] == '*' || str[i] == '/')
{
int ans = 0;
int tmp1 = st.stack_top();
st.pop_stack();
int tmp2 = st.stack_top();
st.pop_stack();
switch (str[i])
{
case '+':
ans=tmp2 + tmp1;
break;
case '-':
ans = tmp2 - tmp1;
break;
case '*':
ans = tmp2 * tmp1;
break;
case '/':
ans = tmp2 / tmp1;
break;
}
st.push_stack(ans);
}
}
return st.stack_top();
}
处理含有两位数的后缀表达式:
//后缀表达式计算
//测试用例:1+2*(4-3)-5/5
int calculate_reverse_polish(string str)
{
seqstack<int> st;
for (int i = 0; i < str.size(); i++)
{
if (str[i] == ' ')
{
int pos = ++i;
int end = pos;
char temparr[100] = { 0 };
int k = 0;
while (str[end] != ' ')
{
temparr[k] = str[end];
k++;
end++;
}
string tmp;
tmp.assign(temparr, sizeof(temparr));
int a = stoi(tmp);
st.push_stack(a);
i = end;
}
else
if (str[i] != '+' && str[i] != '-' && str[i] != '*' && str[i] != '/')
{
st.push_stack(str[i]-'0');
}
else if (str[i] == '+' || str[i] == '-' || str[i] == '*' || str[i] == '/')
{
int ans = 0;
int tmp1 = st.stack_top();
st.pop_stack();
int tmp2 = st.stack_top();
st.pop_stack();
switch (str[i])
{
case '+':
ans=tmp2 + tmp1;
break;
case '-':
ans = tmp2 - tmp1;
break;
case '*':
ans = tmp2 * tmp1;
break;
case '/':
ans = tmp2 / tmp1;
break;
}
st.push_stack(ans);
}
}
return st.stack_top();
}
这两步操作可以进行合并:
初始化,两个栈,操作数栈和运算符栈,如果扫描到操作数,则压入操作数栈,如果扫描到运算符或界限符,按照中缀转后缀的相同逻辑压入运算符栈(期间也会弹出运算符),每当弹出一个运算符时,就需要再弹出两个操作数栈的栈顶元素并执行相应操作,将操作运算得到的结果压回操作数栈。