1.栈
(1)首先定义:
栈是限定仅在表尾进行插入和删除操作的线性表。
允许插入和删除的一端称为栈顶,另一端称为栈底,不含数据元素的栈称为空栈,栈是后进先出的线性表,简称LIFO结构。
表尾是栈顶,不是栈底,最先进栈的在栈底
栈的插入操作,叫进栈,压栈,入栈。
栈的删除操作,叫出栈或弹栈。
(2).栈的抽象数据类型:
ADT 栈(stack)
Data
同线性表,元素具有相同的类型,相邻元素之间有前驱后继的关系
Operator
InitStack(*S)//初始化栈,建立一个空栈
DestroyStack(*S)//若栈存在那就摧毁它
ClearStack(*S)//将栈清空
StackEmpty(S)//判断栈是否为空
GetTop(S,*e)//若栈非空则返回栈顶元素
push(*S,*e)//若栈存在那么插入e到栈中并成为S的栈顶元素
Pop(*S,*e)//删除栈顶元素,并用e返回其值
StackLength(S)//返回栈S的元素个数
endADT
(3)关于栈的例子:
#include<stdio.h>
#include<bits/stdc++.h>//meset函数是初始化数组的,第一个参数是数组的地第二个参数是数组的初始化值,第三个参数是数组的
#include<windows.h>//后边的sleep函数
#define Etype int
#define max 10
typedef struct{
Etype data[max];
int top;
}Sqstack;
void initstack(Sqstack *s)
{
memset(s->data,0,sizeof(s->data));
s->top=-1;
}
int creatstack(Sqstack *s,int n)
{
if(n<1||n>max-1)
{
printf("空间不足!\n");
return 0;
}
for(int i=0;i<n;i++)
{
printf("请输入第%d个元素的值",i+1);
scanf("%d",&s->data[i]);
s->top++;
}
return 1;
}
int trav(Sqstack *s)
{
printf("栈顶是:%d\n",s->top);
printf("以下是你栈中的元素:\n");
for(int i=0;i<=s->top;i++)
{
printf("%d\n",s->data[i]);
}
return 0;
}
int push(Sqstack *s,Etype e)//入栈
{
if(s->top==max-1)
{
printf("栈满!无法添加!!");
return 0;
}
s->top++;
s->data[s->top]=e;
return 1;
}
int pop(Sqstack *s,Etype *e)//出栈
{
if(s->top==-1)
{
printf("空栈,无法删除!\n");
return 0;
}
*e=s->data[s->top] ;
s->top--;
}
int main()
{
Sqstack s;
initstack(&s);
printf("请输入你要添加的元素个数:");
int n;
scanf("%d",&n);
creatstack(&s,n);
trav(&s);
printf("请输入将要入栈的元素:");
int h;
scanf("%d",&h);
push(&s,h);
printf("此时栈顶位置在第%d\n",s.top);
Sleep(2000);
printf("将有一个元素要出栈\n");
int m;
pop(&s,&m);
printf("这个出栈的元素是%d\n",m);
printf("此时栈顶在%d",s.top);
return 0;
}
(4)对两栈共享空间的理解:
首先两栈共享数据的原理是一个一维数组,设长度为n,而且注意,两栈的长度并不是五五开的,而是根据实际情况来说的,但是这里牢记不管栈1和栈2是怎样的只要栈1的top1的值是-1,那么栈1就是空栈,同理,如果如果栈2的top2是值是n时,那么栈2就是空栈,不管一个栈栈处于什么在状态,第二个栈都遵守上边的原则。若top1+1=top2那么两栈全满。
两栈的具体长度都是根据他们俩的top决定,若两个栈都有元素那么,且两栈没满的情况下,那么两栈的具体在数组里的长度不确定,但是如果两栈满的情况下,依据自己的top就可以得到每一个栈的长度了
让我们看一个具体的栈的例子:
#include<stdio.h>
#include<bits/stdc++.h>
#include<windows.h>
#define Etype int
#define max 10
typedef struct{
Etype data[max];
int top1;
int top2;
}Dstack;
void initstack(Dstack *s)
{
memset(s->data,0,sizeof(s->data));
s->top1=-1;
s->top2=max;
}
int add(Dstack *s,int m,int n)
{
Etype k;
printf("请输入你在栈1添加的%d个元素:\n",m);
for(int i=0;i<m;i++)
{
s->top1++;
printf("请输入第%d个元素:",i+1);
scanf("%d",&k);
s->data[s->top1]=k;
}
printf("第一个栈添加完毕,准备添加第二个栈。。。。。。\n");
Sleep(2000);
printf("请输入你在栈2添加的%d个元素:\n",n);
for(int i=0;i<n;i++)
{
s->top2--;
printf("请输入第%d个元素:",i+1);
scanf("%d",&k);
s->data[s->top2]=k;
}
}
int trav(Dstack *s)
{
printf("第一个栈中的元素有:");
for(int i=0;i<=s->top1;i++)
printf("第%d个元素的值为%d\n",i+1,s->data[i]);
printf("第二个栈中的元素有:");
int cnt=0;
for(int i=s->top2;i<max;i++)
{
printf("第%d个元素的值为%d\n",cnt+1,s->data[i]);
cnt++;
}
}
int push(Dstack *s,Etype e,int num)//进栈
{
if(s->top1+1==s->top2)
{
printf("栈满!");
return 0;
}
if(num==1)
{
s->data[++s->top1]=e;
}
else if(num==2)
{
s->data[--s->top2]=e;
}
return 1;
}
int pop(Dstack *s,Etype *e,int num)//出栈
{
if(num==1)
{
if(s->top1==-1)
{
printf("此栈为空\n");
return 0;
}
*e=s->data[s->top1--];
return 1;
}
if(num==2)
{
if(s->top1==max)
{
printf("此栈为空\n");
return 0;
}
*e=s->data[s->top2++];
return 1;
}
}
int main()
{
Dstack s;
initstack(&s);
add(&s,3,2);
trav(&s);
printf("请输入一个入到栈二的元素");
push(&s,5,2);
trav(&s);
printf("有一个元素出栈一\n");
int p;
pop(&s,&p,1);
printf("出栈一的元素为%d",p);
return 0;
}
(5)栈的链式存储结构:
也称栈链,如果栈的使用过程元素个数变化不可预测,有时小有时大那么最好是栈链,如果栈的变化在可变的范围内,那么顺序栈更好。
#include<stdio.h>
#include<malloc.h>
#define Etype int
typedef struct stacknode{//这是表示结点的结构体
Etype data;
struct stacknode *next;
}Stacknode,*liststack;
typedef struct {//这是表示栈的结构体
liststack top;
int count;
}list;
int push(list *S,Etype e)
{
liststack p=(liststack)malloc(sizeof(Stacknode));
p->data=e;
p->next=S->top;
S->top=p;
S->count++;
}
int pop(list *S,Etype *e)
{
if(S->top==NULL)
{
printf("链栈为空:\n");
return 0;
}
*e=S->top->data;
liststack q;
q=S->top;
S->top=S->top->next;
free(q);
S->count--;
return 1;
}
int main()
{
list p;
p.top=NULL;
printf("请输入你想添加的元素\n");
int m;
scanf("%d",&m);
push(&p,m);
printf("将有一个元素要出栈:");
pop(&p,&m);
printf("此元素是:%d",m);
return 0;
}
栈链就是让头指针和栈顶指针合并,也就是说没有了头结点,栈顶指针直接指第一个数据结点,类似于头插法。。。
(5)栈的应用四则运算表达式求值
中缀表达式(波兰式):首先前中后缀表达式,一般正常写的(3+4)5-6这种式子称为中缀表达式
前缀表达式:运算符位于操作数之前。
后缀表达式(逆波兰式):运算符位于操作数之前。
中缀表达式转后缀表达式规则:从左到右依次遍历每个数字和字符,若是数字就输出,成为后最表达式的一部分,若是符号,判断与栈顶元素的优先级,是右括号或优先级不高于栈顶符号则栈顶元素依次出栈成为后缀表达式的一部分,再将当前符号进栈,直到最终后缀表达式输出完毕。
计算机如何运算后缀表达式:先1,2,3进栈,然后+代表栈顶两元素出栈(2和3)相加,和为6再进栈,然后4进栈,遇到再然后栈顶两元素(4和6)出栈相乘再进栈,遇到+,栈顶两元素出栈,相加再进栈,然后5进栈,再然后栈顶两元素相减。。。。
中缀转前缀:
这里与中缀转后缀没啥不同,就是它是从右至左扫描的,还有就是他这里如果扫描元素优先级只要不低于栈顶元素,栈里元素不会出来,还有就是,得到最终的结果倒过来就是最终求解的表达式要颠倒过来结果就是:-+1*+2345
2.队列:
(1)定义:允许在一端进行插入另一端进行删除的操作线性表。
ADT 队列(queue)
Data
元素具有相同的数据类型,相邻元素具有前驱后继的关系
Operator
InitQueue(*Q):初始化操作,建立一个空队列
DestroyQueue(*Q)若队列存在就销毁它
ClearQueue(*Q)将队列清空
QueueEmpty(Q)判断队列是否为空
GetHead(Q,*e)若队列存在且非空,用e返回队头元素
EnQueue(*Q,e)若·队列Q存在插入新元素到队尾成为队尾元素
DeQueue(*Q,*e)删除队头元素,并用e返回其值
QueueLength(Q)返回队列Q的元素个数
endADT
(2)队列顺序存储的不足:
顺序存储说白了就是数组存储,由于队列有头尾的指针(其中指针指向第一个有元素的下标,尾指向第一个空的下标),而且头是用来删除元素的,尾是用来添加元素的,那么如果删除元素,那么数组前边的几个下标的空间就空出来了,如果添加元素到数组最后一个,由于尾指针已经再往后没有空的下标指向了,所以再添加元素的时候就会溢满,然而,数组前面的空间还是空的。
解决方法:
循环链表:
把对列头尾相连的顺序存储结构称为循环队列。
那么有一点不足是,循环链表该如何判满,因为,链表尾空时,头尾指针指向相同,链表满的时候,头尾指针还是指向相同。
1)第一种就是设置标志变量当头尾指针相等而且flag=0时队列为空,当头尾指针相等,flag==1时队列满了
2)第二种是空一个下标,就是尾指针指向的是一个空的下标就算满如下两种情况:
那么这种如何判满呢(设数组长度为n)
这里不确定头尾指针谁在前谁在后用(rear+1)%n和front是否相等。相等就说明满了,没相等就说明没满
那么队列长度如何算,也有上图两种情况一种是rear-front另一种是rear+n-front.
为了实现统一用(rear+n-front)%n就可以同时表示两种情况
下边是例子:
#include<stdio.h>
#include<bits/stdc++.h>
#define Etype int
#define max 5
typedef struct{
Etype data[max];
int front;
int rear;
}Dqueue;
int initQueue(Dqueue *S)
{
memset(S->data,0,sizeof(S->data));
S->front=0;
S->rear=0;
return 1;
}
int len(Dqueue *S)
{
return (S->rear-S->front+max)%max;
}
int Enqueue(Dqueue *S,Etype e)
{
if((S->rear+1)%max==S->front)
{
printf("队列已满!\n");
return 0;
}
S->data[S->rear]=e;
S->rear=(S->rear+1)%max;
return 1 ;
}
int Dlqueue(Dqueue *S,Etype *e)
{
if(S->front==S->rear)
{
printf("队列为空!\n");
return 0;
}
*e=S->data[S->front];
S->front=(S->front+1)%max;
}
int main()
{
Dqueue s;
initQueue(&s);
printf("请添加一个队列元素:");
int m;
scanf("%d",&m);
Enqueue(&s,m);
printf("此时队列长度是:%d\n",len(&s));
printf("此时一个元素出队列........\n");
int n;
Dlqueue(&s,&n);
printf("这个元素是:%d",n);
printf("此时队列的长度为:%d",len(&s));
}
可以看出顺序存储若不是循环队列,时间性能是不高的,但是,循环队列又有可能溢出的问题。
(3)队列的链式存储结构:
注意,这里不同与链式的是,栈链没有头结点,栈链的栈顶指针是直接指向第一个元素。队列的链式存储结构的front是指向头结点的,头结点是指向第一个元素的,开始的时候队列链的front和rear都是指向头节点的,删除操作只能在头结点进行,添加必须在队尾,这里不同与队列的顺序存储结构是,队列的顺存储结构的循环队列的rear指向的是没有元素的位置下标,但是队列链的rear指向最后一个有元素的结点。
还有一个注意点,当删除某一个结点的时候,如果队列链里就一个元素,那么这个时候就需要把rear指向front所指向的头指针。即rear=front.
#include<stdio.h>
#include<bits/stdc++.h>
#include<malloc.h>
#define Etype int
typedef struct Node{
Etype data;
struct Node *next;
}*node;
typedef struct {
node front;
node rear;
}listn;
int initlist(listn *S)
{
node s;
s=(node)malloc(sizeof(struct Node));
if(!s)
{
printf("初始化失败!");
exit(0);
}
s->next=NULL;
S->front=s;
S->rear=s;
return 0;
}
int enqueue(listn *s,Etype e)
{
node p=(node)malloc(sizeof(struct Node));
if(!p)
{
exit(0);
}
p->data=e;
p->next=NULL;
s->rear->next=p;
s->rear=p;
return 1;
}
int Dlqueue(listn *s,Etype *e)
{
if(s->front==s->rear)
{
printf("这是一个空队列!\n");
return 0;
}
*e=s->front->next->data;
node p;
p=s->front->next;
s->front=p->next;
if(s->rear==p)
s->rear=s->front;
free(p);
return 1;
}
int trav(listn *s)
{
int cnt=1;
node p=s->front->next;
while(p!=NULL)
{
printf("输出第%d个元素:%d\n",cnt,p->data);
cnt++;
p=p->next;
}
}
int main()
{
listn p;
initlist(&p);
printf("将有一个链队元素被删除\n");
int h;
Dlqueue(&p,&h);
printf("请输入你想添加的第一个元素:\n");
int m;
scanf("%d",&m);
enqueue(&p,m);
printf("请输入你想添加的第二个元素:\n");
scanf("%d",&m);
enqueue(&p,m);
trav(&p);
}
最后还是当队列元素可以确定的时候,建议用循环队列,但是不确定时用队列链。。