DataStructure_4.Stack & Queue

栈与队列详解
本文详细介绍了栈和队列这两种基本的数据结构。包括它们的定义、特性、应用场景以及顺序存储和链式存储的不同实现方式。重点讲解了栈的后进先出(LIFO)特性和队列的先进先出(FIFO)特性。

4.1 栈的定义

4.1.1 限定仅在表层进行插入和删除操作的线性表。允许插入和删除的的一端称为栈顶,另一端称为栈底,不含任何数据元素的栈称为空栈。栈可以形象地表述为后进先出(Last In First Out)的线性表,简称LIFO结构。

需要明白的是,栈结构只是限定了线性表插入和删除的位置必须在栈顶,但没有对元素进出的时间做限制,只需要保证是栈顶元素出栈就行。

例如:将a,b,c依次压入栈,可以先把a压进,然后a弹出,再压b,然后弹出b。最后压c,再弹c。这样出栈次序为abc。所以说明,不一定最先进栈的元素只能最后出栈。

4.1.2栈的结构定义

typedef int SElemType;

typedef struct{

SElemType data[MaxSize];

int top;//由于是依托数组,所以顺序存储结构的栈顶指针类型为整型,存放数组下标,思路同静态链表

}SqStack

4.2 栈的顺序存储结构

4.2.1 当栈存在一个元素时,top中存储的下标为0,而空栈时top中存储的应为-1,而且top中存储的下标必须小于存储站的长度StackSize

4.2.2 push操作-时间复杂度O(1)

Status Push(SqStack *S, Selemtype e){

if(s->top==MaxSize-1){//栈满的情况

return ERROR;

}

S->top++ //栈顶指针增加1

S->data[S->top]=e//将要入栈的元素赋值给栈顶空间

return OK;

}

4.2.3 pop操作-时间复杂度O(1)

Status Pop(SqStack* S, SElemType *e){

if(S->top==-1) return ERROR;

*e=S->data[S->top];//将要删除的栈顶元素赋值给e(用作弹出)

S->top--; //栈顶指针减1

return OK;

}

4.2.4两栈共享空间

栈的顺序存储结构相对与线性表的顺序存储结构优势在于不需要再删除或插入时移动元素,但自身缺陷在于数组大小必须事先确定,如果不确定那么到时候不够的话会很麻烦。这是针对只有一个栈的情况;当有两个相同的栈时,我们完全可以用一个数组来存储这两个栈。形象的来说就是两个栈分别以一个数组的两端点为栈底,如果增加元素就都向数组中间延伸。只要栈1和栈2的栈顶指针不相遇就能在一个数组中一直使用这两个栈。

易得:栈1为空即为top1==-1,栈2为空即为top2==n,当两指针见面而不重叠时(top1+1==top2)即为栈满

typedef struct{

SElemTypedata[MaxSize];

int top1;

int top2;

}SqDoubleStack;

//判断栈号参数stackNumber

Status Push(SqDoubleStack *S, SElemType e, int stackNumber){

if(S->top1+1==S->top2)//满栈了

return ERROR;

if(stackNumber==1) S->data[++S->top1]=e;//top1+1后给数组元素赋值

else if(stackNumber==2) S->data[--S->top2]=e;//top2-1后给数组元素赋值

return OK;

}//由于之前已经判断了是否满栈的情况,所以后面的top1+1top2-1不用担心溢出

注意:两栈共享空间在数据为反比例关系的时候会很方便,即一方增长另一方减少。

Status Pop(SqDoubleStack *S, SElemType e, int stackNumber){

if(stackNumber==1){

if(S->top1==-1) return ERROR;//1已经空了,溢出

*e=S->data[S->top1--];//将栈1的栈顶元素出栈

}

else if(stackNumber==2){

if(S->top1==MaxSize) return ERROR;//2已经空了,溢出

*e=S->data[S->top2++];//将栈2的栈顶元素出栈

}

return OK;

}

4.3 栈的链式存储结构

4.3.1

