用数组和链表两种方法设计循环队列(C语言)

1.前言:

C语言实现栈和队列-CSDN博客中,我们介绍了队列这一线性数据结构,它有着“先进先出”的性质,我们可以不断的出入数据。那现在有这么一个场景:假设一餐馆有十张桌子,先进店的客人先点餐,离开的客人可以给新来的客人让出桌位,一共就是十张桌子,循环进出客人点菜(假设先进的客人先离开)。那我们可不可以用队列实现它呢?显然一般的队列并不能体现“循环”,因此,我们想到了让队列首尾相接,循环队列

2.循环队列的概念

 循环队列也是一种线性数据结构。相较于队列,不同点在于该队列可以循环使用已有的未被使用的空间。且其空间大小固定,依靠循环才能不断进出数据。

图中一看确实如此,但如果我们细想:如果tail指向的是队尾数据,循环队列中没有数据时head=tail只有一个数据时,head仍等于tail。这肯定是不被允许的。因此我们不能让tail指向队尾数据,而应该让其指向队尾数据的下一个,但细想又不对,因为没有数据满数据两种情况下都有head=tail,我们称之为假溢出。为解决这个问题

①我们会多申请一个空间,即使满的情况下,head也不等于tail。如图:

②在队列中加入一个计数器,通过计数器判断循环队列是否为空/满。

   要求循环队列的功能:出入数据、获取队首/队尾数据、判空。思路已经有了,我们用代码实现它。

3.实现循环队列

我们会用数组、循环链表两种方法实现。

①数组方法

定义循环队列:

typedef struct 
{
	int* p;       //malloc申请的空间返回的是首空间的地址,用指针接收
	int head;     //注意head tail不是指针,是数组的下标
	int tail;
	int num;      //循环队列固定的空间大小
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k)   //初始化循环队列,k是需要的空间大小
{
	MyCircularQueue* tmp = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
	assert(tmp);
	tmp->p = (int*)malloc(sizeof(int)*(k+1));
	tmp->head = tmp->tail = 0;                    //此时head tail均为0
	tmp->num =k ;                                 //num记录空间大小k,下面有大用
	return tmp;
}

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) //入数据
{
	if ((obj->tail + 1) % (obj->num + 1) == obj->head)     //判满,满了则不能入数据
	{
		return false;
	}
	else
	{
		obj->p[obj->tail] = value;                      //向tail处插入数据
		obj->tail = (obj->tail + 1) % (obj->num + 1);    //tail后移1个空间 
		return true;
	}
}

bool myCircularQueueDeQueue(MyCircularQueue* obj)    //删除还要判空,空的当然不能删数据
{
	if (obj->head==obj->tail)
	{
		return false;
	}
	else
	{
		obj->head = (obj->head+ 1) % (obj->num + 1);
		return true;
	}
}
int myCircularQueueFront(MyCircularQueue* obj) //获取队头数据
{
    if (obj->head != obj->tail)
	return obj->p[obj->head];
else
    return -1;
}
int myCircularQueueRear(MyCircularQueue* obj)   //获取队尾数据
{
	
    if (obj->head != obj->tail)
{
	int a = (obj->tail + obj->num) % (obj->num + 1);
	return obj->p[a];
}
else
	return -1;
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj)    //判空
{
	if (obj->head == obj->tail)
	{
		return true;
	}
	else
		return false;
}

void myCircularQueueFree(MyCircularQueue* obj)     //销毁循环队列
{
	free(obj->p);   
    free(obj);                                   //先销毁数组再销毁循环队列
	obj->head = obj->tail = obj->num = 0;
}

要明白num的作用,额外注意要循环的特殊情况:

②链表方法(循环单链表)

思想和数组一样,区别在于创建循环链表(双向链表更加复杂,单链表已经足够了)。

结点:(这里和队列的创建是一样的)

typedef int definetype;
typedef struct node
{
    definetype a;
    struct node* next;
}Node;
typedef struct 
{
    Node* front;
    Node* prere;   //多了一个指针,指向最后一个数据
    Node* rear;    //还是指向最后一个数据的下一个
    int num;
} MyCircularQueue;

因为是单链表,所以不能像数组一样直接得队尾数据,加一个指针指向rear的前一个结点更方便。

MyCircularQueue* myCircularQueueCreate(int k) 
{
    MyCircularQueue* tmp = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    assert(tmp);
    int i = 0;
    Node*c=NULL, *d=NULL;
    while (i!=(k+1))    //创建k+1个结点的单链表
    {
       
        if (i == 0)
        {
            c = (Node*)malloc(sizeof(Node));
            assert(c);
            c->next = NULL;
            tmp->front = tmp->rear=tmp->prere = c;
            i++;
        }
        else
        {
            d = (Node*)malloc(sizeof(Node));
            c->next = d;
            c = d;
            i++;
            if (i == k+1)
            {
                c->next = tmp->front;     //首尾相连
            }
        }
       
    }
    tmp->num = k;
    return tmp;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if (obj->rear->next == obj->front)
    {
        return false;        
    }
    else
    {
        definetype*we=&obj->rear->a ;
        *we = value;                     //修改数据
        obj->prere = obj->rear;
        obj->rear = obj->rear->next;
        return true;
    }
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if (obj->front == obj->rear)
    {
        return false;
    }
    else
    {
       
        obj->front = obj->front->next;
        return true;
    }
}
int myCircularQueueFront(MyCircularQueue* obj) {     //返回队头队尾数据首先需要判空
    if (obj->front == obj->rear)
        return -1;
    else
    return (obj->front)->a;
}
int myCircularQueueRear(MyCircularQueue* obj) {
    if (obj->front == obj->rear)
        return -1;
    else
    return (obj->prere)->a;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    if (obj->front == obj->rear)
    {
        return true;
    }
    else
        return false;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
    if (obj->rear->next == obj->front)
    {
        return true;
    }
    else
        return false;
}
void myCircularQueueFree(MyCircularQueue* obj)   //销毁队列要注意避免出现野指针
{
    Node* tmp = NULL;
    while (obj->num!=-1)                //注意要销毁k+1个结点    
    {
        tmp= obj->front->next;
        free(obj->front);
        obj->front = tmp;
        obj->num--;
    }
    obj->front = obj->rear = obj->prere = NULL;
    obj->num = 0;
    free(obj);
}

总结:循环队列要求循环利用固定的空间,并且无论哪种方法,都要多申请一个空间。一定要区别不同情况,并且考虑完全,才能解决问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值