栈
前言
自己学习数据结构的笔记,看的是王道的视频,总结了栈的基本概念,代码实现,实际应用。写了能跑的源代码,有详细的注释。希望对大家学习数据结构有所帮助。
一、栈的基本概念
1.栈是只允许在一端进行插入和删除操作的线性表
2.栈顶:线性表允许进行插入和删除操作的一端
3.栈尾:不允许进行插入和删除的另一端
4.空栈:不含任何元素的空表
5.栈的特点:后进先出
二、顺序栈
1.顺序栈的定义和初始化
//顺序栈的定义
#define MaxSize 50
typedef struct {
int data[MaxSize]; //用静态数组的方式存储栈
int top; //用top表示数组下标的位置,可看做一个指针(并不是真的指针)
}SqStack;
//初始化
void InitStack(SqStack& S) {
S.top = -1; //栈顶指针指向-1,表示栈空
}
//判空
bool StackEmpty(SqStack S) {
if (S.top == -1) { //判满操作为:S.top == MaxSize-1
return true;
}
return false;
}
2.顺序栈的进栈和出栈
//进栈
bool Push(SqStack& S, int x) {
if (S.top == MaxSize-1)
return false;
S.top = S.top + 1; //栈顶指针加1
S.data[S.top] = x; //在数组下标为top的位置存放元素x
//上面两条语句可以简写为:S.data[++S.top] = x;
return true;
}
//出栈
bool Pop(SqStack& S, int& x) {
if (S.top == -1)
return false;
x = S.data[S.top]; //将数组下标为top位置的元素赋值给x
S.top = S.top - 1; //栈顶指针减1
//上面两条语句可以简写为:x = S.data[S.top--];
return true;
}
3.获取栈顶元素
//获取栈顶元素
bool GetTop(SqStack S, int& x) {
if (S.top == -1)
return false;
x = S.data[S.top];
return true;
}
4.共享栈
利用栈底位置相对不变的特性,可让两个顺序栈共享一个一位数组空间,将两个栈的栈顶分贝设置在共享空间的两端,两个栈顶向共享空间的中间延伸。
//共享栈的定义
#define MaxSize 50
typedef struct {
int data[MaxSize]; //用静态数组的方式存储栈
int top0; //0号栈
int top1; //1号栈
}ShStack;
//初始化
void InitStack(ShStack& S) {
S.top0 = -1; //0号栈栈空
S.top1 = MaxSize; //1号栈栈空
}
三、链栈
链栈一般不带头结点 因为栈只对第一个节点进行操作,如果加上了头结点,在对头结点后面的结点也要进行操作 反而使算法复杂 所以设置头指针即可。因此这部分的代码实现都是不带头结点的。
1.链栈的定义和初始化
typedef struct LinkNode{
int data;
struct LinkNode *next;
}*LiStack,LinkNode;
//初始化
bool InitStack(LiStack &S){
S == NULL;
return true;
}
//判空
bool StackEmpty(LiStack S) {
if (S == NULL) {
return true;
}
return false;
}
2.进栈和出栈
//进栈
bool Push(LiStack &S,int x){
//1.分配内存空间
LinkNode *p = (LinkNode)malloc(sizeof(LinkNode));
if(p == NULL)
return false;
//2.进行进栈操作
p->data = x; //将x的值赋给p的数据域
p->next = S; //让p的指针指向第一个元素
S = p; //让头指针指向p
return true;
}
//出栈操作
bool Pop(LiStack &S,int &x){
if(StackEmpty(S))
return false;
//进行出栈操作
LinkNode *p = S; //创建指针p指向要删除的节点
S = S->next; //让头指针指向要删除节点的下一个节点
x = p->data; //将要删除节点的数据通过x返回
free(p); //将要删除的节点从链表中断开
return true;
}
3.获取栈顶元素
//获取栈顶元素
bool GetTop(LiStack S,int &x){
if(StackEmpty(S))
return false;
x = S->data;
return true;
}
四、栈的应用
1.括号匹配
用栈实现括号匹配:依次扫描所有字符,遇到左括号入栈,遇到右括号则弹出栈顶元素检查是否匹配
bool bracketCheck(char str[],int length){
SqStack S;
InitStack(S); //初始化栈
for(int i=0;i<length;i++){
if(str[i]=='(' || str[i]== '[' || str[i]== '{') {
Push(S,str[i]); //扫描到左括号,入栈
}else{
if(StackEmpty(S)) //扫描到右括号且栈空
return false; //匹配失败
char topElem;
Pop(S,topElem); //栈顶元素出栈
if(str[i]== ')' && topElem != '(')
return false
if(str[i]== ']' && topElem != '[')
return false
if(str[i]== '}' && topElem != '{')
return false
}
}
return (StackEmpty(S); //检查完全部括号后,栈空说明匹配成功
}
2.表达式求值
2.1 中缀表达式转后缀表达式(手算)
① 确定中缀表达式中各个运算符的运算顺序
② 根据运算顺序,按照[左操作数 右操作数 运算符]的方式组合成一个新的操作数
③ 如果还有运算符未处理,重复②
左优先原则:优先使用左边的运算符
2.2 后缀表达式的计算(手算/机算)
手算方法:从左往右扫描,每遇到一个运算符,就让运算符前面最近的两个操作数执行对应运算,合并为一个操作数
机算方法:
① 从左往右扫描下一个元素,直到处理完所有元素
② 若扫描到操作数则压入栈,并回到①,否则执行③
③ 若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到①
注意事项: 先出栈的元素是右操作数
2.3 中缀表达式转前缀表达式
① 确定中缀表达式中各个运算符的运算顺序
② 根据运算顺序,按照[运算符 左操作数 右操作数]的方式组合成一个新的操作数
③ 如果还有运算符未处理,重复②
右优先原则:优先使用右边的运算符
2.4 前缀表达式的计算(手算/机算)
手算方法:从右往左扫描,每遇到一个运算符,就让运算符后最近的两个操作数执行对应运算,合并为一个操作数
机算方法:
① 从右往左扫描下一个元素,直到处理完所有元素
② 若扫描到操作数则压入栈,并回到①,否则执行③
③ 若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到①
注意事项: 先出栈的元素是左操作数
2.5 中缀表达式转后缀表达式(机算)
初始化一个栈,用于保存暂时还不能确定运算顺序的运算符
从左到右处理各个元素,直到末尾,可能遇到三种情况:
① 遇到操作数。直接加入后缀表达式
② 遇到界限符。遇到左括号时直接入栈;遇到右括号则依次弹出栈内运算符并加入后缀表达式,直到弹出左括号为止
注意事项:左括号不加入后缀表达式
③ 遇到运算符。依次弹出栈中和该运算符优先级相同或更高的所有运算符,并加入后缀表达式,若遇到左括号或栈空则停止。并将该运算符入栈。
按上述步骤处理完所有字符后,将栈中剩余运算符依次弹出,并加入后缀表达式。
2.6 中缀表达式的计算(机算)
实现:中缀表达式转后缀表达式和后缀表达式求值两个算法相结合
初始化两个栈,分别存储操作数和运算符
若扫描到操作数,压入操作数栈
若扫描到运算符或界限符,按照2.5节②③操作压入运算符栈
每弹出一个运算符,就需要再弹出两个操作数栈的栈顶元素并执行相应运算,运算结果再压回操作数栈
3.函数调用/递归调用
函数调用的特点:最后被调用的函数最先执行结束
函数调用时,需要用一个”函数调用栈“结构存储:调用返回地址,实参,局部变量等信息
递归调用时,”函数调用栈“可称为“递归工作栈”
每进入一层递归,就将递归调用所需的信息压入栈顶
每退出一层递归,就从栈顶弹出相应信息
五、源代码
顺序栈
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
//顺序栈的定义
#define MaxSize 50
typedef struct {
int data[MaxSize]; //用静态数组的方式存储栈
int top; //用top表示数组下标的位置,可以看做一个指针(并不是真的指针)
}SqStack;
//函数声明
void InitStack(SqStack &S); //初始化栈
bool StackEmpty(SqStack S); //判空
bool Push(SqStack& S, int x); //进栈
bool Pop(SqStack& S, int &x); //出栈
bool GetTop(SqStack S, int& x); //获取栈顶元素
void SqStackPrint(SqStack S); //遍历顺序栈
//主函数
int main(void) {
SqStack S;
InitStack(S);
StackEmpty(S);
Push(S,1);
Push(S,2);
Push(S,3);
Push(S,4);
Push(S,5);
SqStackPrint(S);
int a=0,b=0;
GetTop(S,a);
int x=0,y=0,z=0;
Pop(S,x);
Pop(S,y);
Pop(S,z);
SqStackPrint(S);
GetTop(S,b);
return 0;
}
//初始化
void InitStack(SqStack& S) {
S.top = -1; //栈顶指针指向-1,表示栈空
printf("顺序栈初始化成功!\n");
}
//判空
bool StackEmpty(SqStack S) {
if (S.top == -1) { //判满操作为:S.top == MaxSize-1
return true;
printf("顺序栈目前为空\n");
}
return false;
}
//进栈
bool Push(SqStack& S, int x) {
if (S.top == MaxSize-1)
return false;
S.top = S.top + 1; //栈顶指针加1
S.data[S.top] = x; //在数组下标为top的位置存放元素x
//上面两条语句可以简写为:S.data[++S.top] = x;
printf("元素%d进栈成功\n",x);
return true;
}
//出栈
bool Pop(SqStack& S, int& x) {
if (S.top == -1)
return false;
x = S.data[S.top]; //将数组下标为top位置的元素赋值给x
S.top = S.top - 1; //栈顶指针减1
//上面两条语句可以简写为:x = S.data[S.top--];
printf("元素%d出栈成功\n",x);
return true;
}
//获取栈顶元素
bool GetTop(SqStack S, int& x) {
if (S.top == -1)
return false;
x = S.data[S.top];
printf("栈顶元素的值为:%d\n",x);
return true;
}
//遍历顺序栈
void SqStackPrint(SqStack S){
printf("================\n");
printf("顺序栈的遍历结果为:\n");
while(S.top > -1){
printf("%d ",S.data[S.top]);
S.top--;
}
printf("\n");
printf("================\n");
}
链栈
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
//链栈的定义
typedef struct LinkNode{
int data;
struct LinkNode *next;
}*LiStack,LinkNode;
bool InitStack(LiStack &S); //初始化栈
bool StackEmpty(LiStack S); //判空
bool Push(LiStack& S, int x); //进栈
bool Pop(LiStack& S, int &x); //出栈
bool GetTop(LiStack S, int& x); //获取栈顶元素
void LiStackPrint(LiStack S); //遍历链栈
int main(void){
LiStack S;
InitStack(S);
StackEmpty(S);
Push(S,1);
Push(S,2);
Push(S,3);
Push(S,4);
Push(S,5);
LiStackPrint(S);
int a=0,b=0;
GetTop(S,a);
int x=0,y=0,z=0;
Pop(S,x);
Pop(S,y);
Pop(S,z);
LiStackPrint(S);
GetTop(S,b);
return 0;
}
//初始化
bool InitStack(LiStack &S) {
S = NULL;
printf("链栈初始化成功!\n");
return true;
}
//判空
bool StackEmpty(LiStack S) {
if (S == NULL) {
printf("顺序栈目前为空!\n");
return true;
}
return false;
}
//进栈
bool Push(LiStack &S,int x){
//分配内存空间
LinkNode *p = (LinkNode *)malloc(sizeof(LinkNode));
if(p == NULL)
return false;
//进行进栈操作
p->data = x; //将x的值赋给p的数据域
p->next = S; //让p的指针指向第一个元素
S = p; //让头指针指向p
printf("元素%d进栈成功\n",x);
return true;
}
//出栈操作
bool Pop(LiStack &S,int &x){
if(StackEmpty(S))
return false;
//进行出栈操作
LinkNode *p = S; //创建指针p指向要删除的节点
S = S->next; //让头指针指向要删除节点的下一个节点
x = p->data; //将要删除节点的数据通过x返回
free(p); //将要删除的节点从链表中断开
printf("元素%d出栈成功\n",x);
return true;
}
//获取栈顶元素
bool GetTop(LiStack S,int &x){
if(StackEmpty(S))
return false;
x = S->data;
printf("栈顶元素的值为:%d\n",x);
return true;
}
//链栈表的遍历
void LiStackPrint(LiStack S){
LinkNode *s = S;
printf("==================\n");
printf("链栈的遍历结果为:");
if(s == NULL){
printf("栈空!\n");
}
while(s != NULL){
printf("%d ",s->data);
s = s->next;
}
printf("\n");
printf("==================\n");
}