第三章 栈和队列
1 栈
1)抽象数据类型栈的定义
栈是限定仅在表尾进行插入或删除操作的线性表。
ADT Stack
{
数据对象:D = {
|
∈ElemSet, i = 1, 2, …,n, n≥0}
数据关系:R1 = {<
,
>|
,
∈D, i = 2, …, n}
约定
端为栈顶,
端为栈底。
基本操作:
InitStack(&S)
操作结果:构造一个空栈S。
DestroyStack(&S)
初始条件:栈S已存在。
操作结果:栈S被销毁。
ClearStack(&S)
初始条件:栈S已存在。
操作结果:将S清为空栈。
StackEmpty(S)
初始条件:栈S已存在。
操作结果:若栈S为空栈,则返回TRUE,否则FALSE。
StackLength(S)
初始条件:栈S已存在。
操作结果:返回S的元素个数,即栈的长度。
GetTop(S, &e)
初始条件:栈S已存在且非空。
操作结果:用e返回S的栈顶元素。
Push(&S, e)
初始条件:栈S已存在。
操作结果:插入元素e为新的栈顶元素。
Pop(&S, &e)
初始条件:栈S已存在且非空。
操作结果:删除S的栈顶元素,并用e返回其值。
StackTraverse(S, visit())
初始条件:栈S已存在且非空。
操作结果:从栈底到栈顶依次对S的每个数据元素调用函数visit()。一旦visit()失 败,则操作失效。
}ADT Stack
2)栈的顺序存储表示
#define STACK_INIT_SIZE 100 //存储空间初始分配量
#define STACKINCREMENT 10 //存储空间分配增量
typedef struct
{
SElemType* base; //在栈构造之前和销毁之后,base的值为NULL
SElemType* top; //栈顶指针
int stacksize; //当前已分配的存储空间,以元素为单位
}SqStack;
Status InitStack(SqStack &S); //构造一个空栈S
Status DestroyStack(SqStack &S); //销毁栈S,S不再存在
Status ClearStack(SqStack &S); //把S置为空栈
Status StackEmpty(SqStack S); //若栈S为空栈,则返回TRUE,否则返回FALSE
int StackLength(SqStack S); //返回S的元素个数,即栈的长度
Status GetTop(SqStack S, SElemType &e);
//若栈不空,则用e返回S的栈顶元素,并返回OK,否则返回ERROR
Status Push(SqStack &S, SElemType e);
//插入元素e为新的栈顶元素
Status Pop(SqStack &S, SElemType &e);
//若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR
Status StackTraverse(SqStack S, Status (*visit()));
//从栈底到栈顶依次对栈中的每个元素调用函数visit()。一旦visit()失败,则操作失败
Status InitStack(SqStack &S)
{
S.base = (SElemType*) malloc(STACK_INIT_SIZE * sizeof(SElemType));
if (!S.base)
exit(OVERFLOW);
S.top = S.base;
S.stacksize = STACK_INIT_SIZE;
return OK;
}
Status GetTop(SqStack S, SElemType &e)
{
if (S.top == S.base)
return ERROR;
e = *(S.top - 1);
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)
{
if (S.top == S.base )
return ERROR;
e = * -- S.top;
return OK;
}
2 栈的应用举例
1)数制转换
基本原理:N =(N div d)* d + N mod d
例子:
,运算过程如下:
N N div 8 N mod 8
1348 168 4
168 21 0
21 2 5
2 0 2
程序:
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);
}
}
2)括号匹配的检验
在算法中设置一个栈,每读入一个括号,若是右括号,则或者使置于栈顶的最急迫的期待得以消解,或者是不合法的情况;若是左括号,则作为一个新的更急迫的期待压入栈中,自然使原有的在栈中的所有未消解的期待的急迫性都降了一级。另外在算法的开始和结束时,栈都应该是空的。
3)行编辑程序
一个简单的行编辑程序的功能是:接受用户从终端输入的程序或数据,并存入用户的数据区。较好的做法是,设立一个输入缓冲区,用以接受用户输入的一行字符,然后逐行存入用户数据区。允许用户输入出差错,并在发现有误时可以及时更正。
void LineEdit()
{
InitStack(S);
ch = getchar();
while (ch != 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);
if (ch != EOF) ch = getchar();
}
DestroyStack(S);
}
4)迷宫求解
设定当前位置的初值为入口位置;
do
{
若当前位置可通,则
{
将当前位置插入栈顶;
若该位置出口位置,则结束;
否则切换当前位置的东邻方块为新的当前位置;
}
否则,
若栈不空且栈顶位置沿有其他方向未经探索,
则设定新的当前位置为沿顺时针方向旋转找到的栈顶位置的下一相邻块;
若栈不空但栈顶位置的四周均不可通,则
{
删去栈顶位置;
若栈不空,则重新测试新的栈顶位置,
直至打到一个可通的相邻块或出栈至栈空;
}
}while (栈不空);
程序:
typedef struct
{
int ord; //通道块在路径上的“序号”
PosType seat; //通道块在迷宫中的“坐标位置”
int di; //从此通道块走向下一通道块的“方向”
}SElemType; //栈的元素类型
Status MazePath(MazeType maze, PosType start, PosType end)
{
InitStack(S);
curpos = start; //设定“当前位置”为“入口位置”
curstep = 1; //探索第一步
do
{
if (Pass(curpos)) //当前位置可以通过,即是未曾走到过的通道块
{
FootPrint(curpos); //留下足迹
e = (curstep, curpos, 1);
Push(S, e); //加入路径
if (curpos == end)
return TRUE; //到达终点(出口)
curpos = NextPos(curpos, 1);
curstep ++;
}
else //当前位置不能通过
{
if (!StackEmpty(S))
{
Pop(S, e);
while (e.di == 4 && !StackEmpty(S))
{
MarkPrint(e.seat);
Pop(S, e);
}
if (e.di < 4)
{
e.di ++;
Push(S, e);
curpos = NextPos(e.seat, e.di);
}
}
}
}while (!StackEmpty(S));
return (FALSE);
}
5)表达式求值(算符优先法)
使用两个工作栈。一个称做OPTR,用以寄存运算符;另一个称做OPND,用心寄存操作数或运算结果。算法的基本思想是:
a. 首先置操作数栈为空栈,表达式起始符“#”为运算符栈的栈底元素;
b. 依次读入表达式中每个字符,若是操作数则进OPND栈,若是运算符,则和OPTR栈的栈顶运算符比较优先权后作相应操作,直至整个表达式求值完毕(即OPTR栈的栈顶元素和当前读入的字符均为“#”)。
程序:
OperandType EvaluateExpression()
{
InitStack(OPTR);
Push(OPTR, ‘#’);
InitStack(OPND);
c = getchar();
while (c != ‘#’ | GetTop(OPTR) != ‘#’)
{
if (!In(c, OP))
{
Push(OPND, c); //不是运算符则进栈
c = getchar();
}
else
switch (Precede(GetTop(OPTR), c))
{
case ‘<’: //栈顶元素优先权低
Push(OPTR, c);
Push(OPTR, c);
c = getchar();
break;
case ‘=’: //脱括号并接收下一个字符
Pop(OPTR, x);
c = getchar();
break;
case ‘>’: //退栈并将运算结果入栈
Pop(OPTR, theta);
Pop(OPND, b);
Pop(OPND, a);
Push(OPND, Operate(a, theta, b));
break;
}
}
return GetTop(OPND);
}
3 栈与递归的实现
汉诺塔问题:
void Hanoi(int n, char x, char y, char z)
{
if (n == 1)
mov(x, 1, z);
else
{
hanoi(n - 1, x, y, z);
move(x, n, z);
hanoi(n – 1, y, x, z);
}
}