栈
为什么要使用栈
实际上,可能当时没有“栈”的时候,需求的是一种LIFO(Last In First Out)的模式。这种模式有很多用途。
例如:编程中的函数调用
有如下程序
int main() {
a();
return 0;
}
void a() {
b();
}
void b() {
c();
}
void c() {
}
那么整个程序的函数活动时间可以表示为:
main() a() b() c()
- main()
|
+> - a()
. |
. +> - b()
. . |
. . +> - c()
. . . |
. . + <- return from c()
. . |
. + <- return from b()
. |
+ <- return from a()
|
- return from main()
作者:RednaxelaFX
链接:https://www.zhihu.com/question/34499262/answer/59415153
来源:知乎
函数调用需要的是一种嵌套关系——调用者的生命期总是要长于被调用者的生命期,并且后者在前者之内。这样,被调用者的局部信息所占空间的分配总是后于调用者的(后入),而其释放则总是先于调用者的(先出),需要用LIFO顺序实现。
又例如:四则运算表达式求值
有加减乘除,甚至还带有大中小括号的四则运算。人一眼可以看出运算的顺序,但机器又是要怎么实现呢?
(待补)
因此,有这种“后进先出”的模式需求,人们也就设计出“栈”这种数据结构啦
栈的使用
说明:栈的插入操作(Push),叫作进栈,也称压栈、入栈。栈的删除操作(Pop),叫作出栈,也有的叫作弹栈。
栈的顺序存储结构及实现
最重要的功能是push、pop、peek(返回栈顶的元素)、empty、search(看某个元素离栈顶多远距离)
顺序栈的结构定义
typedef int SElemType; //假定SElemType类型是int
typedef struct
{
SElemType data[MAXSIZE]; //定义数组长度
int top; //top指示栈顶元素在数组中的位置
}SqStack;
NOTE:当栈存在一个元素时,top等于0,因此通常把空栈的判定条件定为top等于-1
push
/* 插入元素e为新的栈顶元素 */
Status Push(SqStack *S, SElemType e)
{
if(S->top == MAXSIZE-1) //栈满
return ERROR;
S->top++; //栈顶指针增加一
S->data[S->top]=e; //将新插入元素赋值给栈顶空间
return OK;
}
pop
/* 若栈不空,则删除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;
}
栈的链式存储结构及实现
链栈结构定义
typedef struct StackNode
{
SElemType data;
Struct StackNode *next; //next指向StackNode结构体,并且next是StackNode结构体的一部分
}StackNode,*LinkStackPtr; //LinkStackPtr指向结构体StackNode
typedef struct LinkStack
{
LinkStackPtr top; //top作为LinkStackPtr结构指向结构体StackNode
int count; //计数
}LinkStack;
栈顶放在单链表的头部。另外有了栈顶在头部,单链表中比较常用的头结点也就失去了意义,通常对于链栈来说,是不需要头结点的
push
/* 插入元素e为新的栈顶元素 */
Status Push(LinkStack *S,SElemType e)
{
LinkStackPtr s=(LinkStackPtr)malloc(sizeof(StackNode))
s->data = e;
s->newxt = S->top; //把当前的栈顶元素赋值给新结点的直接后继
S->top=s; //将新的结点s赋值给栈顶指针
S->count++;
return OK;
}
pop
/* 若栈不空,则删除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;
}
队列
为什么要使用队列
同“栈”的设计类似,人们需要一种“先进先出(First In First Out)”的数据结构。
队列的顺序存储
由于建立一个数组存放数据时,若元素出队列,以数组的特性,后面的元素必须移动补空,否则会造成空间的浪费。但此时一个元素出队列,时间复杂度为O(n),为了提高队列数组的存储效率,于是出现的循环队列
循环队列
我们把队列这种头尾相接的顺序存储结构称为循环队列。
引入两个指针,front指针指向队头元素,rear指针指向队尾元素的下一个位置,当front等于rear时,队列为空。
队列为满的情况
由于如果“循环一圈”后,front==rear的情况会出现,此时我们分不清队列是空的还是满的,因此我们修改条件,保留一个元素空间,并认为该队列已满,如图所示
那么问题来了,front和rear的大小都不确定,机器又是如何确定队列是否已满以及队列的实际长度的呢?
通过公式(rear+1)%QueueSize == front
确定队列是否已满,其中QueueSize是队列的最大尺寸
通用的计算队列的长度公式为:(rear-front+QueueSize)%QueueSize
代码
循环队列的顺序存储结构
typedef int QElemType; //QElemType类型根据实际情况而定,这里假设为int
typedef struct
{
QElemType data[MAXSIZE];
int front; //头指针
int rear; //尾指针,若队列不空,指向队列尾元素的下一个位置
}SqQueue;
初始化代码
/*初始化一个空队列Q*/
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; //将元素e赋值给队尾
Q->rear=(Q->rear+1)%MAXSIZE; //rear指针向后移一位置
//若到最后则转向数组头部
return OK;
}
NOTE:但是顺序存储,若不是循环队列,算法的时间性能是不高的,但循环队列有面临着数组可能会溢出的问题,所以我们还需要研究一下不需要担心队列长度的链式存储结构
队列的链式存储结构
typedef int QElemType;
typedef struct QNode //结点结构
{
QElemType data;
struct QNode *next;
}QNode,*QueuePtr;
typedef struct //队列的链表结构
{
QueuePtr front,rear; //队头、队尾指针
}LinkQueue;
入队操作
/* 插入元素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;
}
出队操作
/* 若队列不空,删除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)
Q->rear=Q->front;
free(p);
return OK;
}
总结
栈是一种后进先出的数据结构,队列是一种先进先出的数据结构。
本文图文参考程杰《大话数据结构》