Leetcode循环队列

这道题十分考验我们对队列的理解。

文章目录

队列的介绍

队列是一种只允许在一段进行插入,在另一端进行删除的数据操作的特殊线性结构,,因此决定了他具有先入先出的特点,其中进行插入操作的一段叫做队尾,出队列的一端叫做队头。

队列的实现

队列可以使用链表或者数组进行实现,对于这两种实现方法,使用链表实现效果更好一点,两个指针中front为链表的头,即队列的队头,出数据的话只需要找到front的下一个假设为pre,将front销毁,front置为pre即可,如果是用数组的结构的话,出队列在数组头上出数据,效率会很低。

链表实现队列代码如下

Queue.h

c 复制代码
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>


typedef int QDataType;

typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QNode;


typedef struct Queue
{
	QNode* head;//头
	QNode* tail;//尾
	int size;//大小
}Que;
void QueueInit(Que* pq);//初始化
void QueuePush(Que* pq,QDataType);//入队列
void QueueDestroy(Que* pq);//销毁
void QueuePop(Que* pq);//出队列
QDataType QueueFront(Que* pq);//队头的数据
QDataType QueueBack(Que* pq);//队尾的数据

bool QueueEmpty(Que* pq);//检查是否为空
int QueueSize(Que* pq);//队列元素

Queue.c

c 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include "Queueh.h"

void QueueInit(Que* pq)
{
	assert(pq);

	pq->head = pq->tail = NULL;
	pq->size = 0;
}

void QueueDestroy(Que* pq)
{
	assert(pq);

	QNode* cur = pq->head;
	while (cur != NULL)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
	pq->size = 0;

}

void QueuePush(Que* pq, QDataType x)
{
	assert(pq);

	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	newnode->next = NULL;
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->data = x;

	if (pq->tail == NULL)//这里要先进行判断
	{
		pq->head = pq->tail = newnode;//如果队列里没有一个元素
		//那么head和tail都为空,将newnode设置为队头,当然也是队尾,毕竟只有一个元素。
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
	pq->size++;
}

void QueuePop(Que* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));//判空,避免引起不必要的错误。
	if (pq->head->next != NULL)
	{
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
	else
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	pq->size--;
}

QDataType QueueFront(Que* pq)
{
	assert(pq);
	//判断是否为空
	assert(!QueueEmpty(pq));
	return pq->head->data;//队头位置
}

QDataType QueueBack(Que* pq)
{
	assert(pq);
	//判断是否为空
	assert(!QueueEmpty(pq));
	return pq->tail->data;//队尾位置
}

bool QueueEmpty(Que* pq)//判断是否为空,为空则返回true
{
	if (pq->head == NULL)
	{
		return true;
	}
	else
	{
		return false;
	}
}

int QueueSize(Que* pq)
{
	assert(pq);
	return pq->size;
}

进入正题

链接:设计循环队列

前边的队列如果空间不够就会扩容,但是这里的循环队列大小是固定 的,只可以保存k个元素,当然还是遵循先入先出的规则

例如下边的环形队列,在pop掉队头数据后,这块空间不会被销毁的,可以继续存储值覆盖原来的值,假设k等于5,当入6个元素后,这个循环队列就满了,当出队列时,此时这个队列的首位置就可以继续存储数据。

循环队列的思路和环形队列一样

那么这道题用数组实现比较好还是链表实现比较好呢?

我想你一定第一反应是用链表实现比较好,然而这道题相对于数组,链表实现就显得更加复杂一点

因为这也是一个环状结构,刚好可以实现一个环形链表来实现,但是我们要注意的是,链表的两个指针的状态通常是左闭右开。

即 [front,tail)

再插入一个数据后,tail=tail->next。

可以发现,tail总是在有效数字的下一位置。

和实现链表不同的地方是,链表的尾只有在开好后才会让tail指向尾节点,这里的链表长度是固定的,当一个元素入队列后,tail会指向下一个位置,如果是这样的话如何表示空和满 呢?

当最后插入一个数据后,tail指向下一个位置刚好是front,判断为满?但是当队列里没有元素的话,tail和front的位置也是相同的,当然,如果你定义一个size来判断当然可以。

然而还有一个更大的缺陷就是题目要求获取队尾元素,如果是单向链表的话,想要找放到tail的前一个结点还需要遍历一边才行,如果用双向链表的话也可以,但是就有点太大费周章了。

如果用数组来实现会简单许多滴!

结构体声明如下

c 复制代码
typedef struct {
    int* a;
    int front;
    int rear;//尾
    int k;
} MyCircularQueue;

初始化函数如下

c 复制代码
MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue*obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    //多开一个方便区分空和满
    obj->a=(int*)malloc(sizeof(int)*(k+1));
    obj->k=k;
    obj->front=obj->rear=0;
    return obj;
}

数组也面临一个同样的问题,如何判断是满还是空?

可以多开一块空间。k==5,开6块空间,最后一块空间浪费不用。

如果front等于tail就为空,在这里设置tail的值为0~5(要注意下标和位置有减一的关系),如果tail的下一个位置为front时,表示队列已满。

obj->tail%=(obj->k+1);//obj为循环队列变量

控制tail的值的变化范围,当tail等于6时置tail为0。

