数据结构_队列

队列类似于日常生活中的排队,它也是一种特殊的线性表。队列和栈有相反的逻辑,但是却属于同类结构。

队列的介绍

定义: 队列是一种特殊的线性表,它只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 的原则。

入队列:从队列的队尾插入数据的操作
出队列:从队列的队头删除数据的操作

队列的遵循的原则如下图所示
在这里插入图片描述
与排队做核酸一样,先排队的人先做,做完离开,后来的人从站在队尾,直到做完核酸离开。

队列的结构

队列是在两端进行操作的,如果采用顺序表的结构的话,出队列时需要将队列中其它元素都向前移动一位,比较麻烦,因此一般情况下队列用链表来实现比较好。下图是队列的抽象结构
在这里插入图片描述

队列的结构体的定义

typedef int QDataType;

// 链式结构的队列 

// 队列中的结点
typedef struct QListNode
{
	struct QListNode* next;
	QDataType data;
}QNode;

// 队列的结构 
typedef struct Queue
{
	QNode* front;
	QNode* rear;
}Queue;

队列由一个个结点构成,所以需要定义队列中结点的结构体,队列的结构体中定义头结点和尾结点,因为队列只会队头结点和尾结点操作。

队列的实现

(1)队列初始化

void QueueInit(Queue* q)
{
	assert(q);

	q->front = NULL;
	q->rear = NULL;
}

队列是需要初始化的,初始时,头结点和尾结点指针都指向NULL。

(2)判断队列是否为空。为空返回1,非空返回0。

int QueueEmpty(Queue* q)
{
	assert(q);

	return q->front == NULL;
}

因为后面的多个操作需要判断队列是否为空,所以先实现这个函数。

(3)入队列

void QueuePush(Queue* q, QDataType data)
{
	assert(q);
	// 创建结点
	QNode* newnode = (QNode*)malloc(sizeof(QNode));

	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	// 为结点赋值
	newnode->data = data;
	newnode->next = NULL;
	// 判断队列是否为空
	if (QueueEmpty(q))
	{
		// 队列为空,插入队列的结点既是头结点也是尾结点
		q->front = q->rear = newnode;
	}
	else 
	{
		// 队列非空,让尾结点指向要插入的结点,然后插入的结点为新的尾结点
		q->rear->next = newnode;
		q->rear = newnode;
	}
}

入队列的步骤

  1. 先创建一个没有指向的结点,给结点赋值;
  2. 判断队列是否为空
    :插入队列的结点既是头结点也是尾结点
    非空:让尾结点指向要插入的结点,然后插入的结点为新的尾结点

(4)出队列

void QueuePop(Queue* q)
{
	assert(q);
	assert(!QueueEmpty(q));

	// 如果只剩一个结点的情况
	if (q->front->next == NULL)
	{
		free(q->front);
		q->front = q->rear = NULL;
	}
	else
	{
		QNode* next = q->front->next;
		free(q->front);
		q->front = next;
	}
}

出队列时需要判断队列中是否只有一个结点。队列中只有一个结点时,出队列后要让队头指针和队尾指针都指向NULL。

(5)队头数据

QDataType QueueFront(Queue* q)
{
	assert(q);
	assert(!QueueEmpty(q));

	return q->front->data;
}

(6)队尾数据

QDataType QueueBack(Queue* q)
{
	assert(q);
	assert(!QueueEmpty(q));

	return q->rear->data;
}

(7)队列中元素个数

int QueueSize(Queue* q)
{
	assert(q);

	int count = 0;

	QNode* cur = q->front;

	while (cur)
	{
		count++;
		cur = cur->next;
	}

	return count;
}

(8)销毁队列

void QueueDestroy(Queue* q)
{
	assert(q);

	QNode* cur = q->front;

	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	q->front = q->rear = NULL;
}

完整代码及测试程序

一共三个文件

  1. Queue.h
  2. Queue.c
  3. Test_Queue.c
#pragma once
#include<stdio.h>
#include<assert.h>
#include<malloc.h>
#include<assert.h>

typedef int QDataType;

// 链式结构:表示队列 
// 队列中的结点
typedef struct QListNode
{
	struct QListNode* next;
	QDataType data;
}QNode;

// 队列的结构 
typedef struct Queue
{
	QNode* front;
	QNode* rear;
}Queue;

// 初始化队列 
void QueueInit(Queue* q);

