普通队列
队列是一种先进先出的数据结构, 不支持随机访问,只能查看队首和队尾
每次只能从队尾插入数据,从队首删除数据
队列可以使用链表作为底层实现,也可以使用动态数组进行实现,由于队首只能出,不能进,使用动态数组则会导致每次出队列之后,前面的空间空着无法使用,所以使用链表来实现。
实现
定义
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int MQElemType;
typedef struct Node {
MQElemType val; //数据域
struct Node* next; //指针域
}Node;
typedef struct MyQueue {
Node* front; //指向头
Node* back; //指向尾
size_t size; //链表大小
}MyQueue;
//创建队列
MyQueue* myQueueCreate();
//添加元素
void myQueuePush(MyQueue* obj, MQElemType x);
//弹出元素
MQElemType myQueuePop(MyQueue* obj);
//查看队首元素
MQElemType myQueueFront(MyQueue* obj);
//查看队尾元素
MQElemType myQueueBack(MyQueue* obj);
//释放队列空间
void myQueueFree(MyQueue* obj);
//判断队列是否为空
bool myQueueIsEmpty(MyQueue* obj);
创建队列
MyQueue* myQueueCreate(){
MyQueue* p = (MyQueue*)malloc(sizeof(MyQueue));
assert(p);//保证空间开辟成功
p->back = NULL;
p->front = NULL;
p->size = 0;
return p;
}
添加元素
void myQueuePush(MyQueue* obj, MQElemType x){
assert(obj); //保证操作对象为真
Node* newNode = (Node*)malloc(sizeof(Node));
assert(newNode);
newNode->val = x;
newNode->next = NULL;
//如果此时队列为空
if (obj->size == 0){
obj->front = newNode;
obj->back = newNode;
}
else{ //否则,此时队尾一定不为空,链接到队列即可
obj->back->next = newNode;
obj->back = newNode;
}
obj->size++;
}
弹出元素
MQElemType myQueuePop(MyQueue* obj){
assert(obj);
//如果此时队列为空,返回-1
if (obj->size == 0){
return -1;
}
//否则,删除队首元素,修改队首指向的位置,返回被删除的元素
MQElemType tmp = obj->front->val;
Node* pCurrent = obj->front;
obj->front = pCurrent->next;
free(tmp);
obj->size--;
//此时删除完毕,如果刚才删除的是队中的最后一个元素,那么队尾也应该变为空
if (obj->size == 0){
obj->back = NULL;
}
return tmp;
}
查看队首元素
MQElemType myQueueFront(MyQueue* obj){
assert(obj);
if (obj->size == 0){
return -1;
}
else {
return obj->front->val;
}
}
查看队尾元素
MQElemType myQueueBack(MyQueue* obj) {
assert(obj);
if (obj->size == 0) {
return -1;
}
else {
return obj->back->val;
}
}
释放队列空间
void myQueueFree(MyQueue* obj){
Node* pCurrent = obj->front;
while (obj->front != NULL){
obj->front = pCurrent->next;
free(pCurrent);
pCurrent = obj->front;
}
free(obj);
}
判断队列是否为空
bool myQueueIsEmpty(MyQueue* obj) {
return obj->size == 0;
}
循环队列
循环队列可以看成一个环,在使用数组实现时,可以将元素插入到之前出队的位置
所以在创建环形队列时,应给其一个指定大小,同时需要在每次操作的时候,判断当前队列的状态。
实现
定义
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef struct {
int* arr; //数组对象
int n; //存储最大数据
int front; //头
int back; //尾
} MyCircularQueue;
//创建环形队列
MyCircularQueue* myCircularQueueCreate(int k);
//入队
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value);
//出队
bool myCircularQueueDeQueue(MyCircularQueue* obj);
//查看队首元素
int myCircularQueueFront(MyCircularQueue* obj);
//查看队尾元素
int myCircularQueueRear(MyCircularQueue* obj);
//判断队空
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
//判断队满
bool myCircularQueueIsFull(MyCircularQueue* obj);
//释放队列
void myCircularQueueFree(MyCircularQueue* obj);
创建环形队列
若使用传统方式来判断当前队列的元素数目,也就是如果队列为空那么尾和头都指向同一个下标,则会导致无法判断是空队列还是满队列,所以当队列为空时我将其头尾都指向-1,那么在入队时,先给back添加,后操作,可以保证back所指的就是最后一个元素,同时也便于区分队列的状态
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* p = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
assert(p); //保证开辟成功
p->arr = (int*)calloc(k, sizeof(int));
assert(p->arr); //保证开辟成功
p->n = k;
p->back = -1; //此时没有元素
p->front = -1;
return p;
}
入队
入队时需要先判断队是否满了,如果满了则失败,否则成功,当队满的时候有一下两种:
1、如果此时back在front前,那么则一定有back + 1 == front
2、如果此时back在front后,那么则有back 与 front之间的元素数目刚好时最大容量,即back == front + n - 1
特殊的是,当这个队的容量只有一个时,此时如果为空,即back和front都是-1,则第二个条件恒成立,我们单独判断这种情况,也就是当front为-1时直接为空
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
//判断队列是否为满,如果队满了,则无法入队,返回假
if ((obj->back + 1 == obj->front + obj->n || obj->front - 1 == obj->back) && (obj->front != -1)) {
return false;
}
//此时可以入队返回空
if (obj->front == -1) {
//如果此时队列为空,则需要同时修改front和back
obj->front = 0;
obj->back = 0;
obj->arr[0] = value;
return true;
}
//否则此时只修改back指针即可
obj->back++;
//判断此时back位置,并进行修正
obj->back %= obj->n; //构成一个循环时指针回到0,否则还是自己
obj->arr[obj->back] = value;
return true;
}
出队
出队时,我们需要考虑当前队列出队后是否为空队列,有两种情况
1、back和front不在最后一个位置
2、back和front在最后一个位置
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if (obj->front == -1) {
return false;
}
obj->front++; //出队,则访问不到前一个元素
//如果当前front为n,则说明这个队列循环了一次
obj->front %= obj->n;
//此时出队,需要判断front 和 back之间的关系,看其是否为空
//此操作为删除,且目前已经删除了,那么只有当front恰好比back大1,或者 front为0,而back为n-1时,为空
//若back的位置刚好在front的前一个,则说明为空了
int t = obj->back + 1;
t %= obj->n;
if (t == obj->front)
{
obj->back = -1;
obj->front = -1;
}
return true;
}
获取队首元素
int myCircularQueueFront(MyCircularQueue* obj) {
if (obj->front == -1)
{
return -1;
}
return obj->arr[obj->front];
}
获取队尾元素
int myCircularQueueRear(MyCircularQueue* obj) {
if (obj->front == -1)
{
return -1;
}
return obj->arr[obj->back];
}
判断队满
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return (obj->back + 1 == obj->front + obj->n || obj->front - 1 == obj->back) || (obj->front == obj->back && obj->front != -1 && obj->n == 1);
}
判断队空
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->front == -1;
}
释放队列
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->arr);
free(obj);
}