顺序栈适用于元素变化可控情况,如果元素变化无常,时大时小(说实话这么写有点绕,所以只要明白链栈不会爆就是了),就使用链栈。

把栈顶放在单链表的头部,同时舍去头结点。

链栈基本不存在栈满的情况,满了说明内存爆了那就当机了。对于空栈,头指针指向空换算成链栈的情况就是top=NULL

typedef struct StackNode{

SElmType data;

struct StackNode *next;

}StackNode,*LinkStackPtr;

  

typedef struct LinkStack{

LinkStackPtr top;

int count;

}LinkStack;

链栈操作绝大部分都和单链表类似,只是在插入和删除上特殊一些。

4.3.2 进栈操作-时间复杂度O(1)

Status Push(LinkStack *Sk, SElemType e){

LinkStackPtr s=(LinkStackPtr) malloc(sizeof(StackNode));

s->data=e;//把要插入的元素值赋给数据域

s->next=Sk->top;//把当前的栈顶元素赋值给新结点的直接后继

Sk->top=s;//将新的结点s赋值给栈顶指针

Sk->count++;

return OK;

}

4.3.3出栈操作-时间复杂度O(1)

Status Pop(LinkStack *Sk, SElemType e){

LinkStackPtr p;

if(StackEmpty(*Sk)) return ERROR;

*e=Sk->top->data;

p=Sk->top;//将栈顶结点赋值给p

Sk->top=Sk->top->next;//使得栈顶指针下移一位,指向后一结点

free(p);//释放结点p

Sk->count--;

return OK;

}

4.4 队列的定义

4.4.1 队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表,与栈不同的是遵从先进先出(First In First Out),FIFO结构。允许插入的一端称为队尾,允许删除的一端称为队头(联想现实情况排队,后到的排在队尾)典型的就是键盘输入到鼠标屏幕上,先按下的键肯定先显示出来,这就是使用队列结构。

4.4.2 由于顺序存储结构在队列的出队实现中既耗费时间(O(n))还容易出现假溢出(队头还有空间结果队尾满了进不去了)所以将队列的头尾连接起来,称为循环队列。

//循环队列的顺序存储结构

typedef int QElemType;

typedef struct{

QElemType data[MaxSize];

int front;//队头指针

int rear;//队尾指针,若队列不空,指向队尾元素的下一位置

}SqQueue;

//初始化

Status InitQueue(SqQueue *Q){

Q->front=0;

Q->rear=0;

return OK;

}

//求队列长度

int QueueLength(SqQueue Q){

return (Q.rear-Q.front+MaxSize)%MaxSize;//通用队列长度计算公式p116

}

//入列

Status EnQueue(SqQueue *QQElemType 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;

}

//出列

Status EnQueue(SqQueue *QQElemType e){

if((Q->front==Q->rear) return ERROR;//判断队列空

*e=Q->data[Q->front];//将队头元素赋值给e

Q->front=(Q->front+1)%MaxSize;//front指针向后移动一位置,若到最后则转为数组头部

return OK;

}

4.4.3 链式存储结构-链队列

队头指针指向链队列的头结点,队尾指针指向终端结点,空队列时队头尾指针都指向头结点

typedef int QElemType;

typedef struct QNode{

QElmType data;

struct QNode *next;

}QNode,*QueuePtr;

  

typedef struct{

QueuePtr front,rear;

}LinkQueue;

//入列

Status EnQueue(LinkQueue *QQElemType e){

QueuePtr s=(QueuePtr) malloc(sizeof(QNode));

if(!s) exit(OVERFLOW);//分配失败

s->data=e;//要入列的元素e赋值给队尾

s->next=NULL;

Q->rear->next=s;//新结点s赋值给原队尾结点的后继

Q->rear=s;//设置s为新的队尾结点

return OK;

}

//出列,就是头结点的后继结点出列,若除头结点只剩一个结点,则需将rear指向头结点

Status DeQueue(LinkQueue *QQElemType *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;//如果队头是队尾,则删除后将rear指向头结点

free(p);

return OK;

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值