定义和特点
定义
顺序栈是栈的顺序实现,它利用了顺序存储结构。顺序栈是用地址连续的存储空间(数组)依次存储栈中数据元素。由于入栈和出栈运算都是在栈顶进行,而栈底位置是固定不变的,可以将栈底位置设置在数组空间的起始处,而栈顶位置会随着入栈和出栈操作而变化。因此,通常会用一个整型变量top来记录当前栈顶元素在数组中的位置。
特点
- 空间利用率高:顺序栈采用连续的存储空间,可以充分利用存储空间,减少内存浪费。
- 入栈和出栈效率高:由于顺序栈的元素入栈和出栈都是在栈顶进行的,因此操作的时间复杂度为O(1),入栈和出栈的效率非常高。
- 动态调整大小:顺序栈不需要手动扩展或收缩,可以根据需要自动调整大小。
- 简单易用:顺序栈提供了简单易用的接口,使得使用顺序栈非常方便。
同时,当数据量很大时,顺序栈也会存在一些问题,比如消耗内存空间较大,入栈和出栈操作可能较慢等。在选择使用顺序栈时,需要根据实际需求和场景进行综合考虑。
基本运算
- 初始化栈:创建一个空的顺序栈,通常会分配一定的初始空间,并设置栈顶指针top为0或-1表示栈为空。
- 入栈操作(push):在栈顶位置插入一个元素。入栈操作需要将元素存储在栈顶位置,并将top指针加1表示栈顶位置已经向后移动一位。如果栈已满,需要重新分配存储空间并更新top指针。
- 出栈操作(pop):删除栈顶元素并将栈顶指针加1。如果栈为空,则不能进行出栈操作。
- 查看栈顶元素(peek或top):获取栈顶元素的值,但不删除栈顶元素。如果栈为空,则无法查看栈顶元素。
- 判断栈是否为空(empty):检查栈是否不包含任何元素。如果栈为空,返回true,否则返回false。
- 获取栈中元素个数(size):返回栈中元素的数量。
顺序表的实现
需要注意的是,在进行入栈和出栈操作时,需要注意栈满和栈空的情况,避免越界或出现错误。
初始化
创建一个空的数组作为顺序栈,并初始化栈顶指针top为-1,表示栈为空。
c
typedef int DataType;
typedef struct
{
DataType *data; /* 堆空间 */
int maxsize;
int top; /* 栈顶指针 */
}SeqStack;
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;
}
入栈
入栈:将一个元素插入到顺序栈的栈顶位置,需要执行以下步骤:
- 首先判断栈是否已满(栈满条件为top等于数组的最大索引值);
- 若栈未满,则将要入栈的元素放入数组中top的下一个位置(即top+1),并将top加1。
c
int push(SeqStack *S, DataType x)
{
/*是否满?*/
if(full(S))
{
printf("栈已满!10002\n");
return 10002;
}
S->top++; /*移动指针*/
S->data[S->top] = x;/*放入数据*/
return 0; /*OK*/
}
出栈
将顺序栈的栈顶元素移除,需要执行以下步骤:
- 首先判断栈是否为空(栈空条件为top等于-1);
- 若栈非空,则将栈顶的元素取出,并将top减1。
c
int pop(SeqStack *S, DataType *x)
{
/*是否空?*/
if(empty(S))
{
printf("栈为空!10003\n");
return 10003;
}
*x = S->data[S->top]; /*栈顶元素赋值给x*/
S->top--; /*移动栈顶指针*/
return 0;
}
取栈顶元素
返回顺序栈的栈顶元素的值,即数组中top位置的元素。
c
int get_top(SeqStack *S, DataType *x)
{
/*是否空?*/
if(empty(S))
{
printf("栈为空!10003\n");
return 10003;
}
*x = S->data[S->top]; /*栈顶元素赋值给x*/
return 0;
}
判空
判断顺序栈是否为空,即栈顶指针top是否为-1。若top为-1,则栈为空。
c
/* 5. 栈为空?*/
int empty(SeqStack *S)
{
return (S->top == -1)?1:0;
}
判满
c
int full(SeqStack *S)
{
return (S->top == S->maxsize - 1)?1:0;
}
销毁
释放顺序栈所占用的内存空间。
c
int destroy(SeqStack *S)
{
free(S->data);
return 0;
}
顺序栈的应用
十进制转换为二进制
这个函数 d_to_b
是将一个十进制整数 d
转换为二进制表示的方法。它利用了顺序栈这一数据结构来完成转换过程。
首先,这个函数创建了一个顺序栈 S
,并初始化它的大小为32。
在while循环中,只要 d
不为0,就会一直执行循环。在每次循环中,都会计算 d
对2的余数,并将这个余数推入栈中。然后,将 d
除以2,向下取整,这样就可以在下一次迭代中得到 d
对2的新余数。
然后,在一个类似的while循环中,只要栈不为空,就会持续执行循环。在每次循环中,都会从栈中弹出一个元素,并将其值存储在 b
中。然后,将这个值打印出来。
最后,使用 destroy
函数销毁栈 S
,释放其占用的内存。
总的来说,这个函数就是利用栈的性质(后进先出)和除2取余的方法,将一个十进制数转换为一个二进制数。
c
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);
}
后缀表达式计算
后缀表达式是一种数学表达式表示方法,它不使用括号来表示优先级,而是通过将操作符放在操作数的后面来表示。
具体来说,这段代码首先创建了一个顺序栈,用于临时存储操作数和运算结果。然后,它从用户输入的后缀表达式中逐个读取字符,对于每个字符,根据其值进行不同的操作:
- 如果字符是数字('0'~'9'),则将其转换为对应的整数值,并将其压入栈中。
- 如果字符是加法运算符(+),则从栈中弹出两个操作数,进行加法运算,并将结果压入栈中。
- 如果字符是乘法运算符(*),则从栈中弹出两个操作数,进行乘法运算,并将结果压入栈中。
在遍历完整个后缀表达式后,栈中最后剩下的元素就是计算结果。最后,销毁栈并输出计算结果。
这个方法的好处是不需要关心运算符的优先级和括号的使用,只需要按照输入的顺序进行计算即可。因此,它可以避免由于运算符优先级和括号使用不当而导致的计算错误。
c
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 '*':
pop(&S, &op1);
pop(&S, &op2);
x = op1 * op2;
push(&S, x);
break;
}
}
pop(&S, &x);
printf("计算结果为:%s = %d\n", exp, x);
destroy(&S);
}
运行截图
完整代码
c
/*
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);
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;
}
c
#include <stdio.h>
#include <string.h>
#include <stdlib.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;
int result;
char welcome[] = "(?ω?)没钱";
int i = 0;
int m = 0;
int n = 0;
for(i=0;i<strlen(welcome);i++)
{
printf("%c",welcome[i]);
for(m=0;m<10000;m++)
for(n=0;n<1000;n++)
{
;
}
}
printf("\n\n\n");
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:
getchar();
printf("确定要出栈(出栈后数据不可恢复,y|n,n)?");
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:
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 '*':
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;
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);
}
总结
顺序栈是一种基于数组实现的数据结构,它具有以下特点:
- 基于数组实现:顺序栈使用一个数组来存储数据元素,栈顶元素位于数组的最后一个位置,栈底元素位于数组的第一个位置。
- 后进先出原则:顺序栈遵循后进先出原则,即最后一个入栈的元素最先出栈。
- 动态调整大小:顺序栈可以根据需要动态调整数组的大小,以适应不同的需求。
- 简单易用:顺序栈提供了简单易用的接口,可以方便地进行入栈、出栈、查看栈顶元素等操作。
顺序栈的主要应用场景包括:
- 表达式求值:后缀表达式(逆波兰表示法)是一种常见的表达式表示方法,它不需要使用括号来表示优先级,而是通过将操作符放在操作数的后面来表示。顺序栈可以用于后缀表达式的求值,其中入栈操作用于将操作数压入栈中,出栈操作和查看栈顶元素用于依次处理后缀表达式中的操作符和操作数。
- 数据结构中的辅助数据结构:顺序栈可以作为数据结构中的辅助数据结构,例如在实现链表、树等数据结构时,可以使用顺序栈来辅助进行节点的插入、删除等操作。
- 程序的内存管理:程序在运行时需要使用动态内存分配,而顺序栈可以用于管理这些动态分配的内存块。例如,可以使用顺序栈来保存程序的函数调用栈,以便在函数调用时压入参数和保存局部变量,函数返回时弹出参数和恢复局部变量。
总之,顺序栈是一种常见的数据结构,它的应用广泛且具有很高的实用价值。
参考文献
[1] 李刚 刘万辉. "线性表的结构分析和应用." 9787040461473: 16. 2017.1.1
[2] 文心一言 (baidu.com)