《详解顺序栈》
目录:
一、顺序栈的定义及其特点
顺序栈指的是用顺序表实现的栈存储结构,栈存储结构存取数据元素必须遵守 "先进后出" 的原则。顺序表和栈存储数据的方式高度相似,只不过栈对数据的存取过程有特殊的限制,而顺序表没有。例如,我们使用顺序表(用 a 数组表示)存储 {1,2,3,4}
,存储状态如图 1 所示:
使用栈存储结构存储 {1,2,3,4}
,存储状态如图 2 所示:
对比图 1 和图 2 不难看出,用顺序表模拟栈结构很简单,只要将数据从数组下标为 0 的位置依次存储即可。栈中存取元素,必须遵循“先进后出”的原则,因此若想将图 1 中存储的元素 1 从栈中取出,需依次先将元素 4、元素 3 和元素 2 从栈中取出,最后才能取出元素 1。
顺序表模拟入栈和出栈的实现思路:定义一个实时记录栈顶位置的变量(假设命名为 top),初始状态下栈内无任何元素,整个栈是"空栈",top 的值为 -1。一旦有数据元素进栈,则 top 就做 +1 操作;反之,如果数据元素出栈,top 就做 -1 操作。
二、顺序栈的实现
1.结构体定义
本次顺序栈的代码中定义了用于存储数据的堆空间,栈顶指针,最大容量,以及用于撤销操作的缓存数组;buffer[0]存储操作时的top位置,用于区分入栈与出栈操作。buffer[1]存储操作的数据。
typedef struct{ DataType* data;//堆空间 int maxSize;//最大容量 int top;//栈顶指针 DataType buffer[2]; }SequenceStack;
2.初始化
初始化时使用MaxSzie(最大容量)* 单个DataType长度申请内存空间,同时将buffer数组(撤销数据)与top(栈顶指针)初始化为-1。在初始化异常时打印error.h中对应的错误信息并返回相应错误码。
/** * @brief 顺序栈初始化 * * @param S 顺序栈指针 * @param MaxSize 最大容量 * * @return 状态码:0为成功 **/ int initSequenceStack(SequenceStack* S, int MaxSize) { S->data = (DataType*)malloc(sizeof(DataType) * MaxSize); if (!S->data) { printf("%s", errorHint[0]); return errorCode[0]; } S->maxSize = MaxSize; S->top = -1; S->buffer[0] = -1; S->buffer[1] = -1; return 0; }
3.打印
打印前进行顺序栈判空,为空则抛出异常。反之则从栈底开始将栈打印。
/** * @brief 打印顺序栈 * * @param S 顺序栈指针 * * @return 状态码:0为成功 **/ int printSequenceStack(SequenceStack* S) { if (isEmptySequenceStack(S)) { printf("%s", errorHint[2]); return errorCode[2]; } else { printf("当前顺序栈为:"); for (int i = 0; i <= S->top; i++) { printf("%d", S->data[i]); } printf("\n\n"); } return 0; }
4.入栈
入栈前进行顺序栈判满,已满则抛出异常。反之则移动top+1(栈顶指针)将数据入栈,同时将入栈位置与入栈数据更新到缓存数组。
/** * @brief 顺序栈入栈 * * @param S 顺序栈指针 * @param x * * @return 状态码:0为成功 **/ int pushSequenceStack(SequenceStack* S, DataType x) { if (isFullSequenceStack(S)) { printf("%s", errorHint[1]); return errorCode[1]; } else { S->top++; S->data[S->top] = x; S->buffer[0] = S->top; S->buffer[1] = x; } return 0; }
5.出栈
出栈前进行顺序栈判空,为空则抛出异常。反之则将出栈位置与出栈数据更新至缓存数组,并移动top-1。
/** * @brief 顺序栈出栈 * * @param S 顺序栈指针 * @param x * * @return 状态码:0为成功 **/ int popSequenceStack(SequenceStack* S, DataType* x) {//出栈 if (isEmptySequenceStack(S)) { printf("%s", errorHint[2]); return errorCode[2]; } else { S->buffer[0] = S->top; S->buffer[1] = S->data[S->top]; *x = S->data[S->top]; S->top--; } return 0; }
6.获取栈顶元素
获取前进行顺序栈判空,为空则抛出异常。反之则将传入的DataType指针指向栈顶数据。
/** * @brief 获取栈顶元素 * * @param S 顺序栈指针 * @param x * * @return 状态码:0为成功 **/ int getSequenceStack(SequenceStack* S, DataType* x) { if (isEmptySequenceStack(S)) { printf("%s", errorHint[2]); return errorCode[2]; } else { *x = S->data[S->top]; } return 0; }
7.获取栈顶元素
使用free()函数释放内存。
/** * @brief 顺序栈销毁 * * @param S 顺序栈指针 * * @return 状态码:0为成功 **/ int destroySequenceStack(SequenceStack* S) { free(S->data); return 0; }
8.撤销操作
撤销操作通过buffer数组实现,先判断buffer[0]的值,如果为-1代表无可撤销操作,打印错误信息并返回错误码。如果不等于-1,则将其与当前栈顶指针做比较,大于栈顶指针则上一步为出栈操作,调用入栈函数,将buffer[1]存储的数据重新入栈。小于则相反,调用出栈操作将栈顶元素出栈。
/** * @brief 撤销操作 * * @param S 顺序栈指针 * * @return 状态码:0为成功 **/ int revocationSequenceStack(SequenceStack* S) { if ((S->buffer[0]) == -1) { printf(errorHint[3]); return errorCode[3]; } else { if (S->buffer[0] > S->top) { if (pushSequenceStack(S, S->buffer[1]) == 0) { printf("操作已撤销!\n\n"); } } else { if (popSequenceStack(S, &S->buffer[1]) == 0) { printf("操作已撤销!\n\n"); } } } return 0; }
9.十进制转二进制实现
本次转换并没有使用递归,而是通过栈“先进后出,后进先出”的特性实现的。初始化栈后,使用循环将十进制数不断向二取余,直至为0,同时将余数进栈。而后再次通过循环不断出栈并打印,直至栈空,至此转换便已完成。最后一定不要忘了销毁栈。
int conversion(int decimalism) { SequenceStack S; int binary; initSequenceStack(&S, 32); while (decimalism) { pushSequenceStack(&S, decimalism % 2); decimalism /= 2; } while (!isEmptySequenceStack(&S)) { popSequenceStack(&S, &binary); printf("%d", binary); } printf("\n"); destroySequenceStack(&S); return 0; }
10.后缀表达式运算
后缀表达式的实现原理很简单,逐个判断字符串中的字符,为数字0~9则入栈,为运算符则出栈两个元素并运算,最后将结果再入栈,直至只剩栈底元素,将其打印即为结果。
int expression() { SequenceStack S; int i; int op1, op2; int x; char exp[20]; /*后缀表达式*/ initSequenceStack(&S, 10); printf("请输入一个后缀表达式(eg. 56+):"); scanf("%s", exp); for (i = 0; i < (int) 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': /*入栈*/ pushSequenceStack(&S, exp[i] - 48); break; case '+': popSequenceStack(&S, &op1); popSequenceStack(&S, &op2); x = op1 + op2; pushSequenceStack(&S, x); break; case '-': popSequenceStack(&S, &op1); popSequenceStack(&S, &op2); x = op1 - op2; pushSequenceStack(&S, x); break; case '*': popSequenceStack(&S, &op1); popSequenceStack(&S, &op2); x = op1 * op2; pushSequenceStack(&S, x); break; case '/': popSequenceStack(&S, &op1); popSequenceStack(&S, &op2); x = op1 / op2; pushSequenceStack(&S, x); break; } } popSequenceStack(&S, &x); printf("计算结果为:%s = %d\n", exp, x); destroySequenceStack(&S); return 0; }
三、完整Demo
welcome.h(欢迎字符图案):
char *welcome[] = { " \n", " 4&(\n", " ` ~&&\\yM#1\n", " ,_'Q!!NMW&\n", " WCb 7N@4D Q%,,\n", " PM'*MDk#M0p,\n", " ]@J0&e~~4r' ,+bQEQ\n", " F8I&#' _&B$$bW#&$\n", " &0A1 L#DE&E~!Q&Q,\n", " _=, ,#0RN1 _T@0$' ZN$Q. grNq5\n", " ^ 'd ,0K0pK^ g*Q0g' #Q4p&,/g9X*&#,_/ (q\n", " TA1 ,sDQWh4^ x&NM0` _ #FQ#K#fA# `*K#XWP~-\n", " ^&p,wNMM0qD: /HE#EN' ..#g)~ '@NG0Qx, `=X*\n", " ' '43$'hEk##m0D04f_g ~^ ~ `-00**0\n", " =0#ONq2W0BF^#, _ p,,\n", " ` ^''~ ~b'' **R3`\n", " ow,F +#F~'\n", " /-9! `\\ \n", " R\n" };
sequenceStack.h(结构与函数声明):
/* sequenceStack.h 顺序栈 */ typedef int DataType; typedef struct{ DataType* data;//堆空间 int maxSize;//最大容量 int top;//栈顶指针 DataType buffer[2]; }SequenceStack; int initSequenceStack(SequenceStack* S, int MaxSize); //初始化 int printSequenceStack(SequenceStack* S);//打印 int pushSequenceStack(SequenceStack *S, DataType x);//入栈 int popSequenceStack(SequenceStack *S, DataType *x);//出栈 int getSequenceStack(SequenceStack *S, DataType *x);//获取栈顶元素 int isEmptySequenceStack(SequenceStack *S);//栈判空 int isFullSequenceStack(SequenceStack *S);//栈判满 int destroySequenceStack(SequenceStack *S);//销毁 int revocationSequenceStack(SequenceStack *S);//撤销
sequenceStack.c(函数实现):
#include <stdio.h> #include <malloc.h> #include "sequenceStack.h" #include "error.h" /** * @brief 顺序栈初始化 * * @param S 顺序栈指针 * @param MaxSize 最大容量 * * @return 状态码:0为成功 **/ int initSequenceStack(SequenceStack* S, int MaxSize) { S->data = (DataType*)malloc(sizeof(DataType) * MaxSize); if (!S->data) { printf("%s", errorHint[0]); return errorCode[0]; } S->maxSize = MaxSize; S->top = -1; // S->buffer[0] = -1; // S->buffer[1] = -1; return 0; } /** * @brief 打印顺序栈 * * @param S 顺序栈指针 * * @return 状态码:0为成功 **/ int printSequenceStack(SequenceStack* S) { if (isEmptySequenceStack(S)) { printf("%s", errorHint[2]); return errorCode[2]; } else { printf("当前顺序栈为:"); for (int i = 0; i <= S->top; i++) { printf("%d", S->data[i]); } printf("\n\n"); } return 0; } /** * @brief 顺序栈入栈 * * @param S 顺序栈指针 * @param x * * @return 状态码:0为成功 **/ int pushSequenceStack(SequenceStack* S, DataType x) { if (isFullSequenceStack(S)) { printf("%s", errorHint[1]); return errorCode[1]; } else { S->top++; S->data[S->top] = x; S->buffer[0] = S->top; S->buffer[1] = x; } return 0; } /** * @brief 顺序栈出栈 * * @param S 顺序栈指针 * @param x * * @return 状态码:0为成功 **/ int popSequenceStack(SequenceStack* S, DataType* x) {//出栈 if (isEmptySequenceStack(S)) { printf("%s", errorHint[2]); return errorCode[2]; } else { S->buffer[0] = S->top; S->buffer[1] = S->data[S->top]; *x = S->data[S->top]; S->top--; } return 0; } /** * @brief 获取栈顶元素 * * @param S 顺序栈指针 * @param x * * @return 状态码:0为成功 **/ int getSequenceStack(SequenceStack* S, DataType* x) { if (isEmptySequenceStack(S)) { printf("%s", errorHint[2]); return errorCode[2]; } else { *x = S->data[S->top]; } return 0; } /** * @brief 顺序栈判空 * * @param S 顺序栈指针 * * @return 状态码:1为空 **/ int isEmptySequenceStack(SequenceStack* S) { return S->top == -1 ? 1 : 0; } /** * @brief 顺序栈判满 * * @param S 顺序栈指针 * * @return 状态码:1为满 **/ int isFullSequenceStack(SequenceStack* S) { return S->top == S->maxSize - 1 ? 1 : 0; } /** * @brief 顺序栈销毁 * * @param S 顺序栈指针 * * @return 状态码:0为成功 **/ int destroySequenceStack(SequenceStack* S) { free(S->data); return 0; } /** * @brief 撤销操作 * * @param S 顺序栈指针 * * @return 状态码:0为成功 **/ int revocationSequenceStack(SequenceStack* S) { if ((S->buffer[0]) == -1) { printf(errorHint[3]); return errorCode[3]; } else { if (S->buffer[0] > S->top) { if (pushSequenceStack(S, S->buffer[1]) == 0) { printf("操作已撤销!\n\n"); } } else { if (popSequenceStack(S, &S->buffer[1]) == 0) { printf("操作已撤销!\n\n"); } } } return 0; }
main.c(主函数):
#include <stdio.h> #include <stdint.h> #include <string.h> #include "welcome.h" #include "sequenceStack.c" /*十进制转换为二进制*/ int conversion(int decimalism); /*后缀表达式*/ int expression(); int main(int argc, char *argv[]) { for (int i = 0; i < 19; i++) { printf("%s", welcome[i]); for (int j = 0; j <= 100000000; j++) { for (int m; m <= 100000000; m++) { ; } } } int operate; int maxSize; char verify; DataType data; SequenceStack S; // PolynomialList* pa, pb, sum; printf("\n\n本应用程序为数据结构-顺序栈演示程序,用于课程实践与研究。由季老师指导,日月同珲开发。\n\n"); do { printf("<----------------------------------------------------顺序栈演示程序--------------------------------------------------->\n"); printf("0. 退出程序\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("请输入操作选项(0~8,0为退出):"); scanf("%d", &operate); switch (operate) { case 1: printf("请输入顺序栈最大容量:"); scanf("%d", &maxSize); // getchar(); if (!initSequenceStack(&S, maxSize)) { printf("初始化完成\n\n"); } break; case 2: printSequenceStack(&S); break; case 3: printf("请输入入栈数据:"); scanf("%d", &data); if (!pushSequenceStack(&S, data)) { printf("入栈成功!\n\n"); } break; case 4: while (1) { printf("出栈操作无法完全恢复是否要出栈?(y/n):"); getchar(); scanf("%c", &verify); if (verify == 89 || verify == 121) { if (!popSequenceStack(&S, &data)) { printf("出栈成功!\n\n"); } break; } else if (verify == 78 || verify == 110) { printf("出栈操作已取消\n\n"); break; } } break; case 5: revocationSequenceStack(&S); break; case 6: printf("请输入需要转换的十进制整数:"); scanf("%d", &data); conversion(data); break; case 7: expression(); break; case 8: printf("本应用程序为数据结构-顺序栈演示程序,用于课程实践与研究。由季老师指导,日月同珲开发。\n\n"); break; case 0: return 0; break; } } while (operate != 0); return 0; } int conversion(int decimalism) { SequenceStack S; int binary; initSequenceStack(&S, 32); while (decimalism) { pushSequenceStack(&S, decimalism % 2); decimalism /= 2; } while (!isEmptySequenceStack(&S)) { popSequenceStack(&S, &binary); printf("%d", binary); } printf("\n"); destroySequenceStack(&S); return 0; } int expression() { SequenceStack S; int i; int op1, op2; int x; char exp[20]; /*后缀表达式*/ initSequenceStack(&S, 10); printf("请输入一个后缀表达式(eg. 56+):"); scanf("%s", exp); for (i = 0; i < (int) 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': /*入栈*/ pushSequenceStack(&S, exp[i] - 48); break; case '+': popSequenceStack(&S, &op1); popSequenceStack(&S, &op2); x = op1 + op2; pushSequenceStack(&S, x); break; case '-': popSequenceStack(&S, &op1); popSequenceStack(&S, &op2); x = op1 - op2; pushSequenceStack(&S, x); break; case '*': popSequenceStack(&S, &op1); popSequenceStack(&S, &op2); x = op1 * op2; pushSequenceStack(&S, x); break; case '/': popSequenceStack(&S, &op1); popSequenceStack(&S, &op2); x = op1 / op2; pushSequenceStack(&S, x); break; } } popSequenceStack(&S, &x); printf("计算结果为:%s = %d\n", exp, x); destroySequenceStack(&S); return 0; }
error.h(错误信息):
int errorCode[] = {100001, 100002, 100003, 100004, 100005}; char* errorHint[] = { "ERROR[100001]:申请内存错误,初始化失败!\n\n", "ERROR[100002]:顺序栈已满!\n\n", "ERROR[100003]:顺序栈为空!\n\n", "ERROR[100004]:无可撤销操作!\n\n", "ERROR[100005]:申请内存错误,结点创建失败!\n\n", };
四、运行截图
五、小结
栈其实在我的日常开发中接触较多,Android中的任务栈则是最经典的例子。在理解的同时活用栈的特性是比较重要的。本周内容个人觉得比较简单,所以总结并不多。
每周闲篇:本周虽然同样忙碌,但已经好上不少了,最要紧的活数据已经接通(虽然写法不太规范),下周完善收尾再优化一下即可~
六、参考文献
王海艳等.《数据结构(C语言)》第二版[M].北京:人民邮电出版社,2020:10~14.