第三章 栈和队列(3.6)
3.6案例分析和实现
案例3.1:数制的转换
1.案例分析
当将一个十进制整数N转换为八进制数时,在计算过程中,把N与8求余得到的八进制数的各位依次进栈,计算完毕后将栈中的八进制数依次出栈输出,输出结果就是待求得的八进制数
2.算法步骤
1、初始化一个空栈S。
2、当十进制数N非零时,循环执行以下操作:
把N与8求余得到的八进制数压入栈S;
N更新为N与8的商。
3、当栈S非空时,循环执行以下操作:
弹出栈顶元素e;
输出e。
3.算法描述
void conversion(int n)
{//对于任意一个非负十进制数,打印输出与其等值的八进制数
InitStack(S); //初始化栈
while(N) //当N非零时,循环
{
Push(S,N%8); //把N与8求余得到的八进制数压入栈S
N=N/8; //N更新为N与8的商
}
while(!StackEmpty(S)) //当栈为非空时,循环
{
Pop(S,e); //弹出栈顶元素e
cout<<e; //输出e
}
}
4.算法分析
显然,该算法的时间和空间的复杂度均为O(log8^n).
这是利用栈的后进先出特征的最简单的例子。在这个例子中,栈的操作是单调的,即先一味地入栈,然后一味地出栈。也许,有的读者会提出疑问:用数组直接实现不是更简单吗?但仔细分析上述算法不难看出,栈的引入简化了程序设计的问题,划分了不同的关注层次,使思考范围缩小了。而数组不仅掩盖了问题的本质,还要分散精力去思考数组下标增减等细节问题。
案例3.2:括号匹配的检验
1.案例分析
检验算法借助一个栈,每当读入一个左括号,则直接入栈,等待相匹配的同类右括号;每当读入一个右括号,若与当前栈顶的左括号类型相同,则二者匹配,将栈顶的左括号出栈,直到表达式扫描完毕在处理过程中,还要考虑括号不匹配出错的情况。
2.算法步骤
①初始化一个空栈S
②设置一标记性变flag,用来标记匹配结果以控制循环及返回结果,1表示正确匹配,0表示错误匹配,fag初值为1。
③扫描表达式,依次读入字符ch,如果表达式没有扫描完毕或flag非零,则循环执行以下操作: 若ch是左括号“[”或“(",则将其压入栈;
若ch是右括号“)”,则根据当前栈顶元素的值分情况考虑:若栈非空且栈顶元素是“(”,则正确匹配,否则错误匹配,fag置为0;
若ch是右括号“]”,则根据当前栈顶元素的值分情况考虑:若栈非空且栈顶元素是"[",则正确匹配,否则错误匹配,flag置为0。④退出循环后,如果栈空且fag值为1,则匹配成功,返回true,否则返回false
3.算法描述
bool Matching()
{
stack<char> s; // 定义一个stack容器
char ch, temp;
int flag = 1; // 标记匹配结果以控制循环及返回结果
cin >> ch; //读入第一个字符
while (ch != '#' && flag) //假设表达式以“#”结尾
{
switch (ch)
{
case '[':
case '(': // 若是左括号,则将其压入栈
s.push(ch);
break;
case ')': //若是“)”,则根据当前栈顶元素的值分情况考虑
if (!s.empty() && s.top() == '(') // 若栈非空且栈顶元素是“(”,则正确匹配
{
temp = s.top();
s.pop();
}
else
flag = 0; // 若栈空或栈顶元素不是“(”,则错误失败
break;
case ']': //若是“]”,则根据当前栈顶元素的值分情况考虑
if (!s.empty() && s.top() == '[') // 若栈非空且栈顶元素是“[”,则正确匹配
{
temp = s.top();
s.pop();
}
else
flag = 0; //若栈空或栈顶元素不是“[”,则错误匹配
break;
} //switch
cin >> ch; //继续读入下一个字符
} //while
if (s.empty() && flag)
return true; // 匹配成功
else
return false; // 匹配失败
}
案例3.3:表达式求值
1.案例分析
任何一个表达式都是由操作数(operand)运算符(operator)和界限符(delimiter)组成的,统称它们为单词。一般地,操作数既可以是常数,也可以是被说明为变量或常量的标识符;运算符可以分为算术运算符、关系运算符和逻辑运算符 3 类;基本界限符 有左右括号和表达式结束符等。为了叙述的简洁,在此仅讨论简单算术表达式的求值问题,这种表达式只含加、减、乘、除4种运算符。读者不难将它推广到更一般的表达式上。
下面把运算符和界限符统称为算符。
我们知道,算术四则运算遵循以下 3条规则:
(1)先乘除,后加减;
(2)从左算到右;
(3)先括号内,后括号外。
根据上述 3条运算规则,在运算的每一步中,任意两个相继出现的算符θ1和θ2之间的优先关系,至多是下面 3 种关系之一 :
θ1 < θ2 θ1的优先权低于θ2
θ1 = θ2 θ1的优先权等于θ2
θ1 > θ2 θ1的优先权高于θ2
表 3.1 定义了算符之间的这种优先关系。

由规则(1), 先进行乘除运算,后进行加减运算,所以有 “+” < “”; “+” < “/”; “” >"+"; “/” > “+” 等。
由规则(2), 运算遵循左结合性,当两个运算符相同时,先出现的运算符优先级高,所以有"+" > “+”; “-” > “-”; “” > “”; “/” > “/”。
由规则(3), 括号内的优先级高,+、-、*和/为θ1时的优先性均低千 (" “但高于 " )”。
表中的 “(” = “)” 表示当左右括号相遇时,括号内的运算已经完成。为了便千实现,假设每个表达式均以"#“开始,以”#" 结束。所以"#" = “#” 表示整个表达式求值完毕。")“与 “(”、"#”与”)" 以及"(“与”#" 之间无优先关系,这是因为表达式中不允许它们相继出现,一旦遇到这种情况,则可以认为出现了语法错误。在 下面的讨论中,我们暂假定所输人的表达式不会出现语法错误。
2.算法步骤
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栈顶元素即为表达式求值结果,返回此元素。
3.算法描述
char EvaluateExpression ()
{//算术表达式求值的算符优先算法,设OPTR和OPND分别为运算符栈和操作数栈
InitStack(OPND); //初始化OPND栈
InitStack(OPTR); //初始化OPTR栈
Push (OPTR,'#') ; // 将表达式起始符"#" 压人OPTR栈
cin>>ch;
while(ch!='#' | | GetTop(OPTR) !='#' ) //表达式没有扫描完毕或OPTR的栈顶元素不为"# "
{
if (!In (ch)) {Push (OPND, ch); cin»ch;} //ch不是运算符则进OPND栈
else
switch (Precede (GetTop (OPTR) , ch)) //比较OPTR的栈顶元素和ch的 优先级
{
case'<':
Push(OPTR,ch);cin>>ch; //当前字符ch压入OPTR栈,读入下一字符ch
break;
case'>':
Pop(OPTR,theta); //弹出OPTR栈顶的运算符
Pop(OPND,b);Pop(OPND,a); //弹出OPND栈顶的两个运算数
Push (OPND, Operate (a, theta, b·)); / /将运算结果压入OPND栈
break;
case'=': //OPTR的栈顶元素是"("且ch是")"
Pop(OPTR,x) ;cin>>ch; //弹出OPTR栈顶的"(", 读入下一字符ch
break;
}//switch
}//while
return GetTop (OPND) ; //OPND栈顶元素即为表达式求值结果
}
案例3.4:舞伴问题
1.案例分析
对千舞伴配对问题,先入队的男士或女士先出队配成舞伴,因此设置两个队列分别存放男士和女士入队者。假设男士和女士的记录存放在一个数组中作为输入,然后依次扫描该数组的各元素,并根据性别来决定是进入男队还是女队。 当这两个队列构造完成之后,依次将两队当前的队头元素出队来配成舞伴,直至某队列变空为止。 此时,若某队仍有等待配对者,则输出此队列中排在队头的等待者的姓名,此人将是下一轮舞曲开始时第一个可获得舞伴的人。
2.算法步骤
1.初始化 Mdancers 队列和 Fdancers 队列。
2.反复循环,依次将跳舞者根据其性别插入 Mdancers 队列或 Fdancers 队列。
3.当 Mdancers 队列和 Fdancers 队列均为非空时, 反复循环, 依次输出男女舞伴的姓名。
4.如果 Mdancers 队列为空而 Fdancers 队列非空, 则输出 Fdancers 队列的队头女士的姓名。
5.如果 Fdancers 队列为空而 Mdancers 队列非空, 则输出 Mdancers 队列的队头男士的姓名。
3.算法描述
void DancePartner(Person dancer[],int num)
{//结构数组dancer中存放跳舞的男女,num是跳舞的人数。
InitQueue(Mdancers); //男士队列初始化
InitQueue(Fdancers); //女士队列初始化
for(i=0;i<num;i++) //依次将跳舞者根据其性别人队
{
p=dancer[i];
if (p.sex=='F') EnQueue(Fdancers, p); / /插入女队
else EnQueue(Mdancers,p); //插人男队
}
cout<<"The dancing partners are:\n";
while(!QueueEmpty(Fdancers)&&!QueueEmpty(Mdancers))
{//依次输出男女舞伴的姓名
DeQueue(Fdancers,p); //女士出队
cout<<p. name<<" "; //输出出队女士姓名
DeQueue(Mdancers,p); //男士出队
cout<<p.name<<endl; //输出出队男士姓名
}
if (! QueueEmpty (Fdancers)) //女士队列非空,输出队头女士的姓名
{
p=GetHead(Fdancers); //取女士队头
cout<<"The first woman to get a partner is: "<< p.name<<endl;
}
else if (!QueueEmpty (Mdancers)) //男士队列非空,输出队头男士的姓名
{
p=GetHead(Mdancers); //取男士队头
cout<<"The first man to get a partner is: "<< p.name<<endl;
}
}

浙公网安备 33010602011771号