当然,在出队列过程中,front是会不断变化的。

我们看front变化的情况

pop一个数据后,向后移一位

判断是否队列已满的条件判断句如下

(obj->tail+1)%(obj->k+1 )==obj->front

前边已经说过了,tail的变化范围为0~5,此时tail被置为0,但front为1,不相等,就表示还有空余的位置,队列没有满,所以上边的判断语句在任何场景都是正常使用的。

判断是否已满函数如下(题目中tail被rear替换)

c 复制代码
bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return (obj->rear+1)%(obj->k+1  )==obj->front;
}

返回类型为布尔值,如果相等就返回true,不相等就返回false。

判断是否为空很简单,直接比较rear和front的值是否相等即可。

c 复制代码
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->front==obj->rear;
}

置空函数

c 复制代码
void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->a);
    free(obj);
}

获取队头元素

很简单,返回front位置的元素即可

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

获取队尾元素

这里就要思考一下了

如果rear不在数组第一个空间上,直接返回数组rear-1处的值即可,当rear位于数组首元素,就要返回数组第k个元素。

代码如下

c 复制代码
int myCircularQueueRear(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    else
    {
        if(obj->rear==0)
        {
            return obj->a[obj->k];
        }
        else{
            return obj->a[obj->rear-1];
        }
		//下边是综合写法
        //return obj->a[(obj->rear+obj->k)%(obj->k+1)];
    }
}

出队列deQueue()

出队列只需要将front++即可,就算之前的数据不销毁,下次入队列操作也会覆盖他的数据。

代码如下

c 复制代码
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
    {
        return false;
    }
    ++obj->front;
    obj->front%=(obj->k+1);
    return true;
}

注意是要有返回值的,如果队列为空,就没有出数据,返回false,出数据成功就返回true。
入队列

代码如下:

c 复制代码
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if(myCircularQueueIsFull(obj))
    {
        return false;
    }

    obj->a[obj->rear]=value;
    obj->rear++;

    obj->rear%=(obj->k+1);

    return true;
}

首先判断循环队列是否已满,如果已满就返回false,如果没有满就将rear位置的值改为value,然后rear++,判断是否超出范围,如果超出就置为0。最后入队列成功返回true。

到了这里这道题目就顺利解决了,如果使用双向链表来做这道题的话当然也可以,但是会稍微麻烦一点,有兴趣可以尝试尝试。

今天的内容就到这里啦,如果有错误欢迎各位指正哦!

综合上述代码后即可通过本题。

全部代码如下

c 复制代码
typedef struct {
    int* a;
    int front;
    int rear;
    int k;
} MyCircularQueue;


MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue*obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    //多开一个方便区分空和满
    obj->a=(int*)malloc(sizeof(int)*(k+1));
    obj->k=k;
    obj->front=obj->rear=0;
    return obj;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->front==obj->rear;
}

bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return (obj->rear+1)%(obj->k+1  )==obj->front;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if(myCircularQueueIsFull(obj))
    {
        return false;
    }

    obj->a[obj->rear]=value;
    obj->rear++;

    obj->rear%=(obj->k+1);

    return true;
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
    {
        return false;
    }
    ++obj->front;
    obj->front%=(obj->k+1);
    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
    {
        if(obj->rear==0)
        {
            return obj->a[obj->k];
        }
        else{
            return obj->a[obj->rear-1];
        }

        //return obj->a[(obj->rear+obj->k)%(obj->k+1)];
    }
}



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

/**
 * Your MyCircularQueue struct will be instantiated and called as such:
 * MyCircularQueue* obj = myCircularQueueCreate(k);
 * bool param_1 = myCircularQueueEnQueue(obj, value);
 
 * bool param_2 = myCircularQueueDeQueue(obj);
 
 * int param_3 = myCircularQueueFront(obj);
 
 * int param_4 = myCircularQueueRear(obj);
 
 * bool param_5 = myCircularQueueIsEmpty(obj);
 
 * bool param_6 = myCircularQueueIsFull(obj);
 
 * myCircularQueueFree(obj);
*/
相关推荐
2401_8572979118 分钟前
秋招内推2025-招联金融
java·前端·算法·金融·求职招聘
通信仿真实验室27 分钟前
MATLAB使用眼图分析QPSK通信系统接收端匹配滤波后的信号
开发语言·算法·matlab
通信仿真实验室32 分钟前
(15)衰落信道模型作用于信号是相乘还是卷积
开发语言·人工智能·算法·matlab
Run with the Wind38 分钟前
【2024.9.29练习】R 格式
算法
Jeremy_1213841 分钟前
三种波束形成方法的区别(MVDR、MMSE以及MSNR波束形成器)
算法
远望樱花兔1 小时前
【d59】【Java】【力扣】146.LRU缓存
java·开发语言·算法
秋夜Autumn1 小时前
贪心算法相关知识
算法·贪心算法
小懒编程日记2 小时前
【数据结构与算法】B树
java·数据结构·b树·算法
王俊山IT2 小时前
C++学习笔记----8、掌握类与对象(五)---- 嵌套类与类中枚举
开发语言·c++·笔记·学习
Bruce_Li_Q2 小时前
C语言贪吃蛇
c语言·开发语言