栈(stack)通常也被称之为“堆栈”。它的本质是线性表。堆(heap)通常我们也称它为优先队列,本质是树。此处讲述一些stack的应用。
-
平衡符号
编译器在检查(){}这样成对出现的符号所造成的语法错误时,通常并不需要去设计一个很复杂的程序去判断。而是使用一个简单的算法,这个算法用到一个栈。算法描述如下:
做一个空栈,从这串代码的开始读到末尾。如果读到的字符是一个开放字符——左括号,那么将它入栈。如果是一个封闭符号——右括号,这时将栈中的元素弹出。如果弹出的元素是封闭符号对应的开放符号,那么正确(正确的时候不做任何提示),否则就报错。如果这时的栈为空,那么说明缺失了开放字符,报错。当这串代码读完时,如果栈不为空,那么报错。
下面给出其C语言实现的代码:栈及其实现不表
int IsSyntaxError(const char *p,const int num)
{
while(end != p)
{
if('(' == *p || '{' == *p)
{
Push(*p);
}
if(')' == *p)
{
char ch;
ch = Pop();
if('(' != ch))
{
return error;
}
}
if('}' == *p)
{
char ch;
ch = Pop();
if('{' != ch))
{
return error;
}
}
}
if(!StackIsEmpty())
return error;
}
-
表达式转换(中缀表达式转后缀表达式)
中缀表达式:操作符位于操作数中间,例如:2+3*5。我们现在使用的算术表达式就是中缀表达式。
后缀表达式:操作符放在两个操作数的后面,并且严格遵守从左向右的运算规则。而且后缀表达式相比于前缀表达式是没有括号运算符的。例如:2 3 *(对应的中缀表达式就是2*3)。
前缀表达式:与后缀表达式刚好相反,操作符位于两个操作数之前。
前缀表达式我们也常称为“波兰表达式”,后缀表达式常称为“逆波兰表达式”。
下面是将中缀表达式转换成后缀表达式的一般步骤:假设我们从标准输入读取一个中缀表达式
-
读到一个数字时,立即将数字放入输出流。
-
读到左括号时,将其压入堆栈中。
-
遇到右括号时,右括号本身不入栈,从栈顶开始弹出操作符,放入输出流,直到遇到一个左括号为止,将这个左括号弹出,但是不放入输出流。
-
遇到运算符时,若该运算符的优先级高于当前栈顶运算符的优先级,则将它压入栈,若该运算符的优先级小于等于当前栈顶运算符的优先级,将栈顶运算符弹出到输出流,然后按照规则继续与新的栈顶运算符进行比较,直到运算符优先级大于栈顶运算符的优先级,将运算符压入栈。
-
按照以上步骤将表达式处理完后,此时若堆栈不为空,则将栈中所有运算符弹出到输出流。
需要注意的是,左括号的优先级的问题,它在栈外时,优先级最高,在栈内时优先级最低。因此必须处理好左括号的优先级。我的代码只是实现了转换,但是实现的并不怎么好。下面给出代码。
栈的头文件
#ifndef STACK
#define STACK
#include<stdlib.h>
#include<stdio.h>
typedef struct StackNode stack;
typedef stack * PSNode;
struct StackNode
{
char c;
PSNode next;
};
PSNode CreatStack();
int IsEmpty(PSNode S);
void Push(PSNode S,char c);
char Pop(PSNode S);
#endif // !STACK
栈的实现
#include "stack.h"
PSNode CreatStack()
{
PSNode top;
top = (PSNode)malloc(sizeof(stack));
top->next = NULL;
return top;
}
int IsEmpty(PSNode S)
{
if (NULL == S->next)
{
return 1;
}
else
{
return 0;
}
}
void Push(PSNode S,char c)
{
PSNode temp;
temp = (PSNode)malloc(sizeof(stack));
temp->c = c;
temp->next = S->next;
S->next = temp;
}
char Pop(PSNode S)
{
if (IsEmpty(S))
{
printf("错误!栈为空!");
return 0;
}
else
{
char c;
PSNode temp;
temp = S->next;
S->next = S->next->next;
c = temp->c;
free(temp);
return c;
}
}
main.c文件
#include"stack.h"
#define MAX 100
void transform(char *ch); //转换函数
PSNode s1;
int main()
{
char str[MAX];
printf("请输入中缀表达式:");
scanf("%s", str);
s1 = CreatStack();
printf("转换后的后缀表达式:");
transform(str);
printf("\n");
system("pause");
return 0;
}
void transform(char * ch)
{
int flag = 1; //遇到负数时的符号。
int number = 0; //记录数字
int i;
for (i = 0;'\0' != ch[i]; i++)
{
//对负数的处理,第一个数是负数和左括号后面有减号就是负数。
if (('-' == ch[i] && 0 == i) || ch[i-1] == '(' && '-' == ch[i])
{
flag = -1;
continue;
}
//将字符串转换成整数(代码不支持浮点数)
if ('0' <= ch[i] && '9' >= ch[i])
{
number = number * 10 + ch[i] - '0';
if (ch[i+1] < '0' || ch[i+1] > '9')
{
printf("%d ", flag * number);
number = 0;
flag = 1;
}
}
else
{
//先处理栈为空的情况,避免栈不为空,但是经常Pop操作后变空了的情况。
if (IsEmpty(s1) && ')' == ch[i]) //遇到右括号,但是栈为空
{
printf("错误,中缀表达式的括号不匹配!\n");
return;
}
if (!IsEmpty(s1) && ')' == ch[i]) //遇到右括号且栈不空
{
while (!IsEmpty(s1) && s1->next->c != '(')
{
printf("%c ", Pop(s1));
}
if (IsEmpty(s1))
{
printf("错误,中缀表达式的括号不匹配!\n");
return;
}
else
{
Pop(s1); //弹出左括号,但是不输出
}
}
if ('+' == ch[i]) //遇到加号
{
if (IsEmpty(s1))
{
Push(s1,ch[i]);
}
else
{
while (!IsEmpty(s1) && '(' != s1->next->c )
{
printf("%c ", Pop(s1));
}
Push(s1, ch[i]);
}
}
if ('-' == ch[i]) //遇到减号
{
if (IsEmpty(s1))
{
Push(s1, ch[i]);
}
else
{
while (!IsEmpty(s1) && '(' != s1->next->c)
{
printf("%c ", Pop(s1));
}
Push(s1, ch[i]);
}
}
if ('*' == ch[i]) //遇到乘号
{
if (IsEmpty(s1))
{
Push(s1, ch[i]);
}
else
{
while (!IsEmpty(s1) && ('*' == s1->next->c || '/' == s1->next->c))
{
printf("%c ", Pop(s1));
}
Push(s1, ch[i]);
}
}
if ('/' == ch[i]) //遇到除号
{
if (IsEmpty(s1))
{
Push(s1, ch[i]);
}
else
{
while (!IsEmpty(s1) && ('*' == s1->next->c || '/' == s1->next->c))
{
printf("%c ", Pop(s1));
}
Push(s1, ch[i]);
}
}
if ('(' == ch[i] ) //遇到左括号
{
Push(s1, ch[i]);
}
}
}
while (!IsEmpty(s1)) //读入结束后,栈不空就将栈中所有运算符弹出
{
printf("%c ", Pop(s1));
}
}
-
后缀表达式求值
-
当遇到操作数时,直接压入栈中。
-
遇见操作符时,就从栈中弹出两个操作数,把这两个操作数按照操作符的运算规则进行运算,将运算结果也压入栈中。
-
重复以上两个步骤,直到将表达式计算完毕。
main.c文件
#include"stack.h"
ElementType Calc(Pstack s, char *str);
int main()
{
char str[MAX];
printf("请输入后缀表达式:");
gets(str);
Pstack s;
s = CreatStack();
ElementType data = Calc(s, str);
Pop(s); //弹出最后一个元素
if (!IsEmpty(s))
{
while (!IsEmpty(s))
{
Pop(s);
}
printf("错误,表达式运算符数目过少!按任意键结束...");
getchar();
exit(0);
}
printf("计算结果是:%d\n", data);
system("pause");
return 0;
}
ElementType Calc(Pstack s, char * str)
{
ElementType num = 0;
int flag = 1;
ElementType temp1, temp2, temp = 0;
for (int i = 0;'\0' != str[i]; i++)
{
if ('-' == str[i] && ' '!= str[i + 1]) //负号后面跟的不是空格表明这里有个负数
{
flag = -1;
continue;
}
if ('0' <= str[i] && '9' >= str[i]) //将数字入栈
{
num = 10 * num + str[i] - '0';
if (' ' == str[i + 1])
{
Push(s, flag * num);
num = 0;
flag = 1;
}
continue;
}
if (' ' == str[i])
{
continue;
}
if (' ' != str[i])
{
//从栈中弹出两个操作数
temp2 = Pop(s);
temp1 = Pop(s);
if (INT_MAX == temp1 || INT_MAX == temp2)
{
printf("表达式错误!按任意键结束程序...");
getchar();
exit(0);
}
switch (str[i])
{
case '+':
{
temp = temp1 + temp2;
Push(s, temp);
break;
}
case '-':
{
temp = temp1 - temp2;
Push(s, temp);
break;
}
case '*':
{
temp = temp1 * temp2;
Push(s, temp);
break;
}
case '/':
{
temp = temp1 / temp2;
Push(s, temp);
break;
}
default:
{
break;
}
}
}
}
return s->next->data; //返回计算结果
}
在这段代码的栈实现中,稍微更改了Pop()函数。将修改后的Pop函数放在下面.
ElementType Pop(Pstack s)
{
Pstack temp;
ElementType dat;
if (NULL == s->next)
{
printf("栈为空!");
return INT_MAX;
}
else
{
temp = s->next;
dat = temp->data;
s->next = s->next->next;
free(temp);
return dat;
}
}
结合上面的中缀表达式转后缀表达式,以及后缀表达式求值。我们可以得到计算一个中缀表达式的方案。当然了,在中缀表达式转后缀表达式的过程中就可以边转边计算。(这样的算法是联机的)
-
函数调用
当调用一个函数时,主调程序的所有局部变量都需要存储起来,否则被调用的新函数将会覆盖调用例程的变量。当然,还需要将主调程序的当前位置必须存储,这样当被调函数执行完后,才能返回到原来的地方继续执行。这些都可以用栈来方便的实现。对于递归函数而言,递归总是能够被去除的(编译器完成这个操作),这样需要借助一个栈。去除递归可能会使程序的执行速度变快,但是也会使的程序的简明性下降。