堆栈
1 什么是堆栈
例子:计算机计算算术表达式。
-
由两类对象构成
1、运算数:1、2、4 等
2、运算符:+、- 等 -
不同运算符优先级不一样
1.1 后缀表达式
- 中缀表达式:运算符号位于两个运算数之间。
- 后缀表达式:运算符号位于两个运算数之后。
后缀表达式策略:从左向右扫描,逐个处理运算数和运算符号。
计算机实现后缀表达式计算方法的数据结构称为堆栈。
1.2 堆栈的抽象数据类型描述
堆栈(Stack):具有一定操作约束的线性表。
- 只在一端(栈顶,Top)做插入、删除
- 插入数据:入栈(Push)
- 删除数据:出栈(Pop)
- 后入先出:Last In First Out(LIFO)
1、类型名称:堆栈
2、数据对象集:一个有0个或者多个元素的有穷线性表。
3、操作集:长度为MaxSize的堆栈S属于Stack,堆栈元素item属于ElementType
- Stack CreatStack( int MaxSize):生产空堆栈
- int IsFull(Stack S, int MaxSize):判断堆栈S是否已满
- void Push(Stack S, ElementType item):将元素item压入堆栈
- int IsEmpty(Stack S):判断堆栈S是否为空
- ElementType Pop(Stack S):删除并返回栈顶元素
2 栈的顺序存储实现
2.1 栈的定义
# define MaxSize <存储数据元素的最大个数>
typedef struct SNode *Stack;
struct SNode {
ElementType Data[MaxSize];
int Top;
};
2.2 入栈
void Push(Stack PtrS, ElementType item)
{
if (PtrS->Top == MaxSize - 1) {
printf("FULL");
return;
}
else {
PtrS->Data[++(PtrS->Top)] = item;
return;
}
}
2.3 出栈
ElementType Pop(Stack PtrS)
{
if (PtrS->Top == -1) {
printf("Stack Empty!");
return ERROR; // ERROR为ElementType的特殊值,标志错误。
}
else
return (PtrS->Data[(PtrS->Top)--]);
}
2.4 数组空间利用率的提升
【思考】请用一个数组实现两个堆栈,要求最大地利用数组空间,使数组只要有空间入栈操作就成功。
【分析】一种比较好的方法是使这两个栈分别从数组的两头开始向中间生长,当两个栈的栈顶指针相遇时,表示两个栈都满了。
【栈的定义】
# define MaxSize <存储数据元素的最大个数>
struct DStack {
ElementType Data[MaxSize];
int Top1;
int Top2;
} S;
S.Top1 = -1;
S.Top2 = MaxSize;
【入栈】
void Push(struct DStack *PtrS, ElementType item, int Tag)
{
if (PtrS->Top2 - PtrS->Top1 == 1) {
printf("Stack Full!");
return;
}
if (Tag == 1)
PtrS->Data[++(PtrS->Top1)] = item;
else
PtrS->Data[--(PtrS->Top2)] = item;
}
【出栈】
ElementType Pop(struct DStack *PtrS, int Tag)
{
if (Tag == 1) {
if (PtrS->Top1 == -1) {
printf("Stack 1 Empty!");
return NULL;
}
else
return PtrS->Data[(PtrS->Top1)--];
}
else {
if (PtrS->Top2 == MaxSize) {
printf("Stack 2 Empty!");
return NULL;
}
else
return PtrS->Data[(PtrS->Top2)++];
}
}
3 堆栈的链式存储实现
栈的链式存储结构实际上就是一个单链表,叫做链栈。插入和删除操作只能在链栈的栈顶进行。
【注意】链栈的指针Top只能指向链表的链头,不能指向链尾。
3.1 链栈的定义
typedef struct SNode *Stack;
struct SNode {
ElementType Data;
struct SNode *Next;
};
3.2 初始化
Stack CreateStack()
{
Stack S;
S = (Stack)malloc(sizeof(struct SNode));
S->Next = NULL;
return S;
}
int IsEmpty(Stack S)
{
// 空函数返回值为1;否则返回值为0.
return (S->Next == NULL);
}
3.3 插入
void Push(ElementType item, Stack S)
{
struct SNode *TmpCell;
TmpCell = (struct SNode *)malloc(sizeof(struct SNode));
TmpCell->Element = item;
TmpCell->Next = S->Next;
S->Next = TmpCell;
}
3.4 删除
ElementType Pop(Stack S)
{
struct SNode * FirstCell;
ElementType TopElem;
if (IsEmpty(S)) {
printf("Stack Empty");
return NULL;
}
else {
FirstCell = S->Next;
S->Next = FirstCell->Next;
TopElem = FirstCell->Element;
free(FirstCell);
return TopElem;
}
}
4 堆栈的应用
4.1 中缀表达式求值
基本策略:中缀表达式转换为后缀表达式,然后求值。
-
运算数相对顺序不变
-
运算符号顺序发生改变
1、需要存储等待中的运算符号
2、要将当前运算符号与等待中的最后一个运算符号比较
4.2 中缀表达式转换称为后缀表达式
从头到尾读取中缀表达式的每个对象,对不同对象按不同的情况处理。
-
运算符:直接输出
-
左括号:压入堆栈
-
右括号:将栈顶的运算符弹出并输出,直到遇到左括号(出栈,不输出)
-
运算符:
1、若优先级大于栈顶运算符时,则把它压栈
2、若优先级小于栈顶运算符时,则将栈顶运算符弹出并输出;再比较新的栈顶运算符,直到该运算符大于栈顶运算符优先级为止,然后将该运算符压栈
-
若各对象处理完毕,则把堆栈中存留的运算符一并输出
4.3 堆栈的其他应用
- 函数调用及递归实现
- 深度优先搜索
- 回溯算法
- …