详解顺序栈
定义
顺序栈是一种特殊的线性表数据结构,它利用了顺序存储结构的特点,即采用地址连续的存储空间(数组)依次存储栈中的数据元素。
顺序栈的元素依次存放在一个一维数组中,下标小的一端作为栈底。用一个变量记录栈顶位置,称“栈顶指针”。进栈操作是将元素存放在栈顶后面一个位置,栈顶往后移;出栈操作是删除栈顶元素,栈顶往前移。
特点
顺序栈的特点是先进后出(LIFO),即最后进入栈的元素最先被删除。顺序栈在逻辑上是线性结构,可以采用顺序存储结构实现,也可以采用链式存储结构实现。顺序栈在操作上采用传地址的方式把出栈的值传给*地址。
栈的运算
初始化
分配内存来存储数据,检查内存是否成功分配,设置栈的最大容量,设置栈顶指针为-1。
/* 1. 初始化 */
int init(SeqStack *S, int MaxSize)
{
/*申请内存空间*/
S->data = (DataType*)malloc(sizeof(DataType)*MaxSize);
if(!S->data)
{
printf("内存申请错误,初始化失败![10001]\n");
return 10001;
}
S->maxsize = MaxSize;
S->top = -1;
return 0;
}
进(入)栈
这个函数是用来将数据放入顺序栈中的。如果栈已满,它会打印错误信息并返回一个错误码。如果栈还有空间,它会将数据放入栈顶,然后将栈顶指针向上移动一个位置。如果操作成功,它会返回0。
/* 2. 进(入)栈 */
int push(SeqStack *S, DataType x)
{
/*是否满?*/
if(full(S))
{
printf("栈已满!10002\n");
return 10002;
}
S->top++; /*移动指针*/
S->data[S->top] = x;/*放入数据*/
return 0; /*OK*/
}
出栈
这个函数是用来从顺序栈中取出元素的。如果栈为空,它会打印错误信息并返回一个错误码。如果栈非空,它会将栈顶元素赋值给x,然后移除栈顶元素(通过将栈顶指针减一),并返回0表示操作成功。
/* 3. 出栈 */
int pop(SeqStack *S, DataType *x)
{
/*是否空?*/
if(empty(S))
{
printf("栈为空!10003\n");
return 10003;
}
*x = S->data[S->top]; /*栈顶元素赋值给x*/
S->top--; /*移动栈顶指针*/
return 0;
}
取栈顶元素
这个函数是用来获取顺序栈的栈顶元素的。如果栈为空,它会打印错误信息并返回一个错误码。如果栈非空,它会将栈顶元素赋值给x,但不移除该元素,然后返回0表示操作成功。
/* 4. 取栈顶元素 */
int get_top(SeqStack *S, DataType *x)
{
/*是否空?*/
if(empty(S))
{
printf("栈为空!10003\n");
return 10003;
}
*x = S->data[S->top]; /*栈顶元素赋值给x*/
return 0;
}
栈为空?
这个函数是用来检查一个顺序栈是否为空的。如果栈顶指针为-1,它就返回1,否则返回0。
/* 5. 栈为空?*/
int empty(SeqStack *S)
{
return (S->top == -1)?1:0;
}
栈满
这个函数是用来检查一个顺序栈是否已满的。如果栈顶指针到达了栈的最大容量减1(因为栈顶指针是从0开始计数的),它就返回1,否则返回0。
/* 6. 栈满?*/
int full(SeqStack *S)
{
return (S->top == S->maxsize - 1)?1:0;
}
销毁
这个函数是用来释放顺序栈的数据数组所占用的内存的。它通过调用free函数来释放内存,并返回0表示成功。
/* 7. 销毁*/
int destroy(SeqStack *S)
{
free(S->data);
return 0;
}
栈的应用
十进制转换为二进制
这段代码是一个将十进制数转换为二进制的函数。它使用了一个栈结构(SeqStack)来处理转换过程。下面是代码的简要解释:
-
定义一个名为
d_to_b的函数,该函数接受一个整数d作为输入。 -
定义一个SeqStack类型的变量
S和一个整数变量b。 -
调用
init函数初始化栈S,指定其容量为32。 -
使用
while循环,当d不为0时执行转换过程:- 通过取模运算获取
d的余数,将余数推进栈S中。 - 将
d除以2,更新d的值。
- 通过取模运算获取
-
使用
while循环,当栈不为空时执行出栈操作:- 使用
pop函数将栈顶元素弹出并存储在变量b中。 - 打印出栈顶元素(即二进制数的每一位)。
- 使用
-
调用
destroy函数销毁栈S。
/*十进制转换为二进制*/
int d_to_b(int d)
{
SeqStack S;
int b;
/*初始化栈*/
init(&S,32);
/*d不为0,余数进栈*/
while(d)
{
push(&S, d % 2);
d /= 2;
}
/*依次出栈*/
while(!empty(&S))
{
pop(&S,&b);
printf("%d", b);
}
/*销毁栈*/
destroy(&S);
}
后缀表达式计算
这段代码是一个后缀表达式计算器,也被称为逆波兰表示法(Reverse Polish Notation,RPN)。在后缀表达式中,操作符位于相应操作数的后面,例如“56+”表示“5+6”。与中缀表达式不同,后缀表达式不使用括号来表示优先级,因此计算过程更为简洁。
代码的执行流程如下:
-
初始化一个空栈和一些变量。
-
从用户输入中读取后缀表达式。
-
遍历后缀表达式的每个字符,根据字符类型执行相应的操作:
- 如果是数字字符(0-9),将其转换为对应的数值,并将其压入栈中。
- 如果是加法运算符(+),从栈中弹出两个操作数,进行加法运算,并将结果压入栈中。
- 如果是减法运算符(-),从栈中弹出两个操作数,进行减法运算,并将结果压入栈中。
- 如果是乘法运算符(*),从栈中弹出两个操作数,进行乘法运算,并将结果压入栈中。
- 如果是除法运算符(/),从栈中弹出两个操作数,进行除法运算,并将结果压入栈中。
-
最后,从栈中弹出一个操作数,即为最终的计算结果。
-
输出计算结果。
-
销毁栈。
/*后缀表达式计算*/
int expression()
{
SeqStack S;
int i;
int op1, op2;
int x;
char exp[20]; /*后缀表达式*/
init(&S, 10);
printf("请输入一个后缀表达式(eg. 56+):");
scanf("%s", exp);
for(i=0;i<strlen(exp);i++)
{
switch(exp[i])
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
/*入栈*/
push(&S, exp[i]-48);
break;
case '+':
/*出2个*/
pop(&S, &op1);
pop(&S, &op2);
x = op1 + op2;
push(&S, x);
break;
case '-':
/*出2个*/
pop(&S, &op1);
pop(&S, &op2);
x = op2 - op1;
push(&S, x);
break;
case '*':
pop(&S, &op1);
pop(&S, &op2);
x = op1 * op2;
push(&S, x);
break;
case '/':
pop(&S, &op1);
pop(&S, &op2);
x = op2 * op1;
push(&S, x);
break;
}
}
pop(&S, &x);
printf("计算结果为:%s = %d\n", exp, x);
destroy(&S);
}
栈的实现
完整代码
项目结构
main.c
seqstack.c
seqstack.h
项目文件
main.c
#include <stdio.h>
#include <string.h>
#include "seqstack.h"
/*十进制转换为二进制*/
int d_to_b(int d);
/*后缀表达式计算*/
int expression();
int main(int argc, char* argv[])
{
SeqStack S;
int cmd;
int d;
DataType x;
int maxsize;
char yn;
do
{
printf("---------顺序栈演示程序-----------\n");
printf(" 1. 初始化\n");
printf(" 2. 入栈\n");
printf(" 3. 出栈\n");
printf(" 4. 取栈顶元素\n");
printf(" 5. 栈是否空?\n");
printf(" 6. 栈是否满?\n");
printf(" 7. 销毁栈\n");
printf(" 8. 栈的应用\n");
printf(" 9. 帮助\n");
printf("请选择(0~9,0退出):");
scanf("%d", &cmd);
switch(cmd)
{
case 1:
printf("请输入栈的最大存储空间(MaxSize):");
scanf("%d", &maxsize);
if(!init(&S, maxsize))
{
printf("栈已初始化!\n");
}
break;
case 2:
printf("请输入入栈元素:x=");
scanf("%d", &x);
if(!push(&S, x))
{
printf("元素【%d】已入栈!\n", x);
}
break;
case 3:
printf("确定要出栈(出栈后数据不可恢复,y|n,n)?");
// flushall();
getchar();
scanf("%c", &yn);
if(yn == 'y' || yn == 'Y')
{
if(!pop(&S, &x))
{
printf("栈顶元素【%d】已出栈!\n", x);
}
}
break;
case 4:
if(!get_top(&S, &x)){
printf("栈顶元素【%d】\n", x);
}
break;
case 5:
if(!empty(&S)){
printf("栈不空!\n");
} else{
printf("栈是空! \n");
}
break;
case 6:
if(full(&S)){
printf("栈已满!\n");
} else{
printf("栈没有满,还可以入栈哦!\n");
}
break;
case 7:
if(empty(&S)){
printf("栈是空的哦!\n");
break;
}
printf("确定要出栈(出栈后数据不可恢复,y|n)?\n");
empty(&S);
getchar();
scanf("%c", &yn);
if(yn == 'y' || yn == 'Y')
{
if(!destroy(&S))
{
printf("栈已经全都销毁!\n");
}
}
break;
case 8:
do
{
printf("----------8.栈的应用------------\n");
printf(" 1. 十进制转换为二进制\n");
printf(" 2. 后缀表达式计算\n");
printf(" 0. 返回\n");
printf("请选择:");
scanf("%d", &cmd);
if(cmd == 1)
{
printf("请输入一个十进制数:");
scanf("%d", &d);
printf("二进制为:");
d_to_b(d);
printf("\n");
}
if(cmd == 2)
{
expression();
}
}while(cmd!=0);
cmd = -1;
break;
case 9:
printf("本程序为顺序栈的演示程序,由付雄宇靓仔设计开发,\n如有问题可以一起学习交流讨论!\n");
}
}while(cmd!=0);
return 0;
}
/*十进制转换为二进制*/
int d_to_b(int d)
{
SeqStack S;
int b;
/*初始化栈*/
init(&S,32);
/*d不为0,余数进栈*/
while(d)
{
push(&S, d % 2);
d /= 2;
}
/*依次出栈*/
while(!empty(&S))
{
pop(&S,&b);
printf("%d", b);
}
/*销毁栈*/
destroy(&S);
}
/*后缀表达式计算*/
int expression()
{
SeqStack S;
int i;
int op1, op2;
int x;
char exp[20]; /*后缀表达式*/
init(&S, 10);
printf("请输入一个后缀表达式(eg. 56+):");
scanf("%s", exp);
for(i=0;i<strlen(exp);i++)
{
switch(exp[i])
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
/*入栈*/
push(&S, exp[i]-48);
break;
case '+':
/*出2个*/
pop(&S, &op1);
pop(&S, &op2);
x = op1 + op2;
push(&S, x);
break;
case '-':
/*出2个*/
pop(&S, &op1);
pop(&S, &op2);
x = op2 - op1;
push(&S, x);
break;
case '*':
pop(&S, &op1);
pop(&S, &op2);
x = op1 * op2;
push(&S, x);
break;
case '/':
pop(&S, &op1);
pop(&S, &op2);
x = op2 / op1;
push(&S, x);
break;
}
}
pop(&S, &x);
printf("计算结果为:%s = %d\n", exp, x);
destroy(&S);
}
seqstack.h
/*
seqstack.h
顺序栈
*/
typedef int DataType;
typedef struct
{
DataType *data; /* 堆空间 */
int maxsize;
int top; /* 栈顶指针 */
}SeqStack;
/* 1. 初始化 */
int init(SeqStack *S, int MaxSize);
/* 2. 进(入)栈 */
int push(SeqStack *S, DataType x);
/* 3. 出栈 */
int pop(SeqStack *S, DataType *x);
/* 4. 取栈顶元素 */
int get_top(SeqStack *S, DataType *x);
/* 5. 栈为空?*/
int empty(SeqStack *S);
/* 6. 栈满?*/
int full(SeqStack *S);
/* 7. 销毁*/
int destroy(SeqStack *S);
seqstack.c
/*
seqstack.c
*/
#include <stdio.h>
#include <malloc.h>
#include "seqstack.h"
/* 1. 初始化 */
int init(SeqStack *S, int MaxSize)
{
/*申请内存空间*/
S->data = (DataType*)malloc(sizeof(DataType)*MaxSize);
if(!S->data)
{
printf("内存申请错误,初始化失败![10001]\n");
return 10001;
}
S->maxsize = MaxSize;
S->top = -1;
return 0;
}
/* 2. 进(入)栈 */
int push(SeqStack *S, DataType x)
{
/*是否满?*/
if(full(S))
{
printf("栈已满!10002\n");
return 10002;
}
S->top++; /*移动指针*/
S->data[S->top] = x;/*放入数据*/
return 0; /*OK*/
}
/* 3. 出栈 */
int pop(SeqStack *S, DataType *x)
{
/*是否空?*/
if(empty(S))
{
printf("栈为空!10003\n");
return 10003;
}
*x = S->data[S->top]; /*栈顶元素赋值给x*/
S->top--; /*移动栈顶指针*/
return 0;
}
/* 4. 取栈顶元素 */
int get_top(SeqStack *S, DataType *x)
{
/*是否空?*/
if(empty(S))
{
printf("栈为空!10003\n");
return 10003;
}
*x = S->data[S->top]; /*栈顶元素赋值给x*/
return 0;
}
/* 5. 栈为空?*/
int empty(SeqStack *S)
{
return (S->top == -1)?1:0;
}
/* 6. 栈满?*/
int full(SeqStack *S)
{
return (S->top == S->maxsize - 1)?1:0;
}
/* 7. 销毁*/
int destroy(SeqStack *S)
{
free(S->data);
return 0;
}
运行结果
小结
顺序栈是一种重要的数据结构,它遵循 LIFO(Last In First Out,后进先出)的原则。
- 定义:栈是一种有序的数据集合,其中数据项按照添加和删除的顺序排列。栈的特性是只能在栈顶进行插入或删除操作。
- 操作:栈主要包含两种操作,即 push 和 pop。push 操作在栈顶添加一个元素,而 pop 操作则删除栈顶的元素。除此之外,有些栈实现还提供了其他操作,如 (查看栈顶元素但不删除)等。
- 应用:栈在许多领域都有广泛的应用,如程序中的函数调用和递归、表达式求值、括号匹配、深度优先搜索等。
- 实现:栈可以通过数组、链表、动态数组等不同的数据结构实现。其中,数组实现较为简单,但可能造成空间浪费;链表实现较为灵活,但需要额外的空间存储节点;动态数组则结合了数组和链表的优点。
- 性质:栈具有后进先出的性质,即最后进入栈的元素最先被删除。这个性质在很多情况下非常有用,但也需要谨慎使用,以避免出现逻辑错误。
- 常见问题:在使用栈时,需要注意一些常见问题,如溢出(当栈满时无法继续添加元素)、下溢(当栈空时无法继续删除元素)等。这些问题可以通过设置大小限制、检查栈状态等方式避免。
总之,顺序栈是一种非常有用的数据结构,掌握它的基本概念、性质和实现方式,可以帮助我们在编程中解决很多问题。