文章目录
3. 栈
3.1 栈的逻辑结构
栈是限定插入和删除只能在“固定端”进行的线性表。
ADT Stack{
数据对象:
D={ai|ai∈ElemSet,i=1,2,....n,n>=0}
数据关系:
R1={<ai-1,ai>|ai-1,ai∈D,i=2,...,n}
基本操作:
InitStack(&S)
DestroyStack(&S)
StackLength(S)
StackEmpty(S)
GetTop(S,&e)
ClearStack(&S)
Push(&S,e)
Pop(&S,e)
StackTravers(S,visit())
}ADT Stack
3.1.1 例题
- 栈中的元素是先进先出,后进后出(x)
- 栈有1个插入位置和2个删除位置(x)
- 栈底不进行元素的删除操作(X)
3.2 栈的顺序存储表示
#define STACK_INIT_SIZE 100;
#define STACKINCREMENT 10;
//栈的顺序存储表示
typedef struct{
SElemType *base;
SElemType *top;
int stacksize;
}SqStack;
//顺序栈的初始化
Status InitStack(SqStack &S){
//构造一个空栈S
S.base=(ElemType*)malloc(STACK_INIT_SIZE*sizeof(ElemType));
if(!S.base)exit(OVERFLOW);//存储分配失败
S.top=S.base;//栈空的标志
S.stacksize=STACK_INIT_SIZE;
return OK;
}
//顺序栈的插入
Status Push(SqStack &S,SElemType e){
if(S.top-S.base>=S.stacksize){
S.base=(ElemType*)realloc(S.base,(S.stacksize+STACKINCREMENT)*sizeof(ElemType));
if(!S.base)exit(OVERFLOW);
S.top=S.base+S.stacksize;
S.stacksize+=STACKINCREMENT;
}
*S.top++=e;
return OK;
}
//顺序栈的删除
Status Pop(SqStack &S,SElemType e){
//若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK
//否则返回ERROR
if(S.top==S.base)return ERROR;
e=*--S.top;
return OK;
}
3.2.1例题
-
顺序栈中的元素进制的顺序是 1、2、3、4、5、6,那么元素出栈的顺序不可能是
2 3 5 1 4 6
3.3 栈的应用
3.3.1 编辑命令行
“每接受一个字符即存入存储器” 并不恰当!
在用户输入一行的过程中,运行用户输入出 差错,并在发现有误时可以即时更正。
合理的作法是:设立一个输入缓冲区,用以接受用户输入的一行字符,然后逐行存入用户数据区,并假设“#”为退格符,“@”为退行符。
假设从终端接受了这样两行字符:
whli##ilr#e(s#*s)
outcha@putchar(*s=#++);
实际有效的是这两行:
while(*s)
putchar(*s++);
编辑命令行的算法
void LineEdit(){
InitStack(S);
ch=getchar(); //从终端接受下一个字符
while(ch!=EOF){ //以EOF为全文结束符
while(ch!=EOF&&ch!='\n'){ //读入每一行
switch(ch){
case'#':Pop(S,c);break;
case'@':ClearStack(S);break;
default:Push(S,ch);break;
}
ch=getchar();//从终端接受下一字符
}
//将从栈底到栈顶的字符传送至调用过程的数据区
ClearStack(S); //重置S为空栈
if(ch!=EOF)ch=getchar();
}
DestroyStack(S);
}//LineEdit
3.3.1.1 例题
假设从终端接受了这样两行字符:
*s.#.top####-+#>s#top:=##=x;
top+@o#s-><#top++;
实际的有效输入:
*s->top=x;
s->top++;
3.3.2 数值转换
算法原理:N=(N div d)x d+N mod d
//数值转换算法
void conversion(){
InitStack(S);
scanf("%d",N);
while(N){
Push(S,N%8);
N=N/8;
}
while(!StackEmpty(S)){
Pop(S,e);
printf("%d",e);
}
}
3.3.3 括号匹配
原理:
-
凡是出现左括弧,则进栈
-
凡是出现右括弧:
若栈空: 则多余
若栈不空,则与栈顶元素比较,若相匹配,则左括弧出栈,否则不匹配
-
表达式检验结束时,若栈空,则表明表达式中匹配正确,否则表明“左括弧”有余。
bool BracketCheck(char str[],int length)
{
SqStack s;
InitStack(s);
int i;
for(i=0;i<length;i++){
if(str[i]=="("||str[i]=="["||str[i]=="{"){//左括号进栈
push(&s,str[i]);
}
else{
if(StackEmpty(&s))//如果是右括号且栈空,则不匹配
return false;
char topElem;
Pop(&s,topElem); //判断栈顶的左括号与右括号是否匹配
if(str[i]==")"&&topElem!="(")
return flase;
if(str[i]=="]"&&topElem!="[")
return flase;
if(str[i]=="}"&&topElem!="{")
return flase;
}
}
return StackEmpty(&s);//如果栈空,则匹配成功
}
3.3.3.1 例题
< ( { [ ] } ( [ ( ) ] ) ) > √
{ ( ( ) [ < >]){ } { } X
( ( ) } [ < >]){ } { } X
3.3.4 迷宫求解
常用穷举求解的方法
若当前位置“可通”,则纳入路径,继续前进;
若当前位置“不可通”,则后退,换方向继续探索;
若四周“均无通路”,则将当前位置从路径中删除出去。
求迷宫中一条从入口到出口的路径的算法:
设定当前位置的初值为入口位置;
do{
若当前位置可通,
则{将当前位置插入栈顶;
若该位置是出口位置,则算法结束;
否则切换当前位置的东邻方块为
新的当前位置;
}
否则 { ……
}
}while (栈不空);
若栈空,则表明迷宫没有通路。
// ……
若栈不空且栈顶位置尚有其他方向未被探索,
则设定新的当前位置为: 沿顺时针方向旋转找到的栈顶位置的下一相邻块;
若栈不空但栈顶位置的四周均不可通,
则{删去栈顶位置; // 从路径中删去该通道块
若栈不空,则重新测试新的栈顶位置,
直至找到一个可通的相邻块或出栈至栈空;
}
3.3.5 表达式求值
- 运算符的优先级等级比较
c1\c2 | + | - | * | / | ( | ) | # |
---|---|---|---|---|---|---|---|
+ | > | > | < | < | < | > | > |
- | > | > | < | < | < | > | > |
* | > | > | > | > | < | > | > |
/ | > | > | > | > | < | > | > |
( | < | < | < | < | < | = | |
) | > | > | > | > | > | > | |
# | < | < | < | < | < | > |
1.建立一个运算符栈和一个操作数栈。
2.首先在oprt栈中压入一个‘#’,用以提示程序表达式的边界。
3.从左向右扫描
①如果读到的是数字字符,先将这些数字字符按顺序存储到缓冲区(temp栈),直到读到运算符时,将缓冲区的数字按照权重相加,构造出完整的运算数,压入运算数栈中去,同时清空temp栈。
②如果读到的是运算符,那么按照规则比较oprt栈顶运算符和此运算符的优先级:(下列三种情况)
a.oprt栈顶运算符高于此运算符,则表明此时已经可以进行计算。将num栈依次出栈两个元素A、B,计算B(op)A,将结果压入num栈。
b.oprt栈顶运算符低于此运算符,则先将这个运算符入栈,然后继续读取。
c.oprt栈顶运算符优先级等于此运算符,例如左右括号匹配时、表达式头‘#’和尾‘#’匹配时,则不将此运算符入栈,反而将oprt栈顶运算符出栈。
重复上述①②操作,直到oprt栈顶元素为‘#’或读取到的字符为‘#’(结束)。此时num栈顶元素即为此表达式的结果。
OperandType EvaluateExpression(){
InitStack(OPTR,"#");//OPTR是运算数栈
InitStack(OPND);c=getchar();//OPND是运算符栈
while(c!='#'||GetTop(OPTR)!='#'){
if(!ln(c,OP)){
Push(OPND,c);
c=getchar();
else{
}//while
return GetTop(OPND)
}
}
Push(OPTR)
}
//
switch(Precede(GetTop(OPTR),c)){
case'<':Push(OPTR,c);c=getchar();break;
case'=':Push(OPTR,x);c=getchar();break;
case'>':Pop(OPTR,theta);Pop(OPND,b);Pop(OPND,a);//退栈
Push(OPND,Operate(a,theta,b));break;//将运算符结果入栈
}//switch
3.3.5.1 前缀表达式
前缀表达式:- + 1 2 × 3 4 (最后一个运算符与右边两个操作数结合进行运算,依次计算下去)
- 从右往左扫描表达式扫到数字就让数字入栈,
- 扫到运算符就让栈顶的两个数出栈,用该运算符对它们做相应的计算,并将结果入栈
- 重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果
3.3.5.2 中缀表达式
中缀表达式:1+2-3×4
计算过程 如上述代码;
3.3.5.3 后缀表达式
后缀表达式:3 4 × 1 2 + - (第一个运算符与左边两个操作数结合进行运算,依次计算下去)
计算过程与前缀表达式类似,只是顺序是从左至右:
- 从左至右扫描表达式遇到数字时,将数字压入堆栈,
- 遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素op 栈顶元素 ),并将结果入栈
- 重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果
3.4 用栈实现递归
-
当在一个函数的运行期间调用另一个函数时,在运行该被调用函数之前,需先完成三项任务:
将所有的实在参数、返回地址等信息传递给被调用函数保存;
为被调用函数的局部变量分配存储区;
将控制转移到被调用函数的入口。
-
从被调用函数返回调用函数之前,应该完成下列三项任务:
保存被调函数的计算结果;
释放被调函数的数据区;
依照被调函数保存的返回地址将控制转移到调用函数。
后调用先返回 !
汉诺塔问题:
void hanoi (int n, char x, char y, char z) {
// 将塔座x上按直径由小到大且至上而下编号为1至n
// 的n个圆盘按规则搬到塔座z上,y可用作辅助塔座。
if (n==1)
move(x, 1, z); // 将编号为1的圆盘从x移到z
else {
hanoi(n-1, x, z, y); // 将x上编号为1至n-1的圆盘移到y, z作辅助塔
move(x, n, z); // 将编号为n的圆盘从x移到z
hanoi(n-1, y, x, z); // 将y上编号为1至n-1的圆盘移到z, x作辅助塔
}
}
3.5八皇后问题
#include<stdio.h>
#define n 8 // n为皇后个数,m为摆法计数
int m=0,a[n]; // a[i]存放第i个皇后放置的行号
int ok(int i, int j) //检查(i,j)上能否放棋子
{ j1=j; i1=i; ok1=1; //检查第i行上能否放棋子
while( (j1>1)&&ok1)
{j1--; ok1=a[j1]!=i ;}
j1=j; i1=i; //检查对角线上能否放棋子
while( (j1>1)&&(i1>1)&&ok1)
{j1--; i1--; ok1=a[j1]!=i1 ;}
j1=j; i1=i; //检查另一对角线上能否放棋子
while( (j1>1)&&(i1<n)&&ok1)
{j1--; i1++; ok1=a[j1]!=i1 ;}
return ok1
}
Void queen(int j) //从第j列开始逐个试探
{ if (j>n)
{ m++; printf("m=%d ",m);
for (i=1;i<=n;i++) printf(" %d",a[i]);
printf(“\n”);
}
else for( i=1; i<=n;i++)
if(ok(i,j)) //检查(i,j)上能否放棋子
{ a[j]=i; //在(i,j)上放一个棋子
queen(j+1) ;
}
}
main()
{queen(1);}
3.6 四色问题
“四染色”定理是计算机科学中著名的定理之一。使地图中相邻的国家或行政区域不重色,最少可用四种颜色对地图着色。利用栈采用回溯法对地图着色。
思想:对每个行政区编号:1-7;对颜色编号;a、b、c、d。从第一号行政区开始逐一染色,每一个区域逐次用四种颜色进行试探,若所取颜色与周围不重,则用栈记下来该区域的色数,否则依次用下一色数进行试探。若出现a-d均与周围发生重色,则需退栈回溯,修改当前栈顶的色数。
void mapcolor(int R[ ][ ],int n,int s[ ])
{ s[1]=1; // 1号区域染1色
a=2; J=1; // a为区域号,J为染色号
while ( a<=n)
{ while(( J<=4)&&(a<=n))
{ k=1; // k表示已经着色的区域号
while(( k<a)&&(s[k]*R[a-1][k-1]!=J)) k=k+1; // 若不相邻,或若相邻且不重色,对下一个相邻已染色区域判断
if (k<a) J=J+1; //相邻且重色
else { s[a]=J; a=a+1; J=1; } //相邻且不重色
}
if (J>4) { a=a-1; J=s[a]+1 } //回到上一个区域,选择下一号颜色试探
}
}