栈和队列
目录
一.栈
1.栈的定义
栈是一种只能在一端进行插入或删除操作的线性表。
栈顶:在表中进行插入和删除的一端。
栈底:表中的另一端就是栈底。固定的一端。
空栈:线性表中没有元素。
元素的插入被称为入栈和进栈。元素的删除被称为出栈和退栈。
栈的特点:
栈的主要特点就是" 先进后出 (First in last out)"(后进先出(last in first out))。即后进栈的元素先出栈。所以也叫做后进先出表。
栈的数学性质:
n 个不同元素进栈,出栈元素不同的排列个数为 。
2.栈的基本运算
栈的基本操作
InitStack(&s): 初始化栈,构造一个空栈
DestoryStack(&s): 销毁栈,释放给栈s分配的内存空间
StackEmpty(s): 判断栈是否为空,若栈为空,返回真,否则返回假
Push(&s): 进栈,将元素e插入栈s中作为栈顶元素
Pop(&s): 出栈,从栈s中删除栈顶元素,并将其赋值给e
GetTop(s): 取栈顶元素,返回当前的栈顶元素,并将其赋值给e。
3.栈的顺序存储以及基本运算的实现
顺序栈的特点:
栈空的条件:s->top ==-1;
栈满的条件:s->top == Maxsize-1 ; (data数组的最大下标)
元素进栈:先将指针 s->top++ ;之后将元素 e 放在栈顶指针上
元素出栈:先将元素取出 e=s->data[s->top] ;之后指针 s->top-- ;
顺序栈:
顺序栈的结构体:
#include <malloc.h>
#define ElemType int
#define Maxsize 50
typedef struct {
ElemType data[Maxsize];
int top;
}SqStack;
从结构体定义发现,我们就会发现,栈和线性表的结构体几乎一致,线性表中定义的是 length 表示线性表的长度,而栈中定义的是 top 表示的是栈顶元素的位置。当然我们也可以通过 top 来得到栈中的元素个数。
InitStack(&s): 初始化栈,构造一个空栈
void InitStack(SqStack*& s) {
s = (SqStack*)malloc(sizeof(SqStack));
s->top = -1;
}
DestoryStack(&s): 销毁栈,释放给栈s分配的内存空间
void DestoryStack(SqStack*& s) {
free(s);
}
StackEmpty(s): 判断栈是否为空,若栈为空,返回真,否则返回假
bool StackEmpty(SqStack* s) {
return (s->top == -1);
}
Push(&s): 进栈,将元素e插入栈s中作为栈顶元素
bool Push(SqStack*& s, ElemType e) {
if (s->top = Maxsize - 1) return false;
s->top++;
s->data[s->top] = e;
return true;
}
Pop(&s): 出栈,从栈s中删除栈顶元素,并将其赋值给e
bool Pop(SqStack*& s, ElemType& e) {
if (s->top = -1) return false;
e = s->data[s->top];
s->top--;
return true;
}
GetTop(s): 取栈顶元素,返回当前的栈顶元素,并将其赋值给e。
bool GetElem(SqStack* s, ElemType& e) {
if (s->top == -1) return false;
e = s->data[s->top];
return true;
}
4.共享栈
如果需要使用两个类型相同的栈,会出现着这种情况,一个栈的空间已经满了,但是另一个栈的空间几乎没有使用。所以需要将这两个栈的空间结合使用起来,从而有了共享栈。
共享栈就是利用栈中栈底元素不变的特性,两个栈共用一个一维数组空间,将两个栈中的栈底一个放在共享栈中的起始位置,一个放在共享栈中的终止位置,使两个栈顶向共享空间的中间延伸。
共享栈的4个特性:
- 栈空条件: 栈1空的条件是 top1 ==-1 ; 栈2空的条件是 top2==Maxsize
- 栈满条件: top1 = top2 -1;
- 元素 x 进栈的操作:栈1的进栈操作 top++;data[top1]=x 栈2的进栈操作 top--;data[top2]=x
- 元素 x 出栈的操作:栈1 的出栈操作 x=data[top1];top-- 栈2的出栈操作 x=data[top2]; top++
共享栈的结构体以及初始化:
// 共享栈的结构体
typedef struct {
ElemType data[Maxsize];
int top1;
int top2;
}ShareStack;
// 初始化栈
void InitStack1(ShareStack*& s) {
s = (ShareStack*)malloc(sizeof(ShareStack));
s->top1 == -1;
s->top2 == Maxsize;
}
共享栈是为了更有效的利用存储空间,两个栈的空间相互调节,只有在整个存储空间被占满时才发生上溢。
其存取数据的时间复杂度O(1)。所以对存取效率没有什么影响。
5.栈的链式存储以及基本运算的实现
链栈的特点:
- 栈空的条件: s->next ==NULL
- 栈满的条件:链栈中几乎不存在栈满的情况
- 元素 e 进栈的操作:新建一个结点p,存放元素 e 。将结点 p 插入头结点之后。
- 出栈的操作:取出首结点的 data 值并且将它删除
链栈的结构体:
#include <malloc.h>
#define ElemType int
typedef struct linkNode {
ElemType data; // 数据域
struct linkNode* next; // 指针域
}LinkStNode;
InitStack(&s): 初始化栈,构造一个空栈
void InitStack(LinkStNode*& s) {
s = (LinkStNode*)malloc(sizeof(LinkStNode));
s->next = NULL;
}
DestoryStack(&s): 销毁栈,释放给栈s分配的内存空间
void DestoryStack(LinkStNode*& s) {
LinkStNode* pre = s;
LinkStNode* p = s->next;
while (p != NULL) {
free(pre);
pre = p;
p = pre->next;
}
free(pre);
}
StackEmpty(s): 判断栈是否为空,若栈为空,返回真,否则返回假
bool StackEmpty(LinkStNode* s) {
return (s->next == NULL);
}
Push(&s): 进栈,将元素e插入栈s中作为栈顶元素
bool Push(LinkStNode*& s, ElemType e) {
LinkStNode* p;
p = (LinkStNode*)malloc(sizeof(LinkStNode));
p->data = e;
p->next = s->next;
s->next = p;
return true;
}
Pop(&s): 出栈,从栈s中删除栈顶元素,并将其赋值给e
bool Pop(LinkStNode*& s, ElemType& e) {
LinkStNode* p;
if (s->next == NULL) return false;
p = s->next;
e = p->data;
s->next = p->next;
free(p);
return true;
}
GetTop(s): 取栈顶元素,返回当前的栈顶元素,并将其赋值给e。
bool GetTop(LinkStNode*& s, ElemType& e) {
if (s->next != NULL) return false;
e = s->next->data; // s表示的是头节点
return true;
}
二.队列
1.队列的定义
队列也是一种操作受限的线性表。其限制为仅允许在表的一端进行插入操作,在表的另一端进行删除操作。
队尾:进行插入的一端叫做队尾。
队头:进行删除的一端叫做对头。
向队列中插入新元素叫做入队或者进队,从队列中删除元素叫做出队和离队。
队列的特点:
队列的主要特点就是" 先进先出 (First in First out)"(后进后出(last in last out))。即先进队列的元素先出队列。所以也叫做先进先出表。
2.队列的基本运算
- InitQueue(&Q):初始化队列,构造一个空队列Q
- QueueEmpty(Q): 判断队列空,若队列 Q 为空返回 true,否则返回 false
- EnQueue(&Q,x):入队,若队列 Q 未满,将 x 加入,使之成为新的队尾。
- DeQueue(&Q,&x):出队,若队列 Q 非空,删除队头元素,并用 x 返回
- DestoryQueue(&Q):销毁队列,并释放队列空间。
3.队列的顺序存储以及基本运算的实现
顺序队列的特点:
- 对空的条件:q->front == q->rear。
- 队满的条件:q->rear == Maxsize-1
- 元素 e 进队的操作:先将 q->rear++;然后将元素 e 放在数组的 rear 位置。
- 出队的操作:先将 q->front++;之后取出 data 数组中 front 位置的元素。
顺序队的结构体:
typedef struct {
ElemType data[Maxsize];
int front; // 队头指针(队头删除元素)
int rear; // 队尾指针(队尾添加元素)
}SqQueue;
InitQueue(&Q):初始化队列,构造一个空队列Q
void InitQueue(SqQueue*& q) {
q = (SqQueue*)malloc(sizeof(SqQueue));
q->front = q->rear = -1;
}
DestoryQueue(&Q):销毁队列,并释放队列空间。
void DestoryQueue(SqQueue*& q) {
free(q);
}
QueueEmpty(Q): 判断队列空,若队列 Q 为空返回 true,否则返回 false
bool QueueEmpty(SqQueue* q) {
return (q->front == q->rear);
}
EnQueue(&Q,x):入队,若队列 Q 未满,将 x 加入,使之成为新的队尾。
bool EnQueue(SqQueue*& q, ElemType e) {
if (q->rear == Maxsize - 1) return false;
q->rear++;
q->data[q->rear] = e;
return true;
}
DeQueue(&Q,&x):出队,若队列 Q 非空,删除队头元素,并用 x 返回
bool deQueue(SqQueue*& q, ElemType& e) {
if (q->front == q->rear) return false;
q->front++;
e = q->data[q->front];
return true;
}
实现基本运算之后,我们根据顺序队列的两个条件可能会出现问题。队满的条件时 :q->rear == Maxsize-1;但是我们发现进队出队是两个伪指针在控制着,如果进队时 rear 不断后移,一直到 Maxsize-1 的下标出,但是在这过程中 font 也是一直在增加,使得元素一直出队。这就导致就算 rear== Maxsize-1;我们这个队列也不是满的,从而造成空间的浪费,这就是假溢出。
使用循环队列来解决假溢出的问题。
4.循环队列以及基本运算实现
循环队列:
在假溢出时 rear ==Maxsize-1时;我们可以发现另外一端还有很多空闲的位置,所以我们将数组的前端位置和后端位置连接起来,类似于循环链表,首尾相连。形成一个循环数组。
当首尾相连之后,当队尾指针 rear==Maxsize-1时;如果需要继续存储元素的话,那么就需要放在另一端的空位置上了。
队头指针 front 循环增1:front =(front+1)%Maxsize
队尾指针 rear 循环增1:rear = (rear+1)%Maxsize
循环队列的特点:
- 初始时:q->front = q->rear=0;
- 队头指针+1:q->front=(q->front+1)%Maxsize
- 队尾指针+1:q->rear=(q->rear+1)%Maxsize
- 队列长度:(rear--front+Maxsize)%Maxsize
队空条件:q->rear==q->front
对满条件:q->front=(q->rear+1)%Maxsize
InitQueue(&Q):初始化队列,构造一个空队列Q
void InitQueue(SqQueue*& q) {
q = (SqQueue*)malloc(sizeof(SqQueue));
q->front = q->rear = 0;
}
QueueEmpty(Q): 判断队列空,若队列 Q 为空返回 true,否则返回 false
bool QueueEmpty(SqQueue*& q) {
return (q->front == q->rear);
}
DestoryQueue(&Q):销毁队列,并释放队列空间。
void DestoryQueue(SqQueue*& q) {
free(q);
}
EnQueue(&Q,x):入队,若队列 Q 未满,将 x 加入,使之成为新的队尾。
bool EnQueue(SqQueue*& q, ElemType e) {
if (q->front ==(q->rear + 1) % Maxsize) return false;
q->rear = (q->rear + 1) % Maxsize;
q->data[q->rear] = e;
return true;
}
DeQueue(&Q,&x):出队,若队列 Q 非空,删除队头元素,并用 x 返回
bool deQueue(SqQueue*& q, ElemType& e) {
if (q->front == q->rear) return false;
q->front=(q->front+1)%Maxsize;
e = q->data[q->front];
5.队列的链式存储以及基本运算的实现
队列中的数据元素的逻辑关系呈线性关系,所以队列可以像线性表那样采用链式存储结构。采用链式存储的队列叫做链队。
链队的特点:
- 队空的条件:q->rear == NULL(q->front ==NULL)
- 当队中只有一个数据元素时:q->rear == q->front
- 队满的条件:不考虑
- 元素 e 进栈的操作:新建一个结点p,存放元素 e 。将结点 p 插入头结点之后。
- 出栈的操作:取出首结点的 data 值并且将它删除
链队的结构体:
// 链栈的数据结点的结构体
typedef struct qnode {
ElemType data;
struct qnode* next;
}DataNode;
// 链栈的头节点
typedef struct {
DataNode* front; // 指向队首结点
DataNode* rear; // 指向队尾结点
}LinkQueue;
InitQueue(&Q):初始化队列,构造一个空队列Q
void InitQueue(LinkQueue*& q) {
q = (LinkQueue*)malloc(sizeof(LinkQueue));
q->front = q->rear = NULL;
}
QueueEmpty(Q): 判断队列空,若队列 Q 为空返回 true,否则返回 false
bool QueueEmpty(LinkQueue *q){
return (q->front == NULL);
}
EnQueue(&Q,x):入队,若队列 Q 未满,将 x 加入,使之成为新的队尾。
bool EnQueue(LinkQueue*& 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; // 将 p 结点放在 q->rear 的后面
q->rear = p; // 将 队尾结点指向 p
}
}
DeQueue(&Q,&x):出队,若队列 Q 非空,删除队头元素,并用 x 返回
bool DeQueue(LinkQueue*& q,ElemType &e) {
DataNode* p;
if (q->rear == NULL) return false;
p = q->front; // 将需要删除的元素指向 p;
if (q->front == q->rear) // 当链表中只有一个元素
q->front = q->rear = NULL;
else {
q->front = q->front->next;
}
e = p->data;
free(p);
return true;
}
DestoryQueue(&Q):销毁队列,并释放队列空间。
void DestoryQueue(LinkQueue*& q) {
DataNode* pre = q->front; // pre指向链队的首结点
DataNode* p;
if (pre != NULL) {
p = pre->next;
while (p != NULL) {
free(pre);
pre = p;
p = pre->next;
}
free(pre);
}
free(q);
}
6.双端队列
双端队列指的是允许两端都可以进行删除和插入的队列。
双端队列的特点:
- 前端进的元素排列在队列中的后端进的元素的前面,后端进的元素排列在前端进的元素的后面。
- 无论是前端还是后端出队,先出的元素排列在后出的元素的前面。
输出受限的双端队列:
允许一段进行插入和删除,但在另一端只允许插入的双端队列称为输出受限的双端队列。
输入受限的双端队列:
允许一段进行插入和删除,但在另一端只允许删除的双端队列称为输入受限的双端队列。