栈的应用
数值转换
括号匹配
行编辑程序
迷宫求解
表达式求值
递归中的应用(如递归的函数)
执行函数时,局部变量的存储(栈结构)
处理函数或过程调用(main里面调用了其他函数等的过程)
一、数值转换
1、初始化一个空栈S。
2、当十进制数N非零时,循环执行以下操作: 把N与8求余得到的八进制数压入栈S; N更新为N与8的商。
3、当栈S非空时,循环执行以下操作: 弹出栈顶元素e; 输出e。
//数值转换
void conversion(int N)
{
SqStack S;
InitStack(S);
while (N)
{
Push(S, N % 8);
N = N / 8;
}
while (!StackEmpty(S))
{
ElemType e;
Pop(S, e);
printf("%d", e);
}
}
时间和空间复杂度均为O(log8n)。
二、括号匹配
检验算法借助一个栈
1、每当读入一个左括号,则直接入栈,等待相匹配的同类右括号;
2、每当读入一个右括号,若与当前栈顶的左括号类型相同,则二者匹配,将栈顶的左括号出栈;
3、直到表达式扫描完毕。
(设置一标记性变量flag,用来标记匹配结果以控制循环及返回结果,1表示正确匹配,0表示错误匹配,flag初值为1。)
//括号匹配
bool Matching()
{
SqStack S;
InitStack(S);
int flag = 1;
ElemType ch;
ElemType s;
scanf_s("%c", &ch);
while (ch != '#' && flag)
{
switch (ch)
{
case '['||'(':
Push(S, ch);
break;
case ')':
if (!StackEmpty(S) && GetTop(S) == '(')
Pop(S, s);
else
flag = 0;
break;
case ']':
if (!StackEmpty(S) && GetTop(S) == '[')
Pop(S, s);
else
flag = 0;
break;
}
scanf_s("%c", &ch);
}
if (StackEmpty(S) && flag)
return true;
else return false;
}
时间和空间复杂度均为O(n)
三、表达式求值
Polish notation(波兰表达式=前缀表达式)
Reverse Polish notation(逆波兰表达式=后缀表达式)
1、中缀表达式
运算符在两个操作数中间
2、后缀表达式
运算符在两个操作数后面
中缀转后缀的手算方法:
(1)确定中缀表达式中各个运算符的运算顺序
(2)按 左操作数 右操作数 运算符的方式组合成新的操作数
(3)如有运算符没被处理,继续(2)
(运算顺序不唯一,后缀表达式也不唯一,可以左优先即只要左边的运算符能先计算就先计算左边的)
后缀表达式的计算:
从左向右扫描,让运算符前最近的两个操作数执行运算,合为一个操作数(注意操作数左右顺序)
特点:
最后出现的操作数先被运算(因为是最近的两个操作数,LIFO后进先出—>栈!!)
用栈实现中缀表达式转后缀表达式
初始化一个栈,用于保存暂时还不能确定运算顺序的运算符。
从左到右处理各个元素,直到末尾。可能遇到三种情况:
①遇到操作数。直接加入后缀表达式。
②遇到界限符。遇到“(” 直接入栈;遇到“)”则依次弹出栈内运算符并加入后缀表达式,直到弹出“(”为止。注意:“(”不加入后缀表达式。
③遇到运算符。依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式,若碰到“(”或栈空则停止。之后再把当前运算符入栈。
按上述方法处理完所有字符后,将栈中剩余运算符依次弹出,并加入后缀表达式。
用栈实现后缀表达式的计算:
(1)从左往右扫描下一个元素,直到处理完所有元素
(2)若扫描到操作数则压入栈,并回到(1);否则执行(3)
(3)若扫描到运算符,则弹出两个栈顶元素,执行运算,运算结果压回栈顶,回到(1)
(注意先出栈的是右操作数;若表达式合法,则最后栈中只会留下一个元素,就是最终结果)
用栈实现中缀表达式的计算:
初始化两个栈,操作数栈和运算符栈;
若扫描到操作数,压入操作数栈;
若扫描到运算符或界限符,则按照“中缀转后缀”相同的逻辑压入运算符栈(期间也会弹出运算符,每当弹出一个运算符时,就需要再弹出两个操作数栈的栈顶元素并执行相应运算,运算结果再压回操作数栈)
课本------【算法步骤】
1、初始化OPTR栈和OPND栈,将表达式起始符“#”压入OPTR栈。
2、扫描表达式,读入第一个字符ch,如果表达式没有扫描完毕至“#”或OPTR的栈顶元素不为“#”时,则循环执行以下操作:
·若ch不是运算符,则压入OPND栈,读入下一字符ch;
·若ch是运算符,则根据OPTR的栈顶元素和ch的优先级比较结果,做不同的处理:
• 若是小于,则ch压入OPTR栈,读入下一字符ch;
• 若是大于,则弹出OPTR栈顶的运算符,从OPND栈弹出两个数,进行相应运算,结果压入OPND栈;
• 若是等于,则OPTR的栈顶元素是“(”且ch是“)”,这时弹出OPTR栈顶的“(”,相当于括号匹配成功,然后读入下一字符ch。
3、OPND栈顶元素即为表达式求值结果,返回此元素。
表1 运算符比较
//计算表达式
char EvaluateExpression()
{
SqStack OPND;
SqStack OPTR;
InitStack(OPND);
InitStack(OPTR);
Push(OPTR, '#');
ElemType c;
scanf_s("%c", &c);
while (c != '#' || GetTop(OPTR) != '#')
{
if (!In(c)) //bool In(ElemType c) //判断是否运算符
{
Push(OPND, c);
scanf_s("%c", &c);
}
else
{
switch (Precede(GetTop(OPTR),c)) //Precede(ElemType s,ElemType c) //判断栈顶元素与当前字符c的优先级
{
case '<': //如果栈顶元素优先级小于c
Push(OPTR, c);
scanf_s("%c", c);
break;
case '>': //如果栈顶元素优先级大于c,弹出栈顶运算符,并依次弹出运算数栈中的两个元素,先弹出的是“右操作数”
Pop(OPTR, theta);
Pop(OPND, b);
Pop(OPND, a);
Push(OPND, Operate(a, theta, b));
break;
case '=': //当右括号遇到左括号,弹出左括号(括号里的运算符已在之前完成运算操作)
Pop(OPTR, x);
scanf_s("%c", c);
break;
}
}
}
return GetTop(OPND); //栈顶元素即为表达式所求值
}
时间和空间复杂度均为O(n)
3、前缀表达式
运算符在两个操作数前面
中缀转前缀的手算方法:
(1)确定中缀表达式中各个运算符的运算顺序
(2)按 运算符 左操作数 右操作数的方式组合成新的操作数
(3)如有运算符没被处理,继续(2)
(运算顺序不唯一,后缀表达式也不唯一,可以右优先即只要右边的运算符能先计算就先计算右边的)
用栈实现后缀表达式的计算:
(1)从右往左扫描下一个元素,直到处理完所有元素
(2)若扫描到操作数则压入栈,并回到(1);否则执行(3)
(3)若扫描到运算符,则弹出两个栈顶元素,执行运算,运算结果压回栈顶,回到(1)
(注意先出栈的是左操作数;若表达式合法,则最后栈中只会留下一个元素,就是最终结果)
队列的应用
数的层次遍历
图的广度优先搜索
缓冲区
计算机系统中的应用(主机与打印机之间速度不匹配问题,打印数据缓冲区;CPU资源竞争)
页面替换算法
舞伴问题
一、舞伴问题
对于舞伴配对问题,先入队的男士或女士先出队配成舞伴,因此设置两个队列分别存放男士和女士入队者。假设男士和女士的记录存放在一个数组中作为输入,然后依次扫描该数组的各元素,并根据性别来决定是进入男队还是女队。当这两个队列构造完成之后,依次将两队当前的队头元素出队来配成舞伴,直至某队列变空为止。此时,若某队仍有等待配对者,则输出此队列中排在队头的等待者的姓名,此人将是下一轮舞曲开始时第一个可获得舞伴的人。
时间和空间复杂度均为O(n)
tips
递归算法与非递归算法:
递归算法中有大量的重复运算,效率会低些
消除递归不一定要用栈:
对于单向递归和尾递归而言,可用迭代的方式消除递归
栈和队列的比较: