【C数据结构】非循环队列和循环队列(链表与顺序表实现)

1、认识队列

在这里插入图片描述

队列的一大特点就是先进先出,后如后出。队列是有序的,从头出,从尾入。

一个很形象的应用就是医院的挂号机,有序的每次叫队列队头的号码,机器每次从记录的队尾加数据。

2、队列实现

一个队列需要实现的操作如下。

  1. 入队
  2. 出队
  3. 查看队头
  4. 查看队尾
  5. 计算队长度

结构的选择有顺序表和链表,从优缺点来看。

  1. 入队,顺序表和链表尾插的效率都一样,但在空间上链表更好,不会浪费空间。
  2. 出队,顺序表头删的效率低于链表头删的效率。
  3. 查看队头,都比较容易。
  4. 查看队尾,顺序表比较容易,链表需要遍历在尾部查看,如果没有建立尾指针的话。
  5. 计算对长度,顺序表本身记录了,链表需要遍历计算。

似乎都有自己擅长的领域,那么都实现一遍吧。

3、非循环队列单链表写法

3.1、测试页和头文件

为了在实现一些功能上更加的便利,在创建队列的结构体中创建头尾指针变量,以及一个size记录长度。

头文件中

//Queue.h
#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;
}QueueNode;

//队列结构体 有效避免二级指针
typedef struct Queue 
{
	QueueNode* front;
	QueueNode* tail;
	int size;
}Queue;

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

//销毁队列
void QueueDestroy(Queue* pq);

//入队
void QueuePush(Queue* pq, QDataType x);

//出队
void QueuePop(Queue* pq);

//返回队头数据
QDataType QueueFront(Queue* pq);

//返回队尾数据
QDataType QueueBack(Queue* pq);

//队列大小
int QueueSize(Queue* pq);

//判空
bool QueueEmpty(Queue* pq);

测试文件中

//Test.c
#include"Queue.h"
void QueueTest1() 
{
	Queue p;
	QueueInit(&p);
	QueuePush(&p, 1);
	QueuePush(&p, 2);
	QueuePush(&p, 3);
	///*QueuePop(&p);
	//QueuePop(&p);
	//QueuePop(&p);*/
	printf("%d\n", QueueSize(&p));
	while (!QueueEmpty(&p))
	{
		printf("%d ",QueueFront(&p));
		QueuePop(&p);
	}
	QueueDestroy(&p);
}


int main() 
{
	QueueTest1();
	return 0;
}

3.2、接口函数的实现

队列的函数实现比较容易

#include"Queue.h"

//初始化队列
void QueueInit(Queue* pq)
{
	assert(pq);

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

//销毁队列
//从头到尾一次一个结点销毁
void QueueDestroy(Queue* pq)
{
	assert(pq);
	QueueNode* p = pq->front;
	while (p)
	{
		QueueNode* cur = p;
		p = p->next;
		free(cur);
	}
	pq->front = NULL;
	pq->tail = NULL;
	pq->size = 0;
}

//入队
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	
	//先创建新结点
	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	
	//入队
	if (pq->front == NULL)
	{
		pq->front = newnode;
		pq->tail = pq->front;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = pq->tail->next;
	}

	pq->size++;
}

//出队
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	QueueNode* p = pq->front;
	pq->front = pq->front->next;
	free(p);

	pq->size--;
}

//看头
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->front->data;
}

//看尾
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->tail->data;
}

//看长
int QueueSize(Queue* pq)
{
	assert(pq);
	return pq->size;
}

//看空
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->front == NULL;
}

4、非循环队列顺序表写法

4.1、测试页和头文件

头文件中

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int QDataType;
typedef struct Queue
{
	int size;
	int capacity;
	QDataType* a;
}Queue;


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

//销毁队列
void QueueDestroy(Queue* pq);

//入队
void QueuePush(Queue* pq, QDataType x);

//出队
void QueuePop(Queue* pq);

//返回队头数据
QDataType QueueFront(Queue* pq);

//返回队尾数据
QDataType QueueBack(Queue* pq);

//队列大小
int QueueSize(Queue* pq);

//判空
bool QueueEmpty(Queue* pq);


测试文件中

//Test.c
#include"Queue.h"
void QueueTest1() {
	Queue p;
	QueueInit(&p);
	QueuePush(&p, 1);
	QueuePush(&p, 2);
	QueuePush(&p, 3);
	///*QueuePop(&p);
	//QueuePop(&p);
	//QueuePop(&p);*/
	printf("%d\n", QueueSize(&p));
	while (!QueueEmpty(&p))
	{
		printf("%d ",QueueFront(&p));
		QueuePop(&p);
	}
	QueueDestroy(&p);
}


int main() {
	QueueTest1();
	return 0;
}

4.2、接口函数的实现

#include"Queue.h"

//初始化队列
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->a = NULL;
	pq->size = -1;
	pq->capacity = -1;
}

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

	free(pq->a);
	pq->size = pq->capacity = 0;
}

//入队
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	if (pq->size == pq->capacity)
	{
		//扩容
		int newcapacity = pq->capacity == -1 ? 4 : pq->capacity;
		QDataType* tmp = (QDataType*)realloc(pq->a, sizeof(QDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("malloc fail");
			exit(-1);
		}

		pq->a = tmp;
		pq->capacity = newcapacity;
	}
	++pq->size;
	pq->a[pq->size] = x;
}

//出队
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	int i = 0;
	for (i = 0; i < pq->size; i++)
	{
		pq->a[i] = pq->a[i + 1];
	}

	pq->size--;
}

//返回队头数据
QDataType QueueFront(Queue* pq)
{
	assert(!QueueEmpty(pq));
	return pq->a[0];
}

