栈与队列
一、栈
(1)栈的定义
栈:限定仅在表尾进行插入与删除操作的线性表
栈顶:允许插入和删除的一端(表尾)
栈底:栈顶的另一端(表头)
空栈:不含任何数据元素的栈
栈又称为后进先出的线性表,简称LIFO结构
进栈(压栈)(入栈)(push):栈的插入操作
出栈(弹栈)(pop):栈的删除操作
(2)栈的抽象数据类型
ADT 栈(stack)
Data
同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系
Operation
InitStack(*s);//初始化操作,建立一个空栈s
DestroyStack(*s);//若栈存在,则销毁它
ClearStack(*s);//将栈清空
StackEmpty(s);//若栈为空,返回true,否则返回false
GetTop(s,*e);//若栈存在且非空,用e返回s的栈顶元素
Push(*s,e);//若栈存在,插入新元素e到栈中并成为栈顶元素
Pop(*s,*e);//删除栈S中栈顶元素,并用e返回其值
StackLength(s);//返回栈s的元素个数
endADT
(3)栈的顺序存储结构及实现
a.栈的顺序存储结构
top变量:指示栈顶元素在数组中位置
StackSize:存储栈的长度
当栈中存在一个元素是,top为0,空栈的判定条件为top等于-1
栈的结构定义
typedef int SElemType;//SElemType类型根据实际情况而定,这里假设为int
typedef struct
{
SElemType data[MAXSIZE];
int top;//用于栈顶指针
}SqStack;
b.栈的顺序存储结构——进栈操作
//插入元素e作为新的栈顶元素
Status Push(SqStack *s,SElemType e)
{
if(s->top==MAXSIZE-1)//栈满
{
return ERROR;
}
s->top++;
s->data[s->top]=e;//将新插入元素赋值给栈顶空间
return OK;
}
c.栈的顺序存储结构——出栈操作
//若栈不为空,则删除s的栈顶元素,并用e返回其值,并返回OK,否则返回ERROR
Status Pop(SqStack *s,SElemType *e)
{
if(s->top==-1)
{
return ERROR;
}
*e=s->data[s->top];//将要删除的栈顶元素赋值给e
s->top--;//栈顶指针减一
return OK;
}
bc时间复杂度均为O(1)
(4)两栈共享空间
a.用一个数组来存储两个栈
数组有两个端点,两个栈有两个栈底,让一个栈的栈底为数组的始端,即下标为0处,另一个栈为数组的末端,即下标为数组长度n-1处,若两个栈增加元素,就是两端点向中间延伸。top1和top2是栈1和栈2的栈顶指针。
当栈1为空栈时,top1为-1,当top2等于n时栈2为空。当两个栈见面时,即两个指针之间相差为1时,top1+1==top2为栈满
两栈共享空间结构代码
//两栈共享空间结构
typedef struct
{
SElemType data[MAXSIZE];
int top1;//栈1栈顶指针
int top2;//栈2栈顶指针
}SqDoubleStack;
b.两栈共享空间的push方法
除了要插入元素值参数外,还需要有一个判断是栈1还是栈2的栈号参数stackNumber
//插入新元素e为新的栈顶元素
Status Push(SqDoubleStack *s,SElemType e,int stackNumber)
{
if(s->top1+1==s->top2)//栈已满,不能再push新的元素
{
retrun ERROR;
}
if(stackNUmber==1)//栈1有元素进栈
{
s->data[++s->top1]=e;//若栈1则先top+1后给数组元素赋值
}
else if(stackNumber==2)//栈2有元素进栈
{
s->data[--s->top2]=e;//若栈2则先top2-1后给数组元素赋值
}
return OK;
}
c.两栈共享空间的pop方法
参数为判断栈1栈2的参数stackNumber
//若栈不为空,则删除s的栈顶元素,用e返回其值,并返回OK;否则返回ERROR
Status Pop(SqDoubleStack *s,SElemType *e,int satckNUmber)
{
if(satckNUmber==1)
{
if(s->top1==1)
{
return ERROR;//说明栈1已经是空栈,溢出
}
*e=s->data[s->top1--1];//将栈1的栈顶元素出栈
}
else if(stackNumber=2)
{
if(s->top2==MAXSIZE)
{
return ERROR;//说明栈2已经是空栈,溢出
}
*e=s->data[s->top2++];//将栈2的栈顶元素出栈
}
return OK;
}
(5)栈的链式存出结构及实现
a.栈的链式存储结构
链栈:栈的链式存储结构
对于空栈,链表原定义是头指针指向空,那么链栈的空其实就是top=NULL
链栈的结构代码
typedef struct StackNode
{
SelemType data;
struct StackNode *next;
}StackNode,*LinkStackPtr;
typedef struct LinkStack
{
LinkStackPtr top;
int count;
}LinkStack;
b.栈的结构存储操作——进栈操作
假设元素值为e的新结点是s,top为栈顶指针
//插入元素e为新的栈顶元素
Status Push(LinkStack *s,SElemType e)
{
LinkStackPtr s=(LinkSatckPtr)malloc(sizeof(StackNode));
s->data=e;
s->next=s->top;//把当前的栈顶元素赋值给新结点的直接后继
s->top=s;//将新的结点s赋值给栈顶指针
s->count++;
return OK;
}
c.栈的链式存储结构——出栈操作
假设变量p用来存储要删除的栈顶结点,将栈顶指针下移一位,最后释放p即可
//若栈不为空,则删除s的栈顶元素,用e返回其值,并返回OK,否则返回ERROR
Status Pop(LinkStack *s,SElemType *e)
{
LinkStackPtr p;
if(StackEmpty(*s))
{
return ERROR;
}
*e=s->top->data;
p=s->top;//将栈顶结点赋值给p
s->top=s->top->next;//使得栈顶指针下移一位,指向后一结点
free(p);//释放结点p
s->count--;
return OK;
}
(6)栈的应用——递归
a.斐波那契数列实现
int Fbi(int i)
{
if(i<2)
{
return i==0?0:1;
}
return Fbi(i-1)+Fbi(i-2);//这里Fbi就是函数自己,在调用自己
}
int main()
{
int i;
for(int i=0;i<40;i++)
{
printf("%d",Fbi(i));
}
return 0;
}
b.递归定义
递归函数:把一个直接调用自己或通过一系列的调用语句间接地调用自己的函数
每个递归定义必须至少有一个条件,满足时递归不再进行,即不再引用自身而是返回值退出
(7)栈的应用——四则运算表达式求值
a.后缀(逆波兰)表达法定义
后缀的原因:所有的符号都是在要运算数字的后面出现
b.后缀表达式的计算结果
中缀表达式:9+(3-1)*3+10/2
后缀表达式:9 3 1 - 3 * + 10 2 / +
规则:从左到右遍历表达式的每个数字和符号,遇到数字就进栈,遇到是符号就将处于栈顶两个数字出栈,进行运算,运算结果出栈,一直到最终获得结果。
- 初始化一个空栈,此栈用于对要运算的数字进出使用
- 后缀表达式中前三个均为数字,将9、3、1进栈
- 第四个为-,因此将栈中1出栈作为减数,3出栈作为被减数,运算3-1得2再将2进栈
- 第五个为3,将3进栈
- 第六个为*,将栈中3与2出栈相乘得6进栈
- 第七个为+,将栈中9与6出栈相加得15进栈
- 第八九个为10、2,将10、2进栈
- 第十个为/,将2出栈作为除数,将10出栈作为被除数,运算10/2得5进栈
- 最后一个为+,将5与15出栈相加运算得20进栈
- 结果为20出栈,栈变为空
c.中缀表达式转为后缀表达式
规则:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分,若是符号,则判断其与栈顶符号的优先级,是右括号或优先级高于栈顶符号(乘除优先加减)则栈顶元素依次出栈并输出,并将当前符号进栈,一直到最终输出后缀表达式为止
中缀表达式:9+(3-1)*3+10/2
- 初始化一空栈,用于对符号进行进出栈使用
- 第一个字符为9直接输出
- 第二个字符为+进栈
- 第3个符号(为左括号,还未配对,进栈
- 第四个字符为3直接输出
- 第四个符号为-进栈
- 第五个符号为1直接输出
- 第六个符号)为右括号,需去匹配之前的(,所以栈顶依次输出直到遇到(出栈为止。此时(上方只有-,因此输出-,总的输出式为9 3 1 -
- 第七个符号为 * ,又因为此时栈顶符号为+,*优先级比+高,不输出, * 进栈
- 第八个字符为3直接输出,总表达式为9 3 1 - 3
- 第九个字符为+,此时栈顶字符为 * , * 的优先级比+高,所以将栈中元素出栈并输出(没有比+更低的优先级,所以全部出栈),总输出式为9 3 1 - 3 * +,然后当前符号+进栈
- 第十个字符为10,直接输出
- 第十一个字符为/,进栈
- 最后一个字符为2,直接输出,总表达式为9 3 1 - 3 * + 10 2
- 因为已经到了最后,所以将栈中符号全部出栈并输出,最终输出的后缀表达式为9 3 1 - 3 * + 10 2 / +
二、队列
(1)队列的定义
队列:只是允许在一端进行插入操作,在另一端进行删除操作的线性表。是一种先进先出的线性表,简称FIFO。
队尾:允许插入的一端
队头:允许删除的一端
(2)队列的抽象数据类型
ADT 队列(Queue)
Data
同线性表。元素具有相同的类型,相邻元素具有前驱后继关系
Operation
InitQueue(*Q)://初始化操作,建立一个空队列Q
DestroyQueue(*Q);//若队列Q存在存在,则销毁它
CleraQueue(*Q);//将队列Q清空
QueueEmpty(Q);//若队列Q为空,返回true,否则返回法拉瑟
GetHead(Q,*e);//若队列Q存在且非空,用饿返回队列Q的队头元素
EnQueue(*Q,e);//若队列Q存在,插入新元素e到队列Q中并成为队尾元素
DeQueue(*Q,*e);//删除队列Q中队头元素,并用e返回其值
QueueLength(Q);//返回队列Q的元素个数
endADT
(3)循环队列
- 为了避免当只有一个元素时,队头和队尾重合使处理变得麻烦,因此引入两个指针,front指针指向队头元素,rear指针指向队尾元素的下一个位置,当front等于raer时,队列为空队列
- 假溢出
a.循环队列的定义
循环队列:队列的头尾相接的顺序存储结构
可以将rear改为指向下标为0的位置,这样子就不会造成指针指向不明的问题了
判断是否为空队列
- 设置一个标志变量flag,当frontrear,且flag=0时队列为空,当frontrear且flag=1时队列为满
- 当队列为空时,条件为front==rear,当队列为满时,修改条件,保留一个元素空间,当队列满时,数组中还有一个空闲单元。
第二种方法的讨论
由于rear可能比front大,也可能比front小,所以尽管相差一个位置时就是满的情况下,也可能是相差整整一圈。
记队列的最大尺寸为QueueSIze
队列满的条件:(rear+1)%QueueSize==front
计算队列长度:(rear-front+QueueSize)%QueueSize
循环队列的顺序存储结构
typedef int QElemType;//QElemType类型根据实际情况而定,这里假设为int
//循环队列的顺序存储结构
typedef struct
{
QElemType data[MAXSIZE];
int front;//头指针
int rear;//尾指针,若队列不空,指向队列尾元素的下一个位置
}SqQueue;
循环队列的初始化代码
//初始化一个空队列
status InitQueue(SqQueue *Q)
{
Q->front=0;
Q->rear=0;
return OK;
}
循环队列求队列长度
//返回Q的元素个数,就是队列的当前长度
int QueueLength(SqQueue Q)
{
return (q.rear-Q.front+MAXSIZE)%MAXSIZE;
}
循环队列的入队列操作代码
//若队列未满,则插入元素e为Q新的队尾元素
status EnQueue(SqQueue *Q,QElemType e)
{
if((Q->rear+1)%MAXSIZE==Q->front)//队列满的判断
{
return ERROR;
}
Q->data[Q->rear]=e;//将元素赋值给队尾
Q->rear=(Q->rear+1)%MAXSIZE;//将rear指针后移一位置,若到最后则转给数组头部
return OK;
}
循环队列的出队操作
//若队列不空,则删除Q中队头元素,用e返回其值
status DeQueue(SqQueue *Q,QElemType *e)
{
if(Q->front==Q->raear)//队列空的判断
{
return ERROR;
}
*e=Q->data[Q->front];//将队头元素赋值给e
Q->front=(Q->front+1)%MAXSIZE://front指针向后移一位置,若到最后则转到数组头部
return OK;
}
(4)队列的链式存储结构及实现
- 头指针指向链队列的头结点,而队尾指针指向终端结点。
- 空队列时,front和rear都指向头结点
链队列的结构
typedef int QElemType;/QElemType类型根据实际情况而定,这里假设为int
typedef struct QNode//结点结构
{
QelemType data;
struct QNode *next;
}QNode,*QueuePtr;
typedef struct//队列的链表结构
{
QueuePtr front,rear;//队头、队尾指针
}LinkQueue;
a.队列的链式存储结构——入队操作
入队操作就是在链表尾部插入结点
//插入元素e为Q的新队尾元素
status EnQueue(LinkQueue *Q,QElemType e)
{
QueuePtr s=(QueuePtr)malloc(Sizeof(QNode));
if(!s)//存储失败
{
exit(OVERFLOW);
}
s->data=e;
s->next=NULL;
Q->rear->next=s;//把拥有元素e新结点s赋值给原队尾结点的后继
Q->rear.=s;//把当前的s设置为队尾结点,rear指向s
return OK;
}
b.队列的链式存储结构——出队操作
出队操作就是头结点的后继结点出队,将头结点的后继改为它后面的结点,若链表除头结点外只剩下一个元素时,则需将rear指向头结点
//若队列不空,删除Q的队头元素,用e返回其值,并返回OK,否则返回ERROR
status DeQueue(LinkQueue *Q,QElemType *e)
{
QueuePtr p;
if(Q->front==Q->rear)
{
return ERROR;
}
p=Q->front->next;//将欲删除的队头结点暂存给p
*e=p->data;//将欲删除的队头结点的值赋给e
Q->front->next=p->next;//将原队头结点后继给p->next赋值给头结点
if(Q->rear==p)//若队头是队尾,则删除后将rear指向头结点
{
Q->rear=Q->front;
}
free(p);
return OK;
}