文章目录
栈(Stack)和队列(Queue)
栈和队列,也属于线性表,都用于存储逻辑关系为 “一对一” 的数据。
栈也可分为顺序栈和链表,队列也分为顺序队列和链队列。
栈(Stack)
栈同顺序表和链表一样,栈也是用来存储逻辑关系为 “一对一” 数据的线性存储结构,且遵循"先进后出(FILO)"
原则。
栈的开口端被称为栈顶,封口端被称为栈底。栈顶元素指的就是距离栈顶最近的元素,栈底元素指的是位于栈最底部的元素。
如下图所示:
- 栈只能从表的一端存取数据,另一端是封闭的。
- 在栈中,无论是存数据还是取数据,都必须遵循"先进后出"的原则,即最先进栈的元素最后出栈。从图中可看出数据的存储状态,元素 1 是最先进的栈。因此,当需要从栈中取出元素 1 时,根据"先进后出"的原则,需提前将元素 3 和元素 2 从栈中取出,然后才能成功取出元素 1。
进栈和出栈
- 向栈中添加元素,此过程被称为"进栈"(入栈或压栈);
- 从栈中提取出指定元素,此过程被称为"出栈"(或弹栈);
栈的实现方式
- 顺序栈:采用顺序存储结构可以模拟栈存储数据的特点,从而实现栈存储结构;
- 链栈:采用链式存储结构实现栈结构;
顺序栈
顺序栈是利用顺序存储结构实现的栈,即利用一组地址连续的存储单元依次存放自栈底到栈顶的元素数据。分别使用top
指针和base
指针指向栈顶和栈底。
初始化顺序栈
- 分配存储空间。
- 置栈为空栈,即栈顶指针和栈底指针都指向栈底。
int InitStack(SqStack &S){
S.base=new int[MAX];
if(!S.base){
return 0;
}
S.top=S.base;
S.stacksize=MAX;
return 1;
}
入栈
- 将入栈元素压入栈顶。
- 然后栈顶指针加1。
int Push_S(SqStack &S,int e){
//将元素e入栈
if(S.top-S.base==S.stacksize){//判断栈是否满
return 0;
}
*S.top++=e;
return 1;
}
出栈
- 首先判断栈是否为空,若空返回ERROR,若不空,执行下一句。
- 栈顶指针减1,然后栈顶元素出栈。
int Pop_S(SqStack &S,int &e){
//用e返回出栈的元素
if(S.top==S.base){//栈空
return 0;
}
e=*--S.top;
return 1;
}
链栈
链栈的存储结构与单链表的存储结构相同。
由于栈是在栈顶进行删除和添加元素的,因此,将链表头部作为栈顶的一端,可以避免在实现数据 “入栈” 和 “出栈” 操作时做大量遍历链表的耗时操作。
链表的头部作为栈顶,意味着:
- 在实现数据"入栈"操作时,需要将数据从链表的头部插入;
- 在实现数据"出栈"操作时,需要删除链表头部的首元节点;
因此,链栈实际上就是一个只能采用头插法
插入或删除数据的链表。
初始化链栈
typedef struct StackNode{
int data;
struct StackNode *next;
}StackNode,*LinkStack;
int InitStack(LinkStack &S){
S=NULL;//将栈顶指针置空
return 1;
}
入栈
int Push(LinkStack &S,int e){
//元素e入栈
//链栈要注意指针的方向是从栈顶指向栈底的
struct StackNode *p;
p=new StackNode;
p->data=e;
p->next=S;
S=p;
}
出栈
int Pop(LinkStack &S,int &e){
if(S==NULL){
return 0;
}
e=S->data;
struct StackNode *q;
q=new StackNode;
q=S;
S=S->next;
delete q;
return 1;
}
队列(Queue)
队列,和栈一样,也是一种对数据的"存"和"取"有严格要求的线性存储结构。与栈结构不同的是,队列的两端都"开口",要求数据只能从一端进,从另一端出,遵循 "先进先出"(FIFO)
的原则。
通常,称进数据的一端为 “队尾(rear)”,出数据的一端为 “队头(front)”,数据元素进队列的过程称为 “入队”,出队列的过程称为 “出队”。
队列实现方式
- 顺序队列:在顺序表的基础上实现的队列结构;
- 链队列:在链表的基础上实现的队列结构;
两者的区别仅是顺序表和链表的区别,即在实际的物理空间中,数据集中存储的队列是顺序队列,分散存储的队列是链队列。
顺序队列
顺序队列,即采用顺序表模拟实现的队列结构,底层使用的是数组,因此需预先申请一块足够大的内存空间初始化顺序队列。
顺序队列的实现
#include <stdio.h>
int enQueue(int *a,int rear,int data){
a[rear]=data;
rear++;
return rear;
}
void deQueue(int *a,int front,int rear){
//如果 front==rear,表示队列为空
while (front!=rear) {
printf("出队元素:%d\n",a[front]);
front++;
}
}
int main() {
int a[100];
int front,rear;
//设置队头指针和队尾指针,当队列中没有元素时,队头和队尾指向同一块地址
front=rear=0;
//入队
rear=enQueue(a, rear, 1);
rear=enQueue(a, rear, 2);
rear=enQueue(a, rear, 3);
rear=enQueue(a, rear, 4);
//出队
deQueue(a, front, rear);
return 0;
}
程序输出结果:
出队元素:1
出队元素:2
出队元素:3
出队元素:4
此实现方式出现问题,随着入队出队的进行,会使整个队列整体向后移动,这样就出现了队尾指针已经移到了最后,再有元素入队就会出现溢出,而事实上此时队中并未真的“满员”,这种现象为“假溢出”,这是由于队尾入,队头出的受限操作,以及为避免数据元素移动,队头队尾不固定造成的。
解决假溢出的方法有:
-
固定队头,出队后,队列中的所有元素向前移动。
-
固定队尾,入队时,队列中的所有元素向前移动。
-
将队列的数据区看成头尾相接的循环结构,头尾指针的关系不变,将其称为
“循环队列”
。
循环队列的实现
#include <stdio.h>
#define max 5//表示顺序表申请的空间大小
int enQueue(int *a,int front,int rear,int data){
//添加判断语句,如果rear超过max,则直接将其从a[0]重新开始存储,如果rear+1和front重合,则表示数组已满
if ((rear+1)%max==front) {
printf("空间已满");
return rear;
}
a[rear%max]=data;
rear++;
return rear;
}
int deQueue(int *a,int front,int rear){
//如果front==rear,表示队列为空
if(front==rear%max) {
printf("队列为空");
return front;
}
printf("%d ",a[front]);
//front不再直接 +1,而是+1后同max进行比较,如果=max,则直接跳转到 a[0]
front=(front+1)%max;
return front;
}
int main() {
int a[max];
int front,rear;
//设置队头指针和队尾指针,当队列中没有元素时,队头和队尾指向同一块地址
front=rear=0;
//入队
rear=enQueue(a,front,rear, 1);
rear=enQueue(a,front,rear, 2);
rear=enQueue(a,front,rear, 3);
rear=enQueue(a,front,rear, 4);
//出队
front=deQueue(a, front, rear);
//再入队
rear=enQueue(a,front,rear, 5);
//再出队
front=deQueue(a, front, rear);
//再入队
rear=enQueue(a,front,rear, 6);
//再出队
front=deQueue(a, front, rear);
front=deQueue(a, front, rear);
front=deQueue(a, front, rear);
front=deQueue(a, front, rear);
return 0;
}
程序输出结果:
1 2 3 4 5 6
使用此方法需要注意的是,顺序队列在判断数组是否已满时,出现下面情况:
- 当队列为空时,队列的头指针等于队列的尾指针;
- 当数组满员时,队列的头指针等于队列的尾指针;
链式队列
链式队列,简称"链队列",即使用链表实现的队列存储结构。
链式队列的实现思想同顺序队列类似,只需创建两个指针(命名为 front 和 rear)分别指向链表中队列的队头元素和队尾元素。
链式队列的实现
//链表中的节点结构
typedef struct QNode{
int data;
struct QNode * next;
}QNode;
//创建链式队列的函数
QNode * initQueue(){
//创建一个头节点
QNode * queue=(QNode*)malloc(sizeof(QNode));
//对头节点进行初始化
queue->next=NULL;
return queue;
}
入队
新的数据元素入队,只需进行以下 3 步操作:
- 将该数据元素用节点包裹,例如新节点名称为 elem;
- 与 rear 指针指向的节点建立逻辑关系,即执行 rear->next=elem;
- 最后移动 rear 指针指向该新节点,即 rear=elem
QNode* enQueue(QNode * rear,int data){
//1、用节点包裹入队元素
QNode * enElem=(QNode*)malloc(sizeof(QNode));
enElem->data=data;
enElem->next=NULL;
//2、新节点与rear节点建立逻辑关系
rear->next=enElem;
//3、rear指向新节点
rear=enElem;
//返回新的rear,为后续新元素入队做准备
return rear;
}
出队
元素出队,需要做以下 3 步操作:
- 通过 top 指针直接找到队头节点,创建一个新指针 p 指向此即将出队的节点;
- 将 p 节点(即要出队的队头节点)从链表中摘除;
- 释放节点 p,回收其所占的内存空间;
void DeQueue(QNode * top,QNode * rear){
if (top->next==NULL) {
printf("队列为空");
return ;
}
// 1、
QNode * p=top->next;
printf("%d",p->data);
top->next=p->next;
if (rear==p) {
rear=top;
}
free(p);
}
本篇完!!!