//返回队尾数据
QDataType QueueBack(Queue* pq)
{
	assert(!QueueEmpty(pq));
	return pq->a[pq->size];
}

//队列大小
int QueueSize(Queue* pq)
{
	assert(pq);
	return pq->size + 1;
}

//判空
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->size == -1;
}

5、循环队列的实现

循环队列的实现概念来自一个leetcode上的题。

设计循环队列
在这里插入图片描述

循环队列的逻辑图
在这里插入图片描述
在题目要求中,我们需要完成:

  1. 队列创建
  2. 获取队头队尾数据
  3. 插入删除
  4. 判空,判满

值得注意的是,队列的长度在一开始就确定,所以是个静态的。

那么,对于循环队列的结构我们选链表还是顺序表?

6、循环队列单链表写法

如果选择链表

  1. 队列创建,一开始我们就需要循环创建一个长度为K的链表,这个链表有着队头和队尾指针。
  2. 获取队头队尾数据,只需要通过队头队尾指针获取。
  3. 插入删除,由于链表长度固定,插入就是尾指针指向的data直接赋值,删除就让队头指针到下一个位置。
  4. 判空,判满,这个似乎很复杂,下面讨论一下这两种情况。

链表结构的判空与判满:

在创建好 K=6 的链表后,front和back分别表示队头和队尾指针,这个时候如果插入数据,back会有两种插入表示方法,第一种back位置直接插入,front不动back++,这种情况判空很容易,当front和back相等的时候队列为空。
在这里插入图片描述

如果一直往下插入,当在back位置插入后,back又回到了front位置,这个时候队列是满的,但与队空的情况出现一致,所以这种方式还是有问题的。
在这里插入图片描述

那么就想,在这个循环队列中,能否每次多开辟一个空间,使得back能够在队满的时候能和front有一个距离,并且使得每次插入的操作都是一样的。

所以此时,每当back->next==front时,队列为满。
在这里插入图片描述

代码实现

typedef int SLDataType;
typedef struct STList
{
    struct STList* next;
    SLDataType data;
}SL;

typedef struct {
    SL* head;
    SL* front;
    SL* back;
} MyCircularQueue;

//声明
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);

//创建队列
MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    obj->head = obj->front = obj->back = NULL;
    //建立循环链表 为了更方便判断队列为空以及队列为满的情况 还是建立K+1的队列长度
    int i = 0;
    for (i = 0; i <= k; i++)
    {
        SL* newnode = (SL*)malloc(sizeof(SL));
        newnode->next = NULL;
        if (obj->head == NULL)
        {
            obj->head = obj->front = obj->back = newnode;
        }
        else
        {
            obj->back->next = newnode;
            obj->back = newnode;
        }
    }
    //尾接头 构成循环
    obj->back->next = obj->front;
    obj->back = obj->front;

    return obj;
}

//队列插入数据
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if (myCircularQueueIsFull(obj))
    {
        return false;
    }
    else
    {
        obj->back->data = value;
        obj->back = obj->back->next;
        return true;
    }
}

//队列删除数据
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj))
    {
        return false;
    }
    else
    {
        obj->front = obj->front->next;
        return true;
    }
}

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

//看队尾数据
int myCircularQueueRear(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    else
    {
        SL* p = obj->front;
        while (p->next != obj->back)
        {
            p = p->next;
        }
        return p->data;
    }
}

//判空
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->back == obj->front;
}

//判满
bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return obj->back->next == obj->front;
}

//摧毁队列
void myCircularQueueFree(MyCircularQueue* obj) {
    obj->back = obj->head;
    obj->front = obj->head;
    //将back至尾
    while (obj->back->next != obj->head)
    {
        obj->back = obj->back->next;
    }

    //从头开始一个一个删结点
    while (obj->head != obj->back)
    {
        obj->head = obj->head->next;
        free(obj->front);
        obj->front = obj->head;
    }
    free(obj->back);
    obj->head = obj->front = obj->back = NULL;
    free(obj);
    obj = NULL;

}

在这里插入图片描述

7、循环队列顺序表写法

如果使用顺序表:

  1. 队列创建,开辟一块连续的固定空间,依然是有容量和当前大小。
  2. 获取队头队尾数据,只需要通过下标0和当前大小来获取。
  3. 插入删除,插入数据就size++,删除就size–。
  4. 判空,判满,依然需要我们讨论。
  5. 顺序表的循环的实现:在size大于k值的时候,需要对size取余数,这样就很好实现了循环。

顺序表结构的判空与判满:
K=6
同样是多建立一个空间,但这也使得,如果我们在改变size的时候,都需要考虑一下size是否大于k+1,能够实现循环效果。
在这里插入图片描述
代码实现

typedef int QDataType;
typedef struct {
    int k; //队列长度
    QDataType* a;
    int front;
    int back;
} MyCircularQueue;

//声明
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);

//创建队列
MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    obj->a = (QDataType*)malloc(sizeof(QDataType) * (k + 1)); //多开一个空间
    obj->k = k + 1;
    obj->front = obj->back = 0;

    return obj;
}

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if (!myCircularQueueIsFull(obj))
    {
        obj->back = (obj->back) % (obj->k); //实现循环效果
        obj->a[obj->back] = value;
        obj->back++;

        return true;
    }
    else
    {
        return false;
    }
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if (!myCircularQueueIsEmpty(obj))
    {
        obj->front++;
        obj->front = (obj->front) % (obj->k); //实现循环效果
        return true;
    }
    else
    {
        return false;
    }
}

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->back == 0)
        {
            obj->back = obj->k;
        }
        return obj->a[obj->back - 1];
    }
}

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

bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return (((obj->back) + 1) % (obj->k)) == obj->front;
}

void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->a);
    obj->k = obj->front = obj->back = 0;
    free(obj);
}

在这里插入图片描述

本章完。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值