// 队尾入队列 
void QueuePush(Queue* q, QDataType data);

// 队头出队列 
void QueuePop(Queue* q);

// 获取队列头部元素 
QDataType QueueFront(Queue* q);

// 获取队列队尾元素 
QDataType QueueBack(Queue* q);

// 获取队列中有效元素个数 
int QueueSize(Queue* q);

// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue* q);

// 销毁队列 
void QueueDestroy(Queue* q);
#include"Queue.h"


void QueueInit(Queue* q)
{
	assert(q);

	q->front = NULL;
	q->rear = NULL;
}

void QueuePush(Queue* q, QDataType data)
{
	assert(q);

	QNode* newnode = (QNode*)malloc(sizeof(QNode));

	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}

	newnode->data = data;
	newnode->next = NULL;

	if (QueueEmpty(q))
	{
		q->front = q->rear = newnode;
	}
	else 
	{
		q->rear->next = newnode;
		q->rear = newnode;
	}

}

void QueuePop(Queue* q)
{
	assert(q);
	assert(!QueueEmpty(q));

	// 如果只剩一个结点的情况
	if (q->front->next == NULL)
	{
		free(q->front);
		q->front = q->rear = NULL;
	}
	else
	{
		QNode* next = q->front->next;
		free(q->front);
		q->front = next;
	}

}

QDataType QueueFront(Queue* q)
{
	assert(q);
	assert(!QueueEmpty(q));

	return q->front->data;
}

QDataType QueueBack(Queue* q)
{
	assert(q);
	assert(!QueueEmpty(q));

	return q->rear->data;
}

int QueueSize(Queue* q)
{
	assert(q);

	int count = 0;

	QNode* cur = q->front;

	while (cur)
	{
		count++;
		cur = cur->next;
	}

	return count;
}

// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue* q)
{
	assert(q);

	return q->front == NULL;
}

// 销毁队列 
void QueueDestroy(Queue* q)
{
	assert(q);

	QNode* cur = q->front;

	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	q->front = q->rear = NULL;
}
#include"Queue.h"


Test()
{
	Queue q;
	QueueInit(&q);

	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);

	printf("队头:%d ", QueueFront(&q));
	printf("队尾:%d\n", QueueBack(&q));
	printf("出队列:\n");
	QueuePop(&q);
	printf("队头:%d", QueueFront(&q));
	printf("队尾:%d\n", QueueBack(&q));
	printf("队列大小:%d\n", QueueSize(&q));
	printf("全部出队列:");

	while (!QueueEmpty(&q))
	{
		printf("%d ", QueueFront(&q));
		QueuePop(&q);
	}

	QueueDestroy(&q);

}

int main()
{
	Test();

	return 0;
}

循环队列

循环队列的介绍

循环队列可以使用数组来实现的,它里面能够存储的最大元素个数是固定的,它可以在数组前面的元素出队列后,把前面的位置当成尾来插入数据,因此可以实现对数组的这内存的循环使用,也就是说可以重复利用之前用过的空间。但是操作逻辑还是跟队列一致,先进先出、后进后出,因此称为循环队列。
要实现循环队列,那肯定在队尾下标超出数组下标后,要让队尾下标回到0位置,再插入,但是这个时候会遇到队空队满无法判断的问题,如下图所示,在队满和队空时会遇到同样的情况tail==front
在这里插入图片描述

为了解决上述问题,可以采取空一个位置的方式设计队列。接下来结合下面这个图来说明循环队列的实现方式。
在这里插入图片描述
在上图中A、B、C指的是队列的三种状态,顺序是从A到C,这三种状态演示了如何循环使用数组。在队列中,front是队头元素的下标,tail是队尾元素的下标+1,绿色填充方块是插入的数据,灰色是空下来的空间。假设队列最大可以存储k个元素(上图中为9),但是我们需要开辟k+1个单位的空间(下标从0到9,k+1=9+1=10),将tail位置的空间空出来。当下标超过数组下标是时,回到数组开始的下标(也就是让下标等于0),即tail和font的值在区间[0,9]上循环,很多书上是用取余,本质都是让超过数组下标后回到开始的下标。此时队空的判断条件为tail==front,队满的判断条件为tail+1==front

