1.知识构件图
2.栈的基本操作
1) 顺序栈:
a) 基本操作:
1.置空栈
1. SeqStack *InitStack (SeqStack * S){
2. S->top = -1;
3. return S;
4. }
2.判栈空
1. if(S->top>=0)
2. return 0;
3. else
4. return 1;
5. }
3.判栈满
1. int StackFull(SeqStack * S){
2. if(S->top<maxlen-1&&S->top>=0)
3. return 0;
4. else
5. return 1; }
4.取栈顶元素
1. Datatype GetTop(SeqStack * S){
2. if(S->top<=maxlen-1&&S->top>=0)
3. return (S->data[S->top]);
4. else
5. printf("error");
6. }
5.入栈
1. void Push(SeqStack * S,Datatype x){
2. if(S->top<maxlen-1&&S->top>=-1){
3. S->top++;
4. S->data[S->top] = x;
5. }
6. else
7. printf("error");
8. }
6.出栈
1. void Pop(SeqStack * S){
2. if(S->top>=0)
3. S->top--;
4. else
5. printf("error");
6. }
b) 性能分析:
顺序栈的这6种基本操作中,置空栈,判空栈,判栈满,以及取栈顶元素操作的运算的算法执行时间和问题的规模无关,算法的时间复杂度为O(1)。出栈和入栈也和元素的个数没有关系,所以时间复杂度为O(1)。
顺序栈的操作运算时间执行时间所需要的空间都是用于存储算法本身的指令,常数,变量,各算法的空间性能均好,只是对于较大的数据很难把握。
2)链栈
a) 基本操作:
1.置空栈
1. LinkStack * SetStack (){
2. LinkStack * LS;
3. LS == NULL;
4. return LS;
5. }
2.判栈空
1. int StackEmpty(LinkStack * S){
2. if(LS == NULL)
3. return 1;
4. else
5. return 0;
6. }
3.取栈顶元素
1. Datatype GetTop(LinkStack * LS){
2. if(LS!=NULL)
3. return(LS->data);
4. else
5. printf("栈空\n");
6. }
4.入栈
1. LinkStack * Push(LinkStack *LS,Datatype x){
2. LinkStack * p;
3. p = (LinkStack *)malloc(sizeof(LinkStack));
4. p->data = x;
5. p->next = LS;
6. LS = p;
7. return LS;
8. }
5.出栈
1. LinkStack * Pop(LinkStack *LS){
2. LinkStack * u;
3. u = LS;
4. LS = u->next;
5. free(u);
6. return LS;
7. }
b) 性能分析:
链栈的基本操作都是在栈顶进行的,算法的执行时间和栈的大小无关,即运算的时间复杂度都是O(1)。
链栈的执行基本时间所需要的空间和顺序栈一样,都是用于存储算法本身所用的指令,常数
链栈的每个节点需要额外的空间来存储放置后继节点的地址,所以,若栈中元素实际所需要的存储空间不大。
3.栈的应用举例
1) 数制转换问题:利用栈的特性将一个十进制的数字转换为任意进制的数字。
分析:
将10进制的数字转换为任意进制的数字,是通过相除取余的方法来获得的,让这个10进制的数字除以对应的任意进制数然后取余数,直到10进制数为0然后将余数倒序输出即为所求
利用栈的先进后出的特点,将每一次取得的余数放入栈中,等到10进制数除完之后,将栈中的元素依次弹出。即为所求的任意进制数。
1. #include <stdio.h>
2. const int maxlen = 100;
3. int N,d,e;
4. typedef struct
5. {
6. int data[maxlen];
7. int top;
8. }SeqStack;
9. SeqStack * S;
10. SeqStack *InitStack (SeqStack * S){
11. S->top = -1;
12. return S;
13. }
14. int StackEmpty(SeqStack * S){
15. if(S->top>=0)
16. return 0;
17. else
18. return 1;
19. }
20. int GetTop(SeqStack * S){
21. if(S->top<=maxlen-1&&S->top>=0)
22. return (S->data[S->top]);
23. else
24. printf("error");
25. }
26. void Push(SeqStack * S,int x){
27. if(S->top<maxlen-1&&S->top>=-1){
28. S->top++;
29. S->data[S->top] = x;
30. }
31. else
32. printf("error");
33. }
34. void Conversion(){
35. InitStack(S);
36. scanf("%d %d",&N,&d);
37. while(N){
38. Push(S,N%d);
39. N = N/d;
40. }
41. while(!StackEmpty(S)){
42. e = GetTop(S);
43. }
44. }
45. int main(int argc, char const *argv[])
46. {
47. //freopen("input.txt","r",stdin);
48. //freopen("output.txt","w",stdout);
49. Conversion();
50. printf("\n");
51. return 0;
52. }
2) 算术表达式的计算:借助栈来实现按运算符的优先级完成表达式的求值计算。
分析:
利用栈来求算法表达式是很经典的一个栈的应用题,早在1960s Dijkstra就提出了用双栈解决带括号的表达式求值算法。就是建立两个栈,一个作为存储数字的栈,另一个作为存储运算符的栈,然后扫描表达式,遇到数字就压入数字栈中,遇到运算符就压入运算符栈中,然后遇到一次运算符就和运算符中栈中的栈顶运算符比较优先级,从而可以根据优先级选择从数字栈中弹出数字进行运算然后将结果压入数字栈中或者将运算符压入运算符栈中等待操作。
但是这个算法存在一个弊端,这种算法不能计算含多重括号的复杂算术表达式或者是含有未知数的表达式,为了有一个算法可以应对所有的情况,在查询了资料之后,找到了一种将传统的中缀表达式转换为前缀表达式(波兰式)或者后缀表达式(逆波兰式)的算法可以应对这些情况,而且不需要和传统的一样利用判断结构来判断运算符的优先级。
下面是代码:
1. #include <stdio.h>
2. #include <stdlib.h>
3. #define newp (stype *)malloc(sizeof(stype)) //定义一个申请栈地址的宏
4. typedef struct _stack{
5. char dat;
6. struct _stack *next;
7. } stype; //建立栈类型
8. int tance(char x) //探测优先级很多种写法,觉得这种是最简洁的了
9. {
10. if(x=='+'||x=='-') return 0;
11. else if (x=='*'||x=='/') return 1;
12. else if (x=='@'||x=='('||x==')') return -1;
13. }
14. int main()
15. {
16. stype *s,*top; //栈指针和栈顶指针
17. char c;
18. s=newp;
19. s->dat='@';
20. s->next=NULL;
21. top=s;
22. c=getchar(); //此后为读取中缀表达式的部分,用字符一个一个的读,直到读到回车
23. while(c!='\n')
24. {
25. if (c>='0'&&c<='9') //如果读入数字,直接打印
26. {
27. printf("%c ",c);
28. }
29. else if (c=='(') //如果是左括号,直接进栈
30. {
31. s=newp;
32. s->dat=c;
33. s->next=top;
34. top=s;
35. }
36. else if (c==')') //如果是右括号,匹配左括号,把两者之间的栈内符号全部弹出
37. {
38. while (top->dat!='(')
39. {
40. s=top;
41. printf("%c ",top->dat);
42. top=top->next;
43. free(s);
44. }
45. s=top;
46. top=top->next;
47. free(s);
48. }
49. else //否则肯定是+-*/了
50. {
51. int a=tance(c);
52. int b=tance(top->dat); //比较该符号和栈顶符号的优先级
53. if (a>b) //如果大于直接压进去
54. {
55. s=newp;
56. s->dat=c;
57. s->next=top;
58. top=s;
59. }
60. else //否则就把栈顶的符号一直弹出,直到弹到可以压进去,然后压进去(也就是说等于也不能压进去)
61. {
62. while (a<=b)
63. {
64. s=top;
65. printf("%c ",top->dat);
66. top=top->next;
67. free(s);
68. b=tance(top->dat);
69. }
70. s=newp;
71. s->dat=c;
72. s->next=top;
73. top=s;
74. }
75. }
76. c=getchar(); //读取下一个字符
77. }
78. while (top->dat!='@') //读完和还不算完,还要把栈内剩余的所有符号挨个弹出
79. {
80. s=top;
81. printf("%c ",top->dat);
82. top=top->next;
83. free(s);
84. }
85. return 0; //后缀表达式输出完毕
86. }
3) 文档编辑:借助栈实现输入文档时对输入文档的编辑修改。
分析:
在用户输入文档的时候,是不能输入一个就存储一个的,那样如果要修改的话会很麻烦,所以就需要用一个缓冲区来存储输入的数据,这个任务就交给栈来完成,用户输入字符串,遇到‘#’ 表示前面的一个字符无效,遇到‘@‘时表示前面一行的无效,遇到’*‘停止输入
我们可以建一个栈来完成这个任务,输入数据的时候入栈遇到‘#‘时弹出栈顶元素,遇到’@‘时清空栈,遇到‘*’时停止输入,然后栈里面的数据就是修改后的数据。
此外,我们建立结构体的时候可以不用数组,而是定义两个指针变量,一个指向栈顶的元素,一个指向栈底指针。这样不仅可以节省空间,还很方便的可以倒序输出。
代码:
1. #include <stdio.h>
2. #include <stdlib.h>
3. #include <malloc.h>
4. #define STACK_INIT_SIZE 100
5. #define STACKINCREMENT 10
6. #define OVERFLOW -2
7. #define OK 1
8. #define ERROR 0
9.
10. typedef char SElemType;
11. //栈结构体
12. typedef struct {
13. SElemType *base;
14. SElemType *top;
15. int stacksize;
16. }SqStack;
17. int InitStack(SqStack *S);//初始化栈
18. int Push(SqStack *S,SElemType e);//入栈
19. int Pop(SqStack *S,SElemType *e);//删除栈中的元素
20. int DestoryStack(SqStack *S);//销毁栈
21. void LineEdit(SqStack *S);//行编辑程序
22. int ClearStack(SqStack *S);//清空栈中的元素
23.
24. //初始化栈
25. int InitStack(SqStack *S) {
26. //S->base = (SElemType *)malloc(STACK_INIT_SIZE*sizeof(SElemType));
27. if(!S->base) {
28. exit(OVERFLOW);
29. }
30. S->top = S->base;
31. S->stacksize = STACK_INIT_SIZE;
32.
33. return OK;
34. }
35.
36. //入栈
37. int Push(SqStack *S,SElemType e) {
38. if((S->top-S->base)>=S->stacksize) {
39. //S->base = (SElemType*)realloc(S->base,(S->stacksize+STACKINCREMENT)*sizeof(SElemType));
40. if(!S->base) {
41. exit(OVERFLOW);
42. }
43. S->top = S->base + S->stacksize;
44. S->stacksize += STACKINCREMENT;
45. }
46. *S->top++ = e;
47. //printf("%c\n",e);
48. return OK;
49. }
50.
51. //删除栈中的元素
52. int Pop(SqStack *S,SElemType *e) {
53. if(S->top == S->base) return ERROR;
54. *e = *--S->top;
55. return OK;
56. }
57.
58. //清空栈中的元素
59. int ClearStack(SqStack *S) {
60. S->top = S->base;
61. return OK;
62. }
63.
64. //销毁栈
65. int DestoryStack(SqStack *S) {
66. S->top = S->base;
67. free(S->base);
68. S->top = NULL;
69. S->base = NULL;
70. return OK;
71. }
72.
73. //行编辑程序
74. void LineEdit(SqStack *S) {
75. SElemType *p,ch,c;
76. InitStack(S);
77. ch = getchar();
78. while(ch != EOF) {
79. while(ch!=EOF&&ch!='\n') {
80. switch(ch) {
81. case '#':Pop(S,&c);break;
82. case '@':ClearStack(S);break;
83. default:Push(S,ch);break;
84. }
85. ch = getchar();
86. }
87. p = S->base;
88. while(p!=S->top) {
89. printf("%c",*p);
90. ++p;
91. }
92. ClearStack(S);
93. if(ch!=EOF) ch = getchar();
94. }
95. }
96.
97. int main()
98. {
99. SqStack sq;
100. int f;
101. LineEdit(&sq);//进行括行编辑
102. DestoryStack(&sq);//将栈销毁
103. return 0;
104. }
4**.总结:**
以上就是栈这一章的知识点和代码了,最开始接触栈的时候是接触的C++ STL里面的栈,觉得栈是很不错的一种数据结构,顺序栈用的很熟悉了,但是对于链栈用的还不是很熟悉,以后可以多训练一下对链栈的训练,最短路算法中就有一个是Dijkstra算法然后用链栈来存储图,可以完成很多操作。所以栈是很强大而且又不是很复杂的数据结构,但是在使用栈的过程中,却要注意很多东西,顺序栈的时候要时刻注意的就是栈溢出问题,所以,以后在写题目的过程要多总结错误点,谨慎的写好每一个代码。