本系列文章是笔者复习自用,参考教材为严蔚敏编著的《数据结构(C语言版)》,如能对大家有所帮助实属荣幸
本篇的内容主要是栈的线性表示和实现,主要包括栈的定义和常用函数的实现。
4.1 抽象类型栈的类型定义
栈和队列是两种很重要的线性结构,是两种特殊的线性表,本文先介绍一下栈。
栈是只能在表尾进行插入和删除操作的线性表,对栈来说,第一个元素所在的地方称为栈顶,最后一个元素所在的地方成为栈顶。正是因为栈只能在栈顶进行插入删除操作的特性,使得后进入栈的数据必须先被删除,新数据被删除后旧数据才能被删除,对于栈来说,插入操作被称为入栈,删除操作被称为出栈。总的来说,栈的特点就是后进先出。
可以将栈看作一个只有一个开口的管道,想要取出底部的元素,只能先取出距离开口处最近的元素。
下面是栈的一些概念。
栈顶(top):按照线性表来说属于表尾,是可以进行插入和删除的一端。
栈底(base):按照线性表来说属于表头,是不能进行插入删除操作的一端。
入栈(push):相当于线性表的插入操作,将栈顶后移一位。
出栈(pop):相当于线性表的删除操作,栈顶前移一位,并不是真的删去。
4.2 栈的表示和实现
1.栈的定义
通常来说,顺序栈的定义中有三个成员,分别是代表指向栈底的指针top,指向栈顶的指针base和记录已分配给栈的空间大小的stacksize。base始终不改变,入栈出栈时top改变,重新分配空间时stacksize改变。
typedef struct{
ElemType *base;//栈底指针,初始化之前为NULL
ElemType *top;//栈顶指针,该指针指向栈中最顶端元素的后一位空间,这空间不属于栈
int stacksize;//已分配的储存空间,不等于栈实际占用的
}SqStack;
2.栈的初始化
在栈的初始化过程中,需要给栈分配一个初始空间,base指向这段空间的开头,由于是空表,top和base指向同一段空间,并且,stacksize的大小等于初始空间的大小。
Status InitStack(SqStack &S){
S.base = (ElemType *)malloc(InitSize*sizeof(ElemType));//为栈申请初始大小的空间
if (!S.base) exit(ERROR);//创建失败则退出
S.top = S.base;
S.stacksize = InitSize;
return OK;
}
3.获取栈顶元素
获得栈顶第一个元素的值,不过,top指针一般指向位于栈中最新元素的后一位,所指的空间不属于栈,所以获取的栈顶元素应该是top-1所指空间的值。
ElemType GetTop(SqStack S){
if (S.base == S.top) exit(ERROR);//如果栈底等于栈顶,则栈空
return *(S.top - 1);
}
4.进栈
进栈操作指的是让一个新的元素进入栈内,使其成为栈顶。
算法思想为,首先判断栈是否已满,若满了,给栈重新分配空间,确保栈内有足够空间之后,将值赋给top所指空间,后移top使其指向最后一个元素的后一位。
Status PushStack(SqStack &S, ElemType e){
//进栈首先判断栈是否满,满则需要重新分配空间
if (S.top - S.base >= S.stacksize){
S.base = (ElemType *)realloc(S.base,(S.stacksize + IncreaseSize)*sizeof(ElemType));
if (!S.base) exit(ERROR);
S.top = S.base + S.stacksize; //由于realloc返回的很可能是一个新的地址,所以top指针需要进行迁移
S.stacksize += IncreaseSize;
}
*S.top++ = e;//先给之前top的空间赋值,再top往后推
return OK;
}
其中,S.top=S.base+S.stacksize语句值得注意。ralloc函数在执行时会判断原位置后是否有足够的连续空间,若有则直接使用后面的空间,若没有,则会在一个新的地址创建空间,然后将原数据复制过来。如果缺少此语句,top指针会停留在原地址,就有可能出错。所以,这个语句必不可少。
5.入栈
入栈操作指的是将栈顶的一个元素删去。
算法思想为,首先判断栈是否为空,若为空则退出,否则返回top所指向空间的值,并使top前移一位。
ElemType PopStack(SqStack &S){
//出栈首先判断是否为空,为空则不进行操作
if (S.base == S.top) exit(ERROR);
ElemType e = *(S.top - 1);
S.top--;
return e;
}
4.3 栈的相关应用
由于后进先出特性,栈这一数据类型有很多独特的应用领域,下面举出一些。
1.数制转换
众所周知,十进制数和其他进制数的转换是计算机实现计算的基本问题,下面是其中一种解决方法。
N=(N div d)×d+N mod d(其中。N为十进制数,d为转换后的进制,div为整除计算,mod为求余计算)
例如,1348转换为8进制,计算过程如下:
答案为八进制下的2504 。
这个计算过程与栈的关系为,进行求余计算之后,将结果存入栈中,直到计算结束,使结果逐个出栈。根据后进先出,若求余的结果依次为4、0、5、2,则最终输出的结果为2504,完成进制转换。代码如下:
void conversion(int N,int d){
int e;
SqStack S;
InitStack(S);
while (N)//num等于0时停止
{
PushStack(S, N % d);//将求余所得的数存入栈
N=N/8;
}
while (S.base!=S.top)
{
e = PopStack(S);
printf("%d", e);
}
}
2.括号匹配的验证
假设表达式中允许包含两种括号,方括号和圆括号,其套嵌的顺序随意,但必须匹配,即[(])这样的不匹配,写程序判断一个表达式是否合规。
其算法思想是,遍历该字符串,如果遇到左括号,则直接入栈;如果遇到右括号,则判断其是否与栈顶符号匹配,若匹配则出栈,否则什么都不做。如果遍历完成后,栈为空,则该表达式合法。
void main(){
SqStack S;
char c[10]="[[[123]]]";
char *p=c;
InitStack(S);
while(*p!=NULL)
{
if (*p == '(' || *p == '[')
PushStack(S, *p);
else if (*p == ']' && GetTop(S) == '[')
PopStack(S);
else if (*p == ')' && GetTop(S) == '(')
PopStack(S);
Print(S);
printf("\n");
p++;
}
if (S.base == S.top)
printf("该表达式合法");
else
printf("该表达式不合法");
}