数据结构与算法分析(7)表、栈和队列(二)
介绍栈的相关知识:
(2)栈ADT:
2.1栈模型:
栈是限制插入和删除只能在一个位置上进行的表,该位置是表的末端,叫做栈的顶(top)。对栈的基本操作有Push(进栈)和Pop(出栈),前者相当于插入,后者则是删除最后插入的元素。
栈有时又叫做LIFO(后进先出表)。一般的栈模型是,存在某个元素位于栈顶,而该元素是唯一可见元素。
2.2栈的实现:
由于栈是一个表,所以任何实现表的方法都可以实现栈,我们将给出两种方法,一个使用指针,一个使用数组。
1)栈的链表实现:
栈的链表实现的例程:
struct Node; typedef struct Node *PtrToNode; typedef PtrToNode Stack; int IsEmpty(Stack s); Stack CreateStack(void); void DisposeStack(Stack s); void MakeEmpty(Stack s); void Push(ElementType x,Stack s); ElementType Top(Stack s); void Pop(Stack s); struct Node{ ElementType Element; PtrToNode Next; };
栈的链表实现的简单实例:
编写程序,从键盘输入10个数据放入栈中,然后从栈中依次弹出这些数据,并输出。
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<math.h> 4 5 struct Node{ 6 int Element; 7 struct Node* Next; 8 }; 9 //定义一个指向结点类型变量的指针类型 10 typedef struct Node *Stack; 11 Stack creatStack(){ 12 Stack s; 13 s=malloc(sizeof(struct Node)); 14 if(s==NULL) 15 printf("Out of space!"); 16 s->Next=NULL; 17 return s; 18 } 19 void Push(int n,Stack s){ 20 struct Node *temp; 21 temp=malloc(sizeof(struct Node)); 22 if(temp==NULL) 23 printf("Out of Space!"); 24 else{ 25 temp->Element=n; 26 temp->Next=s->Next; 27 s->Next=temp; 28 } 29 } 30 int IsEmpty (Stack s){ 31 return s->Next==NULL; 32 } 33 int Top(Stack s){ 34 struct Node *temp; 35 if(IsEmpty(s)){ 36 printf("Empty stack"); 37 return 0; 38 } 39 else 40 return s->Next->Element; 41 } 42 void Pop(Stack s){ 43 struct Node *temp; 44 if(IsEmpty(s)) 45 printf("Empty stack"); 46 else 47 temp=s->Next; 48 s->Next=s->Next->Next; 49 free(temp); 50 } 51 void MakeEmpty(Stack s){ 52 if(s==NULL){ 53 printf("此栈为空,必须先创建一个栈!\n"); 54 }else{ 55 while(!IsEmpty(s)){ 56 Pop(s); 57 } 58 } 59 } 60 int main(){ 61 printf("请输入10个整数,压入栈中:\n"); 62 Stack s; 63 s=creatStack(); 64 int temp; 65 int k; 66 for(k=0;k<10;k++){ 67 //printf("请输入第%d个数\n",k+1); 68 temp=rand()%100; 69 printf("Num[%d]=%2d ",k+1,temp); 70 Push(temp,s); 71 } 72 printf("\n下面按照顺序弹出栈中所有元素并输出:\n"); 73 for(k=0;k<10;k++){ 74 printf("第%2d个出栈的是%4d",k+1,s->Next->Element); 75 if((k+1)%5==0){ 76 printf("\n"); 77 } 78 Pop(s); 79 } 80 return 0; 81 }
2)栈的数组实现:
栈的数组实现避免了指针并且可能是更加流行的解决方案。这种策略的唯一潜在危害是我们需要提前声明一个数组的大小。
栈的数组实现中一个很大的问题是对空栈的Pop和对满栈的Push都将超出数组的界限并引起程序的崩溃,因此有时可能需要防止用于构造栈的数组的下标越界。
栈的数组实现的例程:
#include<stdlib.h> #include<stdio.h> struct StackRecord; typedef struct StackRecord *Stack; typedef int ElementType; int IsEmpty(Stack s); int IsFull(Stack s); Stack CreateStack(int MaxElements); void DisposeStack(Stack s); void MakeEmpty(Stack s); void Push(ElementType x,Stack s); ElementType Top(Stack s); void Pop(Stack s); ElementType TopAndPop(Stack s); #define EmptyTOS (-1) #define MinStackSize (5) struct StackRecord{ int Capacity; int TopOfStack; ElementType *Array; }; void MakeEmpty(Stack s){ s->TopOfStack=EmptyTOS; } int IsFull(Stack s){ return s->TopOfStack==s->Capacity; } Stack CreateStack(int MaxElements){ Stack s; if(MaxElements<MinStackSize){ printf("所建的栈空间太小"); MaxElements=5; } s=malloc(sizeof(struct StackRecord)); s->Array=malloc(sizeof(ElementType)*MaxElements); s->Capacity=MaxElements; MakeEmpty(s); return s; } void DisposeStack(Stack s){ if(s!=NULL){ free(s->Array); free(s); } } int IsEmpty(Stack s){ return s->TopOfStack==EmptyTOS; } void Push(ElementType x,Stack s){ if(!IsFull(s)){ printf("栈空间已经用完了"); } else s->Array[++s->TopOfStack]=x; } ElementType Top(Stack s){ if(!IsEmpty(s)) return s->Array[s->TopOfStack]; printf("空栈"); return 0; } void Pop(Stack s){ if(IsEmpty(s)) printf("空栈"); else s->TopOfStack--; } ElementType TopAndPop(Stack s){ if(!IsEmpty(s)) return s->Array[s->TopOfStack--]; printf("空栈"); return 0; }
栈的数组实现的实例在此就不做介绍了,下面的后缀表达式的实例涉及到了。
2.4栈的应用:
1)平衡符号:
编译器检查你的程序的语法错误,但是常常由于缺少一个符号(如遗漏一个花括号或者注释起始符)引起编译器列出上百行的诊断,而真正的错误却没有找出。
在这种情况下一个有用的工具就是检验是否每件事情都能成对出现的一个程序:
做一个空栈。读入字符指到文件尾,如果字符是一个开放的符号,则将推入栈中。如果是一个封闭符号,则当栈为空时报错,否则将栈元素推出。在文件尾,如果栈非空则报错。
2)后缀表达式:
主要包括后缀表达式的计算和中缀到后缀的转换:
编写程序,从键盘输入一个中缀表达式,利用栈先转换为后缀表达式,然后计算该后缀表达式的结果,要求
(1)用栈的数组实现完成
(2)为简单起见,表达式中的操作数为非负整数
(3)操作符仅包括加(+)、乘(*)、左括号(、右括号)
(4)若后缀表达式正确,输出计算结果;若表达式有误,输出错误提示信息
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<string.h> 4 5 #define EmptyTOS (-1) 6 #define MinStackSize 5 7 8 struct StackRecord{ 9 int Capacity;//能存元素最大量 10 int TopOfStack;//记录新插入的元素所在数组中的位置 11 char Array[20][5];//字符串数组,每个字符串的大小最多为5 12 }; 13 typedef struct StackRecord *Stack; 14 15 void MakeEmpty(Stack s){ 16 s->TopOfStack=EmptyTOS; 17 } 18 19 Stack CreateStack(int MaxElement){ 20 Stack s; 21 if(MaxElement<MinStackSize){ 22 printf("要创建的栈太小,应大于5。\n"); 23 exit(0); 24 }else{ 25 s=malloc(sizeof(struct StackRecord)); 26 s->Capacity=MaxElement; 27 MakeEmpty(s); 28 } 29 return s; 30 } 31 32 //判断栈是否为空栈 33 int isEmpty(Stack S){ 34 return S->TopOfStack==EmptyTOS; 35 } 36 //判断是否满了,当为1是满了,为0是表示未满 37 int IsFull(Stack S){ 38 if(S->TopOfStack+1>=S->Capacity){ 39 return 1; 40 }else 41 return 0; 42 } 43 //压栈 44 void Push(char *x,Stack S){ 45 if(IsFull(S)){ 46 printf("栈已经满了!\n"); 47 }else{ 48 strcpy(S->Array[++S->TopOfStack],x); 49 } 50 } 51 //只获得头元素 52 char *Top(Stack S){ 53 if(isEmpty(S)){ 54 printf("此栈为空,无法取栈头元素!\n"); 55 exit(0); 56 }else{ 57 return S->Array[S->TopOfStack]; 58 } 59 } 60 //只删除头元素 61 void Pop(Stack S){ 62 if(isEmpty(S)){ 63 printf("此栈为空,无法去除栈头元素!\n"); 64 }else{ 65 S->TopOfStack--; 66 } 67 } 68 //获取头元素并删除 69 char *PopAndTop(Stack S){ 70 if(isEmpty(S)){ 71 printf("此栈为空,无法执行获取栈头元素和去除栈头元素!\n"); 72 exit(0); 73 }else{ 74 return S->Array[S->TopOfStack--]; 75 } 76 } 77 //释放栈空间 78 void DisposeStack(Stack s){ 79 if(s!=NULL){ 80 free(s); 81 } 82 } 83 void printStack(Stack s){ 84 int i; 85 for(i=0;i<=s->TopOfStack;i++){ 86 printf("%s ",s->Array[i]); 87 } 88 } 89 90 int isNumber(char p){ 91 if(p>='0'&&p<='9'){ 92 return 1; 93 }else 94 return 0; 95 } 96 int isOperator(char *p){ 97 //当长度为1,且不为从0到9的数时,才是操作符 98 if(isNumber(p[0])){ 99 return 0; 100 }else 101 return 1; 102 } 103 int exist(char *p,Stack temp){ 104 int i; 105 for(i=0;i<=temp->TopOfStack;i++){ 106 if(strcmp(temp->Array[i],p)==0){ 107 return 1; 108 } 109 } 110 return 0; 111 } 112 //位置是从栈头开始计算,所以谁小谁离栈比较远 113 int LastPosition(char *p,Stack temp){ 114 int i; 115 if(exist(p,temp)){ 116 for(i=temp->TopOfStack;i>=0;i--){ 117 if(strcmp(temp->Array[i],p)){ 118 return i; 119 } 120 } 121 } 122 else{ 123 //这个应该执行不到 124 //printf("临时栈没有%s这个操作符\n",p); 125 } 126 } 127 void turnInto(Stack A,Stack B){ 128 Stack temp=CreateStack(10); 129 int i; 130 for(i=0;i<=A->TopOfStack;i++){ 131 //如果不是操作符,直接输出到后缀表达式 132 if(!isOperator(A->Array[i])){ 133 strcpy(B->Array[++B->TopOfStack],A->Array[i]); 134 //printf("输出中存的有:%s\n",A->Array[i]); 135 }else{ 136 char c=A->Array[i][0]; 137 //当是操作符时,有可能是右括号 138 if(c==')'){ 139 //printStack(temp); 140 //当两个字符串不相等时 141 while(!strcmp( "(",Top(temp) )==0){ 142 strcpy(B->Array[++B->TopOfStack],PopAndTop(temp)); 143 //printf("输出中存的有:%s\n",B->Array[B->TopOfStack]); 144 } 145 Pop(temp); 146 }else if(c=='('){ 147 //Push("(",temp); 148 //printf("%s",A->Array[i]); 149 Push(A->Array[i],temp); 150 }else if(c=='+'){ 151 //如果临时栈中有乘号 152 if(exist("*",temp)){ 153 //printStack(temp); 154 //当没有左括号或者有,但左括号在乘号的左边 155 if( !exist("(",temp) || ( exist("(",temp)&& LastPosition("(",temp)>LastPosition("*",temp) ) ){ 156 //printf("%d<%d",LastPosition("(",temp),LastPosition("*",temp)); 157 //将乘号及以后的操作符输出到后缀表达式 158 while(Top(temp)[0]!='*'){ 159 //printf("%s",Top(temp)); 160 strcpy( B->Array[++B->TopOfStack],PopAndTop(temp) ); 161 //printf("输出中存的有:%s\n",A->Array[i]); 162 } 163 strcpy(B->Array[++B->TopOfStack],PopAndTop(temp)); 164 //printf("输出中存的有:%s\n",B->Array[B->TopOfStack]); 165 Push(A->Array[i],temp); 166 //printStack(temp); 167 }else 168 Push(A->Array[i],temp); 169 } 170 else{ 171 //如果不存在乘号 172 Push(A->Array[i],temp); 173 } 174 }else if(c=='*'){ 175 strcpy(temp->Array[++temp->TopOfStack],A->Array[i]); 176 } 177 } 178 } 179 while(!isEmpty(temp)){ 180 strcpy(B->Array[++B->TopOfStack],PopAndTop(temp)); 181 } 182 } 183 184 void calculate(Stack A){ 185 //定义一个辅助计算后缀表达式的栈 186 Stack as=CreateStack(10); 187 int result,i; 188 int a,b,temp; 189 char str[5]; 190 for(i=0;i<=A->TopOfStack;i++){ 191 //printf("%s\n",A->Array[i]); 192 if(isNumber(A->Array[i][0])){ 193 Push(A->Array[i],as); 194 }else{ 195 int a=atoi(PopAndTop(as)); 196 int b=atoi(PopAndTop(as)); 197 char c=A->Array[i][0]; 198 //putchar(c); 199 switch(c){ 200 case '+':temp=a+b;break; 201 case '*':temp=a*b;break; 202 default:printf("不知名错误!\n"); 203 } 204 itoa(temp,str,10); 205 Push(str,as); 206 } 207 } 208 209 if(as->TopOfStack<=0){ 210 strcpy(str,Top(as)); 211 result=atoi(str); 212 printf("计算的结果为:%3d\n",result); 213 } 214 } 215 216 int main(){ 217 printf("请输入一个中缀表达式(最多30个字符的字符串),只包含非负整数,+号,-号和()两个括号!\n"); 218 Stack s_center=CreateStack(20); 219 Stack s_later=CreateStack(20); 220 char x[30]; 221 gets(x); 222 int i=0; 223 //用于提取有多个位数的数字,或操作符 224 char temp[5]; 225 int flag; 226 while(x[i]!='\0'){ 227 flag=-1; 228 while(isNumber(x[i])){ 229 temp[++flag]=x[i]; 230 temp[flag+1]='\0'; 231 i++; 232 } 233 Push(temp,s_center); 234 //printf("temp=%s\n",temp); 235 while(!isNumber(x[i])){ 236 if(x[i]!='\0'){ 237 temp[0]=x[i]; 238 temp[1]='\0'; 239 i++; 240 Push(temp,s_center); 241 //printf("temp=%s\n",temp); 242 }else{ 243 break; 244 } 245 } 246 } 247 printf("此中缀表达式为:\n"); 248 printStack(s_center); 249 //将中缀表达式转化为后缀表达式 250 turnInto(s_center,s_later); 251 printf("\n这个后缀表达式是:\n"); 252 printStack(s_later); 253 printf("\n"); 254 calculate(s_later); 255 return 0; 256 } //这里的所有用来输出的被注释掉的语句都是调试用的。
这里只是简单的介绍包括+,*,(,)的运算,以后还会将四则运算全部解决,敬请期待。
3)函数调用
检测平衡符号的算法提供一种实现函数调用的方法。当主调函数调用被调函数时,系统需要将主调例程中的所有局部变量存起来以防止被被调函数的变量覆盖,而且还要记录主调函数的位置,以便被掉函数运行完时知道向哪里转移。在这里函数调用和函数返回就类似与开括号和闭括号。
函数调用问题的过程所有的工作都可以由一个栈来完成,所存储的信息或称为活动记录(activation record),或叫做栈帧(stack frame)。
在这里我们简要了解,如果以后的工作需要用到单片机,汇编语言,计算机组成原理和编译原理时,我会深入探讨这个问题。