栈和队列
一栈
1.栈的顺序存储结构
特点:先进后出
栈是一种只能在一端进行插入或删除操作的线性表。
表中允许插入删除操作的一端为栈顶(top),表的另一端为栈底(bottom),
1 结构体的定义
cpp
#include <stdio.h>
typedef int ElemType;
#define MAX_SIZE 100
typedef struct {
int data[MAX_SIZE]; // 用于存储栈中的元素
int top; // 栈顶指针,指向栈顶元素的索引
} SqStack;
2 初始化栈
栈顶指针赋值为-1
cpp
void InitStack(SqStack *&s) {
s = (SqStack *) malloc(sizeof(SqStack));//申请一个顺序栈空间
s->top = -1;//栈顶指针赋值为-1
}
3 销毁栈
释放节点空间
cpp
void DestroyStack(SqStack *&s) {
free(s);
}
4 判断栈是否为空
指针为-1
cpp
bool StackEmpty(SqStack *&s) {
return (s->top == -1);
}
5 进栈
先判断栈是否为满
注意先将指针++,再对栈顶元素进行赋值
cpp
bool PushStack(SqStack *&s, ElemType e) {
if (s->top == MAX_SIZE - 1) {//栈满的情况
return false;
}
s->top++;
s->data[s->top] = e;//先加再赋值(s-data[++s->top])
return true;
}
6 出栈
先对栈元素赋值,再将栈顶指针--
cpp
bool Pop(SqStack *&s, ElemType &e) {
if (s->top == -1) {//栈空的情况
return false;
}
e=s->data[s->top];
s->top--;
return true;
}
7 获取栈顶元素
cpp
bool GetTop(SqStack *s,ElemType &e){
if (s->top==-1)
return false;
e=s->data[s->top];
return true;
}
补充:共享栈
- 顺序栈采用一个数组存放栈中的元素。如果需要用到两个类型相同的栈,这时若为它们各自开辟一个数组空间,极有可能出现这样的情况:第一个栈已经满了,再进栈就溢出了,而另一个栈还有许多的空间。
- 共享栈是为了更有效地利用存储空间,两个栈的空间相互调节,只有在整个存储空间被占满时才会发生上溢。其存储数据的时间复杂度均为O(1),所以对存取效率没有什么影响。
- 栈空条件:栈0空为top==-1;栈1空为top1==MaxSize.
- 栈满条件:top==top1-1
- 元素进栈操作:进栈0操作为top0++;data[top0]=x;进栈1操作为top1--;data[top1]=x
- 元素出栈操作:出栈0操作为x=data[top0];top0--;出栈1操作为x=data[top1];top1++
- 在上述操作中,data数组表示共享栈的存储空间,top0和top1分别为两个栈的栈顶指针,这样该共享栈通过data,top0和top1来标识,也可以将他们设计为一个结构体类型。
2.栈的链式存储结构
栈中的数据元素的逻辑关系呈线性关系,所以栈可以像线性表一样采用链式存储结构
链栈通过单链表实现
不存在栈满上溢的情况,可以一直添加。
cpp
typedef struct linknode
{
//定义链表节点数据
ElemType data;
//定义指针域,单链表的后继指针域
struct linknode *next;
}LiStack;
栈的四要素:
栈空的条件:
s->next==NULL
cpp
bool StackEmpty(LiStack *s)
{
return(s->next == NULL);
}
栈满的条件:
链栈不存在满的情况
进栈的条件
将带新元素的节点插入
cpp
//定义链表节点指针
LiStack *p;
//为新节点申请空间
p = (LiStack *)malloc(sizeof(LiStack));
//为新节点的数据区赋值
p -> data = e;
//头插法
//将新节点后继指针指向下一个节点 ,下一个节点插入前存放在头结点的后继指针
p -> next = s ->next;
//然后将头结点的后继指针指向新节点
s->next = p;
出栈的条件:
将栈顶节点删除
cpp
//传入要出栈的链栈 , 传入要出栈的指针元素
bool Pop(LiStack *&s , ElemType &e)
{
//用指针指向要出栈的元素,就是头结点的后继节点
LiStack *p;
//如果s->next 为空,则表明为空栈 ,无法出栈元素
if(s->next == NULL)
{
return false;
}
//如果经过上面条件的验证,s->next不为空,则接着往下走
//先定位要出栈的元素
p = s->next; //头结点的后继节点
e = p->data; //栈顶数据传出
//开始删除头结点的后继节点 p,也就是头结点指向要删除节点的下一个节点
s->next = p->next;
//然后释放 p 空间
free(p);
return true;
}
二 队列
特点:先进先出
队列只允许在一端进行插入另一端进行删除,允许插入的时队尾,允许删除的时队头
插入元素称为入队,删除元素称为出队。
1 顺序队列
-
用一组地址连续的存储单元,以此存放从队头到队尾的数据元素,称为顺序队列。
-
需要附设两个指针,一个队头指针(front)一个队尾指针(rear),分别指向队头队尾。
-
初始时front和rear均为-1
-
元素进队rear加1
-
front指向队首元素的前一位置
-
队满条件:rear==MaxSize-1
-
队空条件:front==rear
-
进队操作:元素进队rear++,data[rear]=e
-
出队操作:元素出队front++,e=data[front]
顺序队中实现队列的基本运算算法
结构体声明
cpp
typedef struct {
ElemType data[MaxSize];//存放队中元素
int front,rear;//队头和队尾指针
}SqQueue;
1 初始化
构造一个空队列q,将front和rear指针均设置为初始状态-1
cpp
void InitQueue(SqQueue *&q){
q=(sqQueue *)malloc(sizeof(SqQueue));
q->front=q->rear=-1;
}
2 入队
在队不满的情况下,先将队尾指针rear++,再将元素添加到该位置
cpp
bool enQueue(SqQueue *&q, ElemType e) {
if (q->rear == MaxSize - 1)
return false;
q->rear++;
q->data[q->rear] = e;
return true;
}
3 出队
在队不空的情况下,将队首元素front++,再将该位置的元素赋值给e
cpp
bool deQueue(SqQueue *&q, ElemType &e) {
if (q->front == q->rear)
return false;
q->front++;
e = q->data[q->front];
return true;
}
2 环形队列:
这是因为采用了rear=MaxSize-1作为队满条件的缘故,当出现这种情况可能会出现还有空间没有填满的情况。这种情况称为假溢出。
解决方案:环形队列
实际上地址一定是连续的不可能是环形的,这里是通过逻辑方式实现环形队列
- rear=(rear+1)%MaxSize
- front=(front+1)%MaxSize
- 队空条件:rear==front
- 队满条件:(rear+1)%MaxSize==front
- 进队操作:rear=(rear+1)%MaxSize,并且将e放在rear处
- 出队操作:front=(front+1)%MaxSize,将front位置e取出
- 这种情况下会比原先少放一个元素
相关计算:
3 链队
- 采用链表存储的称为链队。
相关操作:
单链表中数据节点类型DataNode声明如下
cpp
typedef struct qnode{
ElemType data;
struct qnode *next;
}DataNode;
链队节点类型LinkQuNode声明如下
cpp
typedef struct {
DataNode *front;
DataNode *rear;
}LinkQuNode;
链队四要素:
- 链队为空的判断条件:front=rear=NULL
- 链队为满的判断条件:不考虑
- 链队插入的判断条件:将含e的单链表节点插入至链表结尾
- 链队删除的判断条件:将单链表的首元节点删除
代码实现;
cpp
#include <stdio.h>
#include <cstdlib>
typedef int ElemType;
typedef struct qnode {
ElemType data;//存放元素
struct qnode *next;//下一个节点指针
} DataNode;//数据节点的类型
typedef struct {
DataNode *front;
DataNode *rear;
} LinkQuNode;
初始化:
cpp
void InitQueue(LinkQuNode *&q) {
q = (LinkQuNode *) malloc(sizeof(LinkQuNode));
q->front = q->rear = NULL;
}
判断是否为空:
cpp
bool QueueEmpty(LinkQuNode *q) {
return (q->rear == NULL);
}
进队列
cpp
bool enQueue(LinkQuNode *&q, ElemType e) {
DataNode *p;
p = (DataNode *) malloc(sizeof(DataNode));
p->data = e;
p->next = NULL;
if (q->rear == NULL)
q->front = q->rear = p;
else {
q->rear->next = p;
q->rear = p;
}
return true;
}
出队列
cpp
bool deQueue(LinkQuNode *&q, ElemType &e) {
DataNode *t;
if (q->rear == NULL)
return false;
t = q->front;
if (q->front == q->rear)
q->front = q->rear = NULL;
else
q->front = q->front->next;
e = t->data;
free(t);
return true;
}
4 双端队列
所谓双端队列是指两端都可以进行进队和出队的操作的队列,将队列的两端分别成为前端和后端,两端都可以进队和出队。