第三章--栈和队列
栈和队列是操作受限的线性表。因此可称为限定性的数据结构。
□3.1 栈
栈是限定仅在表尾进行插入或删除操作的线性表。因此对栈来说表尾端有其特殊含义,称为栈顶(top),相应地表头端称为栈底(bottom)。不含元素的空表称为空栈。栈又称为后进先出(last in first out)的线性表(LIFO结构)。
□3.1.2 栈的表示和实现
顺序栈,即栈的顺序存储结构是利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时附设指针top指示栈顶元素在顺序栈中的位置。通常习惯做法是以top = 0表示空栈。
一般来说在初始化设空栈时不应限定栈的最大容量。一个较合理的做法是:先为栈分配一个基本容量,然后在应用过程中,当栈的空间不够使用时再逐段扩大。
/*存储空间初始分配量*/
#define STACK_INIT_SIZE 100
/*存储空间分配增量*/
#define STACKINCREMENT 10
typedef char SElemType;
typedef struct{
/*在栈构造之前和销毁之后,base的值为NULL*/
SElemType *base;
/*栈顶指针*/
SElemType *top;
/*当前已经分配的存储空间,以元素为单位*/
int stacksize;
}SqStack;
/*构造一个空栈*/
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;
}
/*若栈不空,则用e返回S的栈顶元素,并返回OK,否则返回ERROR*/
Status GetTop(SqStack S, SElemType e)
{
if (S.top == S.base){
return ERROR;
}
/*栈顶后面下一个元素*/
e = *(S.top - 1);
return OK;
}
Status Pop(SqStack *S, SElemType *e)
{
if ((*S).top == (*S).base){
return ERROR;
}
*e = *--(*S).top;
return OK;
}
/*插入元素e为新的栈顶元素*/
Status Push(SqStack *S, SElemType e)
{
/*插入元素e为新的栈顶元素*/
if ((*S).top - (*S).base >= (*S).stacksize){
(*S).base = (SElemType*)realloc((*S).base, ((*S).stacksize + STACKINCREMENT) * sizeof(SElemType) );
/*存储分配失败*/
if(!(*S).base){
exit(OVERFLOW);
}
(*S).top = (*S).base + (*S).stacksize;
(*S).stacksize += STACKINCREMENT;
}
*((*S).top)++ = e;
return OK;
}
Status DestroyStack(SqStack *S)
{ /* 销毁栈S,S不再存在 */
free((*S).base);
(*S).base = NULL;
(*S).top = NULL;
(*S).stacksize = 0;
return OK;
}
Status ClearStack(SqStack *S)
{ /* 把S置为空栈 */
(*S).top = (*S).base;
return OK;
}
Status StackEmpty(SqStack S)
{ /* 若栈S为空栈,则返回TRUE,否则返回FALSE */
if(S.top == S.base){
return TRUE;
}
else{
return FALSE;
}
}
int StackLength(SqStack S)
{ /* 返回S的元素个数,即栈的长度 */
return S.top - S.base;
}
Status StackTraverse(SqStack S,Status(*visit)(SElemType))
{ /* 从栈底到栈顶依次对栈中每个元素调用函数visit()。 */
/* 一旦visit()失败,则操作失败 */
while(S.top > S.base){
visit(*S.base++);
}
printf("\n");
return OK;
}
Status visit(SElemType c)
{
printf("%d ",c);
return OK;
}
每当插入新的栈顶元素时,指针top增加1;删除栈顶元素时,指针top减去1,因此非空栈中的栈顶指针始终在栈顶元素的下一个位置上。
□3.2 栈的应用举例
用数组直接实现栈的功能不是更简单吗?栈的引入简化了程序设计的问题,划分了不同的关注层次,使思考范围缩小了。而用数据不仅掩盖了问题的本质,还要分散精力去考虑数组下标增减等细节问题。
□3.2.1 数制转换
十进制数N和其他d进制数的转换是计算机实现计算的基本问题,N = (N div d)×d + N mod d;
对于输入的任意一个非负十进制整数,打印输出与其等值的八进制数。
/*对于输入的任意一个非负十进制整数,打印输出与其等值的八进制数*/
void conversion()
{
SqStack S;
int N = 0;
int e;
InitStack(&S);
scanf("%d", &N);
while ( N ){
Push(&S, N % 8);
N = N / 8;
}
while (!StackEmpty(S)){
Pop(&S, &e);
printf("%d", e);
}
}
□3.2.3 行编辑程序
一个简单的功能是:接受用户从终端输入的程序或数据,并存入用户的数据区。当用户发现刚刚键入的一个字符是错的时,可补进一个退格符"#",以表示前一个字符无效;如果发现当前键入的行内差错较多或难以补救,则可以键入一个退行符"@"以表示当前行中的字符均无效。如果它既不是退格符也不是退行符,则将该字符压入栈顶。
/*利用字符栈S,从终端接受一行并传送至调用过程的数据区*/
void LineEdit()
{
SqStack S;
char ch;
InitStack(&S);/*构造空栈*/
ch = getchar();/*从终端接收一个字符*/
while(ch != EOF){ /*EOF为全文结束符*/
while (ch != EOF && ch != '/n'){
switch(ch){
case '#':
Pop(&S, &ch);/*仅当栈非空时退栈*/
break;
case '@':
ClearStack(&S);/*重置S为空栈*/
break;
default:
Push(&S, ch);/*有效字符进栈,未考虑栈满的情形*/
break;
}
ch = getchar();/*从终端接收下一个字符*/
}
/*将从栈底到栈顶的栈内存字符传送至调用过程的数据区*/
ClearStack(&S);/*重置S为空栈*/
if (ch != EOF){
ch = getchar();
}
}
DestroyStack(&S);
}