目录
1.6.2后缀表达式⭐(中转后:手算+机算;后缀求值:手算+机算)
栈和队列都是操作受限制的线性表。
1.栈
1.1栈的基本概念
栈(stack):只能在一端进行(栈顶)插入/删除的线性表。故其逻辑结构为线性结构(一对一)。
线性表:具有相同数据类型的n个数据元素的有限序列。
栈的特点:后进先出(LIFO)
基本操作:
InitStack(&S) //初始化栈。构造一个空栈S,分配内存空间。
DestroyStack(&S) //销毁栈。销毁并释放栈S所占用的内存空间。
Push(&S,x) //进栈,若栈S未满,则将x加入使之成为新栈顶。
Pop(&S,&x) //出栈,若栈S非空,则弹出栈顶元素,并用x返回。
GetTop(S,&x) //读栈顶元素。若栈S非空,则用x返回栈顶元素
StackEmpty(S) //判断一个栈S是否为空。若S为空,则返回true,否则返回false
常考题型:已知进栈顺序,有哪些合法的出栈顺序?
n个不同元素进栈,出栈元素不同排列顺序个数为:
(卡特兰数Catalan),可用数学归纳法证明(不要求掌握)
1.2栈的顺序存储实现
顺序栈:逻辑结构——线性结构;存储结构(物理结构)——顺序存储
顺序栈的定义:
//顺序栈的定义:
#define maxsize 10
typedef struct {
ElemType data[maxsize]; //数据域(静态分配:静态数组)
int top; //栈顶指针( 实际指的是该静态数组的数组下标,起到指针的作用)
}SqStack; //sequence stack
基本操作1(top== -1时,即栈顶指针始终指向非空区域)
顺序栈的初始化:
void InitStack(SqStack &S){
S.top = -1; //设定-1代表空栈
}
SqStack S; 声明一个顺序栈后,分配了连续的存储空间,大小为maxsize×sizeof(ElemType)
//栈空判断
bool StackEmpty(SqStack S){
if(S.top == -1) return true;
else return false;
}
顺序栈的进栈操作(增):
bool Push(SqStack &S,ElemType x){
if(S.top == maxsize-1) return false; //栈满,无法再入栈,报错
// S.top = S.top + 1; //先移动栈顶指针
// S.data[S.top] = x; //新元素再入栈
S.data[++S.top] = x; //等价为一句 (top先加再用)
return true;
}
顺序栈的出栈操作(删):
bool Pop(SqStack &S,ElemType &x){
if(S.top == -1) return false;
// x = S.data[S.top]; //先删除元素,并由x代回
// S.top = S.top - 1; //再移动栈顶指针
x = S.data[S.top--]; //等价为一句 (top先用再减)
return true;
}
读栈顶元素操作(查):
ElemType GetTop(SqStack S){
if(S.top == -1) return false; //栈空,报错
return S.data[S.top];
}
bool GetTop(SqStack S,ElemType &x){
if(S.top == -1) return false; //栈空,报错
x = S.data[S.top];
return true;
}
基本操作2(top== 0时,即栈顶指针始终指向下一个可插入的空区域)
顺序栈的初始化:
void InitStack(SqStack &S){
S.top = 0; //设定0代表空栈
}
//判空
bool StackEmpty(SqStack S){
if(S.top == 0) return true;
else return false;
}
顺序栈的进栈操作(增):
bool Push(SqStack &S,ElemType x){
if(S.top == maxsize) return false;
S.data[S.top++] = x; //top先用再加
return true;
}
顺序栈的出栈操作(删):
bool Pop(SqStack &S,ElemType &x){
if(S.top == 0) return false;
x = S.data[--S.top];//top先减再用
return true;
}
读顺序栈的栈顶元素的操作(查):
bool GetTop(SqStack S,ElemType &x){
if(S.top == 0) return false;
x = S.data[--S.top];
return true;
}
top == -1 和 top == 0 的区别:(注意审题)
比较项 | top == -1 栈顶指针始终指向非空区域 |
top == 0 栈顶指针始终指向下一个可插入的空区域 |
栈空 | top == -1 | top == 0 |
栈满 | top == maxsize -1 | top == maxsize |
push | S.top先加再用 | S.top先用再加 |
pop | S.top先用再减 | S.top先减再用 |
1.3共享栈
共享栈:两个栈共享同一片存储空间。
定义与初始化:
//定义
#define maxsize 10
typedef struct{
ElemType data[maxsize];
int top0; //0号栈栈顶指针
int top1; //1号栈栈顶指针
}ShStack; //shared stack
//初始化
void InitShStack(ShStack &S){
//共享栈所占空间的头尾分别作为栈顶指针
S.top0 = -1;
S.top1 = maxsize;
}
栈满条件:top0+1 == top1
栈的销毁?——系统包分配包回收
在声明栈(Stack S)时,系统自动给栈分配空间;在所在生命域的生命周期结束后(如所在函数运行结束后),系统自动回收内存。
而上述中 top == -1 或 top == 0;都是在逻辑上的销毁。
1.4栈的链式存储实现
链栈:逻辑结构——线性结构;存储结构(物理结构)——链式存储
进栈/出栈只能在链栈的一端(栈顶)进行的单链表
链栈的定义:
//定义
typedef struct Linknode{
ElemType data;
struct Linknode *next;
}Linknode, *LiStack;
//等价于以下三句代码
struct Linknode{ //定义单链表的结点类型
ElemType data;//数据域 每个结点存放一个数据元素
struct Linknode *next;//指针域 指向单链表中下一个结点
};
typedef struct Linknode Linknode;
typedef struct Linknode* LiStack;
两种重命名后,声明时的区别:
Linknode * //强调这是一个结点
LiStack //强调这是一个单链表
//Linknode是个结构体,LiStack是(结构体)指针。两种重命名的灵活选择,可以提升代码可读性。
基本操作:
链栈的初始化(创):
不带头结点
//初始化(创)
//不带头节点
bool InitLiStack(LiStack &S){
if(S == NULL) return false;//非法参数
S = NULL;
return true;
}
带头结点
//初始化(创)
//带头节点
bool InitLiStack(LiStack &S){
S = (Linknode *)malloc(sizeof(Linknode));
if(S == NULL) return false;//内存不足,分配失败
S.next = NULL;
return true;
}
链栈的判空:
//链栈的判空(带头结点)
bool LiStackEmpty(LiStack S){
if(S.next == NULL) return true;
else return false;
}
链栈的进栈操作(增):
//对应单链表对头结点的后插操作(头插法建立单链表)
bool Push(LiStack &S,ElemType x) {
//判满 ?
Linknode *p = (Linknode *)malloc(sizeof(Linknode));//新元素
if(p == NULL) return false; //内存不足,分配失败
p.data = x;
p->next = S.next;
S.next = p;//修改栈顶
return true;
}
链栈的出栈操作(删):
//对应单链表对头结点的后删操作
bool Pop(LiStack &S,ElemType &x) {
if(S.next == NULL) return false;//栈空 无法出栈 报错
x = S.next.data;
S.next == S.