目录
栈(stack)
特点
-
是一个特殊的线性表,是限定仅在一端(通常是表尾)进行插入和删除操作的线性表。
-
栈 又称为 后进先出(Last In First Out)的线性表,简称LIFO结构。
-
表尾称为 栈顶 Top,表头称为 栈底 Base。
-
插入元素到 栈顶 的操作,称为 入栈。(压栈)(PUSH)
-
从 栈顶 删除最后一个元素的操作,称为 出栈。(弹栈)(POP)
逻辑结构
-
与线性表相同,仍为一对一关系。
存储结构
-
顺序栈或链栈,顺序栈更常见。
实现方式
-
主要是入栈和出栈函数,链栈和顺序栈不同。
栈与一般线性表的区别
一般线性表 | 栈 | |
---|---|---|
逻辑结构 | 一对一 | 一对一 |
存储结构 | 顺序表、链表 | 顺序栈、链栈 |
运算规则 | 随机存取 | 后进先出(LIFO) |
思考
-
假设有3个元素a,b,c,入栈顺序是a,b,c,则出栈顺序有几种可能?
-
利用栈实现进制转换(如十进制数转换为八进制数)。
-
扩号匹配的检验(括号的顺序、数量检查)(假设只有圆括号和方括号)。
顺序栈
栈的顺序存储:同一般线性表的顺序存储结构完全相同。利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素。栈底一般在低地址端。
-
附设 top 指针,指示栈顶元素在顺序栈中的位置。
-
另设 base 指针,指示栈底元素在顺序栈中的位置。
但是,为了方便操作,通常top指示真正的 栈顶元素之上 的下标地址。
-
另外,用stacksize表示栈可使用的最大容量。
使用数组作为顺序栈存储方式的特点:
简单、方便、但容易产生溢出(数组大小固定)。
上溢(overflow):栈已经满,又要压入元素。是一种错误,使问题处理无法进行。
下溢(underflow):栈已经空,还要弹出元素。一般不认为是错误,而是一种结束条件,即问题处理结束。
顺序栈的定义
#define MAXSIZE 100
typedef struct {
SElemType *base; //栈底指针
SElemType *top; //栈顶指针
int stacksize; //栈的最大容量
} SqStack;
但是顺序栈的top和base指针可以是 int 类型 用来存放数组下标,并不是真正意义上的指针变量。
存数组单元的地址也可以,在做一些操作时(比如top-base,得到的值是两个数组单元的距离),和存放下标的方式区别不大。
顺序栈的初始化
算法思路:
-
开辟一片大小为stacksize的空间
-
top和base都指向栈底
算法描述:
Status InitStack(SqStack &S) { //构造一个空栈
S.base = new SElemType[MAXSIEZE];
//或S.base = (SElemType*)malloc(MAXSIZE*sizeof(SElemType));
if(!S.base) exit (OVERFLOW); //存储分配失败
S.top = S.base; //栈顶指针等于栈底指针
S.stacksize = MAXSIZE;
return OK;
}
顺序栈的销毁
算法思路:
-
把整个栈空间释放掉
-
释放掉base指向的空间
-
把stacksize设置为0
-
算法描述:
Status DestroyStack(SqStack &S) {
if(S.base) {
delete S.base;
S.stacksize = 0;
S.base = S.top = NULL;
}
return OK;
}
判断顺序栈是否为空
算法思路:
-
top指针和base指针都指向栈底,此时top == base
算法描述:
Status StackEmpty(SqStack S) {
//若栈为空,返回TRUE,否则返回FALSE
if(S.top == S.base)
return TRUe;
else
return FALSE;
}
求顺序栈的长度
算法思路:
-
指针相减,差为两个指针的间隔,也就是元素个数。
算法描述:
int StackLength(SqStack S) {
return S,top - S.base;
}
取栈顶元素
算法思路:
-
判断栈是否存在并且非空
-
返回栈顶元素
算法描述:
Status GetTop(S, &e) {
if(S.top == S.base) return ERROR;
e = *(S.top-1);
return OK;
}
清空顺序栈
算法思路:
-
把栈当作是空就行了,也就是top也指向栈底位置。
算法描述:
Status ClearStack(SqStack S) {
if(S.base) S.top = S.base;
return OK;
}
顺序栈的入栈
算法思路
-
判断是否栈满,若满则出错(上溢)
-
将元素存到top所指的位置
-
top上移
算法描述:
Status Push(SqStack &S, SElemType e) {
if(S.top - S.base == S.stacksize) //栈满
return ERROR;
*S.top++ = e; //(*S.top=e;S.top++;)
return OK;
}
顺序栈出栈
算法思路:
-
判断是否栈空,若空则出错(下溢)
-
top下移
-
取top指针所指位置上的元素
算法描述:
Status Pop(SqStack &S, SElemType &e) {
//若栈不空,则“删除”栈顶元素,用e返回,“删除”成功返回OK,失败返回ERROR
if(S.top == S.base) //if(StackEmpty(S))
return ERROR;
e = *--S.top; //--S.top;e = *S.top;
return OK;
}
链栈
逻辑都是相似的。
-
链栈是运算受限的单链表,只能在链表头部进行操作。
几个注意的点:
-
链表的头指针就是栈顶
-
不需要头结点
-
基本不存在栈满的情况
-
空栈相当于头指针指向空
-
插入和删除仅在栈顶处执行
链栈的定义
typedef struct StackNode {
SElemType data;
struct StackNode *next;
} StackNode, *LinkStack;
LinkStack S;
如同定义单链表。
链栈的初始化
算法描述:
void InitStack(LinkStack &S) {
//构造一个空栈,栈顶指针置空
S = NULL;
return OK;
}
判断链栈是否为空
算法思路:
-
头指针不指向任何元素时为空
算法描述:
Status StackEmpty(LinkStack S) {
if(S == NULL) return TRUE;
else return FALSE;
}
取栈顶元素
算法思路:
-
直接取出S所指的元素
算法描述:
SElemType GetTop(LinkStack S) {
if(S != NULL)
return S->data;
}
链栈的入栈
算法思路:
-
生成新结点p
-
将新节点插入栈顶
-
修改栈顶指针
算法描述:
Status Push(LinkStack &S, SElemType e) {
p = new Stacknode; //生成新结点p
p->data = e; //将新节点的数据域置为e
p->next = S; //将新节点插入栈顶
S = p; //修改栈顶指针
return OK;
}
链栈的出栈
算法思路:
-
判断栈是否存在且非空
-
保存当前栈顶地址
-
修改栈顶指针
-
释放刚才的栈顶空间
算法描述:
Status Pop(LinkStack &S, SElemType &e) {
if(S == NULL) return ERROR;
e = S->data;
p = S;
S = S->next;
delete P;
return OK;
}