数据结构之栈与队列浅谈

栈(Stack)和队列(Queue)

栈和队列,也属于线性表,都用于存储逻辑关系为 “一对一” 的数据。
栈也可分为顺序栈和链表,队列也分为顺序队列和链队列。

栈(Stack)

栈同顺序表和链表一样,栈也是用来存储逻辑关系为 “一对一” 数据的线性存储结构,且遵循"先进后出(FILO)"原则。

栈的开口端被称为栈顶,封口端被称为栈底。栈顶元素指的就是距离栈顶最近的元素,栈底元素指的是位于栈最底部的元素。

如下图所示:
在这里插入图片描述

  1. 栈只能从表的一端存取数据,另一端是封闭的。
  2. 在栈中,无论是存数据还是取数据,都必须遵循"先进后出"的原则,即最先进栈的元素最后出栈。从图中可看出数据的存储状态,元素 1 是最先进的栈。因此,当需要从栈中取出元素 1 时,根据"先进后出"的原则,需提前将元素 3 和元素 2 从栈中取出,然后才能成功取出元素 1。

进栈和出栈

  • 向栈中添加元素,此过程被称为"进栈"(入栈或压栈);
  • 从栈中提取出指定元素,此过程被称为"出栈"(或弹栈);

栈的实现方式

  • 顺序栈:采用顺序存储结构可以模拟栈存储数据的特点,从而实现栈存储结构;
  • 链栈:采用链式存储结构实现栈结构;
顺序栈

顺序栈是利用顺序存储结构实现的栈,即利用一组地址连续的存储单元依次存放自栈底到栈顶的元素数据。分别使用top指针和base指针指向栈顶和栈底。

初始化顺序栈
  1. 分配存储空间。
  2. 置栈为空栈,即栈顶指针和栈底指针都指向栈底。
int InitStack(SqStack &S){
	S.base=new int[MAX];
	if(!S.base){
		return 0;
	}
	S.top=S.base; 
	S.stacksize=MAX;
	return 1;
}
入栈
  1. 将入栈元素压入栈顶。
  2. 然后栈顶指针加1。
int Push_S(SqStack &S,int e){
	//将元素e入栈 
	if(S.top-S.base==S.stacksize){//判断栈是否满 
		return 0;
	}
	*S.top++=e;
	return 1;
}
出栈
  1. 首先判断栈是否为空,若空返回ERROR,若不空,执行下一句。
  2. 栈顶指针减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);
}

本篇完!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wahkim

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值