栈和队列都属于线性表
什么是栈
栈也是一种线性表,但它是受到限制的线性表,就是先放入的东西后被取到,后放入的东西优先被取到,认为其遵循 “后进先出” 原则。
栈中允许插入和删除操作的一端成为栈顶,不允许插入和删除操作的一端成为栈底。
向一个栈中插入新元素称为入栈或压栈,入栈之后该元素被放在栈顶元素的上面,称为新的栈顶元素
从一个栈中删除元素又称为出栈或弹栈,是把栈顶元素删除,使其相邻元素成为新的栈顶元素。
栈也分为顺序栈和链栈两种存储结构。
栈的操作
入栈
入栈:先将元素插入到栈中,然后按照数据入栈的先后顺序,从下往上以此排列,每当插入新的元素时,栈顶指针就会向上移动,指向新插入的元素
(顺序栈)
从上图可以看出,向栈中插入元素时,需要先使top指针指向栈顶元素上面的空位,然后进行赋值。
当栈已满时不能执行入栈操作
出栈
执行出栈操作时,栈顶元素会被先弹出,接着按照后进先出的原则将栈中的元素依次弹出。弹出栈顶元素后,栈顶指针就向下移动,指向原栈顶下面的一个元素,这个元素就成为了新的栈顶元素。
(顺序栈)
栈为空时,不能继续执行出栈操作。
【注意】:出栈是将top指针向下移动,指示到新的栈顶元素,原来的栈顶元素依然存在于存储单元中,但无法通过栈进行访问.
若要从栈中获取元素,只能通过栈顶指针取到栈顶元素,无法取得其他元素,,在上图中,当Data3没有被弹出时,无法读取到下面的元素。
删除栈中元素
只将top指针往下移动,指向新的栈顶元素,删除过程参考上图的出栈
栈的实现(顺序栈)
两栈共享空间(顺序栈)
如果有两个相同类型的栈,为它们各自开辟了数组空间,极有可能是第一个栈已经满了,再进栈就溢出了,而另外一个栈还有很多存储空间。所以两栈共享空间的思想是:让一个栈的栈底为数组的开始端,即下标为0处,另一个栈的栈底为数组的末端,即下标为数组长度的n-1出,这样,两个栈如果增加元素,就是两端点向中间延伸。当两个栈见面之时,也就是两个指针相差1时,即top1 + 1 == top2时为栈满。
不使用两栈共享空间的顺序栈实现的基本原理都差不多,不在详细叙述
定义最大长度
#define MAX 20
定义结构体
typedef struct
{
int data[MAX];
int top1,top2;//两栈共享空间
}strock;
创建空栈(初始化栈)
void createStrock(strock *s,int n){
if(n == 1)//判断初始化哪一个栈
s->top1 = -1;
if(n == 2)
s->top2 = MAX;
}
入栈
//入栈
void Push(strock *s,int n){
if (s->top2 - s->top1 == 1)
{
printf("栈已满\n");
}
else{
if (n == 1)
{
scanf("%d",&s->data[++s->top1]);
}
if (n == 2)
{
scanf("%d",&s->data[--s->top2]);
}
}
}
获取栈顶元素
//获取栈顶元素
void showData(strock *s,int n){
if (n == 1 && s->top1 > -1)
{
printf("top1栈顶元素为:%d\n",s->data[s->top1]);
}
else if (n == 2 && s->top2 < MAX)
{
printf("top2栈顶元素为:%d\n",s->data[s->top2]);
}
else
printf("栈为空或其它错误!\n");
}
出栈
//出栈
int Pop(strock *s,int n){
int x;
if(s->top1 != -1 && n == 1){
x = s->data[s->top1];
printf("栈顶元素:%d\n",s->data[--s->top1]);
}
else if (s->top2 != MAX && n == 2)
{
x = s->data[s->top2];
printf("栈顶元素:%d\n",s->data[++s->top2]);
}
else
x = -1;
return x;
}
顺序栈的其他操作不再详细介绍
链栈
栈的链式存储也称为链栈,它和链表的存储原理一样,都可以利用闲散空间来存储元素,用指针来建立各结点之间的逻辑关系。链栈也会设置一个栈顶元素的标识符top,称为栈顶指针。和链表的区别是,它只能在一端进行各种操作。说白了,链栈就是一个单端操作的链表。
创建结构体
typedef struct stackNode{//数据结点
int data;//数据
struct stackNode *next;//指针
}sNode;
typedef struct topNode{//此结构记录栈的大小和栈顶元素指针
sNode *top;//链栈元素指针
int lens;//栈大小
}stackTop;
创建栈
//创建栈
stackTop *Create(){
stackTop *h;
h = (stackTop*)malloc(sizeof(stackTop));//开辟空间
//栈链可以不需要头节点
h->top = NULL;
h->lens = 0;
return h;
}
判断栈是否为空
//判断链栈是否为空
int IsEmpty(stackTop *h){
if(h->lens == 0 || h->top == NULL)
return 1;
return 0;
}
入栈
//入栈
//入栈
void push(stackTop *h){
sNode *node = (sNode *)malloc(sizeof(sNode));
scanf("%d",&node->data);//数据
node->next = h->top;//新元素指向下一个结点
h->top = node;//top指向心新结点
h->lens++;
}
出栈
//出栈
void Pop(stackTop *h){
if(isEmpty(h)){
printf("栈为空\n");
}
else{
sNode *top1 = h->top;//top1指向栈顶元素
h->top = top1->next;//top指向下一个元素
free(top1);
h->lens--;
}
}
获取栈顶元素
//获取栈顶元素
//获取栈顶元素
void get(stackTop *h){
sNode *node = h->top;
if(isEmpty(h))
printf("栈为空\n");
else
printf("%d\n",node->data);
}
中缀转后缀表达式
中缀表达式:是一个通用的算术或逻辑公式表示方法, 操作符是以中缀形式处于操作数的中间(例:3 + 4),中缀表达式是人们常用的算术表示方法,但是不容易被计算机解析。
后缀表达式;逆波兰式(Reverse Polish notation,RPN,或逆波兰记法),也叫后缀表达式。
中缀转后缀表达式:中缀表达式的简单是相对人类的思维结构来说的,对计算机而言中缀表达式是非常复杂的结构。相对的,逆波兰式在计算机看来却是比较简单易懂的结构。因为计算机普遍采用的内存结构是栈式结构,它执行先进后出的顺序。
转化算法
首先需要分配2个栈,一个作为临时存储运算符的栈S1(含一个结束符号),一个作为存放结果(逆波兰式)的栈S2(空栈)。
1. 从左到右扫描依次每一个字符。如果扫描到的字符是操作数(如a、b、c等),就直接存储到栈S2中。
2. 如果扫描到的字符是一个操作符,可以分为三种情况:
(1)如果栈S1为空,直接将操作符存储到栈S1中
(2)如果该操作符的优先级大于栈S1栈顶的操作符(不包括括号运算符),就直接将操作符存储到栈S1中
(3)如果该操作符的优先级低于栈S1栈顶的操作符,就将栈S1栈顶的操作符导出并存储到栈S2中 ,直至栈S1栈顶运算符(包括左括号)低于(不包括等于)该运算符优先级时停止弹出运算符,最后将该运算符送入S1栈。
3. 如果遇到的操作符是左括号"(”,直接将该操作符存储到栈S1中。并且,该符号需特殊处理,如果扫描到的操作符是右括号“)”,将栈S1中的操作符出栈,存储到栈S2中,直到遇见左括号“(”。并将栈S1中的左括号出栈。继续扫描下一个字符
4. 重复上述步骤,直至处理完所有的输入字符。
5. 如果中缀表达式扫描完毕,则将S1栈内所有运算符(不包括“#”),逐个出栈,依次送入S2栈。
6. 完成以上步骤,S2栈便为逆波兰式输出结果。不过S2应做一下逆序处理。便可以按照逆波兰式的计算方法计算了。
后缀转中缀表达式
将后缀表达式依次元素的压入到栈中,当压入的是字符是不采取任何操作,当压入的是运算符时,把运算符下面的两个字符依次弹出和运算符进行运算,然后把结果继续压入栈中,重复以上操作,直到结束。