A状态:front=0,tail=9,tail+1==9+1==10,超过数组下标了,所以把tail+1看作0(这块具体可以参考判断队列满的代码,代码中实际上是定义了一个变量,用这个变量来跟front比较),此时正好等于front,所以是队满状态;在B状态:先出2个数据,此时front=2,那么根据前面的条件判断队非满,所以在9位置处插入数据,然后tail++,此时tail==10== 9+1 == k+1,所以让tail=0;C状态:插入一个数据后,tail=1,此时tail+1==(1+1)==2==front,表示队列已满。

循环队列的实现

结构体的定义

typedef struct {
    int* a; // 数组
    int k; // 队列最多能存多少个元素
    int front; // 队头下标
    int tail; // 队尾(队尾元素的下一个下标)
} MyCircularQueue;

注意: k指的是队列中可以放进去的元素个数,不是开辟的数组大小。k为9时,开辟的空间应该k+1个。

(1)队列初始化

MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    obj->a = (int*)malloc(sizeof(int)* (k+1));
    obj->k = k;
    obj->front = 0;
    obj->tail = 0;

    return obj;
}

(2)判断队列是否为空

bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->front == obj->tail;
}

(3)判断队列是否已满

bool myCircularQueueIsFull(MyCircularQueue* obj) {
    // 如果满的话tail+1 == front 
    int tailNext = obj->tail + 1;
    
    if (tailNext == obj->k+1) // 类似于对k+1取余
    {
        tailNext = 0;
    }

    return tailNext == obj->front;
}

定义变量tailNext的原因: 避免tai在数组最后一个位置时,tail+1超过数组下标,应该回到数组下标0位置,但是又不能让tail+1等于0,所以用这个tailNext这个临时变量来帮助判断。

(4)往队列中插入数据

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    
    if (myCircularQueueIsFull(obj))
    {
        return false;
    }
    else
    {
        obj->a[obj->tail] = value;
        obj->tail++;

        if (obj->tail == obj->k+1) // 超出数组下标后回到0位置
        {
            obj->tail = 0;
        }

        return true;
    }
}

(5)从队列中出数据

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj))
    {
        return false;
    }
    else
    {
        obj->front++;
        if (obj->front == obj->k+1)
        {
            obj->front = 0;
        }

        return true;
    }
}

(6)队头数据

int myCircularQueueFront(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj))
        return -1;
    else 
        return obj->a[obj->front];
}

(7)队尾数据

int myCircularQueueRear(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj))
        return -1;
    else 
    {
        int tailPrev = obj->tail-1;

        if (tailPrev == -1) // 如果tail指向队列第一个位置,则队尾在第k个位置
        {
            tailPrev = obj->k;
        }

        return obj->a[tailPrev];
    }
}

(8)销毁队列

void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->a);
    free(obj);
}

完整代码

typedef struct {
    int* a; // 数组
    int k; // 队列最多能存多少个数据
    int front; // 队头
    int tail; // 队尾(队尾数据的下一个下标)

} MyCircularQueue;

// 创建循环队列
MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    obj->a = (int*)malloc(sizeof(int)* (k+1));
    obj->k = k;
    obj->front = 0;
    obj->tail = 0;

    return obj;

}

// 判断队列是否为空
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->front == obj->tail;
}

// 判断队列是否已满
bool myCircularQueueIsFull(MyCircularQueue* obj) {
    // 如果满的话tail+1 == front 
    int tailNext = obj->tail + 1;
    if (tailNext == obj->k+1)
    {
        tailNext = 0;
    }

    return tailNext == obj->front;
}

// 往队列中插入数据
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    
    if (myCircularQueueIsFull(obj))
    {
        return false;
    }
    else
    {
        obj->a[obj->tail] = value;
        obj->tail++;

        if (obj->tail == obj->k+1)
        {
            obj->tail = 0;
        }

        return true;
    }
}

// 从队列中出数据
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj))
    {
        return false;
    }
    else
    {
        obj->front++;
        if (obj->front == obj->k+1)
        {
            obj->front = 0;
        }

        return true;
    }
}

// 队头数据 
int myCircularQueueFront(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj))
        return -1;
    else 
        return obj->a[obj->front];
}

// 队尾数据
int myCircularQueueRear(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj))
        return -1;
    else 
    {
        int tailPrev = obj->tail-1;

        if (tailPrev == -1) // 如果tail指向队列第一个位置,则队尾在第k个位置
        {
            tailPrev = obj->k;
        }

        return obj->a[tailPrev];
    }
 
}

// 销毁队列
void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->a);
    free(obj);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值