队列全面解析(包含循环队列)

一、什么是队列

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

入队列:进行插入操作的一端称为队尾。

出队列:进行删除操作的一端称为队头。
队列全面解析(包含循环队列)_第1张图片

如果使用数组实现,要同时实现首尾的操作,头部的操作无论是删除还是插入,效率都较低。

使用链表实现时,头部操作很方便,对于尾部操作,我们可以加一个尾指针rear,这样可以很方便的实现尾插和头删,因此可以将链表头作为队头,链表尾作为队尾。

 

typedef int QDataType;

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


typedef struct Queue
{
	QNode* front;
	QNode* rear;
	int size;
}Queue;

用单链表作为队列的结点,队列中包含头尾指针便于头删尾插,和取出首尾元素,size便于返回队列中的元素总数。

//队列的初始化
void QueueInit(Queue* pq);
//队列的销毁
void QueueDestroy(Queue* pq);
//队尾入队列
void QueuePush(Queue* pq, QDataType x);
//队头出队列
void QueuePop(Queue* pq);
//检查队列是否为空
bool QueueEmpty(Queue* pq);
//检验队列中有效元素的个数
int QueueSize(Queue* pq);
//返回队头元素
QDataType QueueFront(Queue* pq);
//返回队尾元素
QDataType QueueBack(Queue* pq);

二、初始化

//队列的初始化
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->front = pq->rear = NULL;
	pq->size = 0;
}

分别对队列中的首尾指针、size进行初始化即可,因为有多个元素,因此最好封装成函数,若是单链表,只用一个指针指向,就不用将初始化封装了。

三、入队列

//队列结点的生成
QNode* BuyQnode(QDataType x)
{
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (NULL == newnode)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
//队尾入队列
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QNode* tmp = BuyQnode(x);
	if (NULL == tmp)
	{
		perror("BuyQnode fail");
		return;
	}

	QNode* newnode = tmp;
	if (NULL == pq->front)
	{
		pq->front = pq->rear = newnode;
	}
	else
	{
		pq->rear->next = newnode;
		pq->rear = newnode;
	}
	pq->size++;

}

先用BuyQnode得到新结点。

如果是第一次入队列,首尾指针front和rear都要改变。不是第一次的话,链接到rear后,然后更新rear的指向即可。最后不要忘了将size++

队列全面解析(包含循环队列)_第2张图片

 

四、出队列

//检查队列是否为空
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->front == NULL;
}

先进行判空操作。可以用front是否指向空判断,也可以用size是否等于0判断。

然后进行出队,即头删。

//队头出队列
void QueuePop(Queue* pq)
{
	assert(pq);

	//先判断队列不为空
	assert(!QueueEmpty(pq));
	if (pq->front == pq->rear)
	{
		//指向唯一一个结点
		free(pq->front);
		pq->front = pq->rear = NULL;
	}
	else
	{
		QNode* del = pq->front;
		pq->front = pq->front->next;
		free(del);;
	}

	pq->size--;
}

有多个元素时,先用del保存要删除的位置,然后让front指向下一个元素,作为新的头,然后free掉del位置的元素,完成删除。

如果只有一个元素,删除后front指向空,但是rear尾指针仍然指向原来唯一的元素,此时为空指针,因此应该直接free掉这个,然后将front和rear都置为空。

删除完成后不要忘了size--

队列全面解析(包含循环队列)_第3张图片

 和栈相似,由于先进先出或者先进后出的顺序结构,栈和队列没法随机访问,得到任意位置的元素。需要利用循环,队列不为空时,取出队头元素,然后出队头Pop的元素,然后才能得到后面的数据。

五、拿元素

1、拿队头

2、拿队尾

队列全面解析(包含循环队列)_第4张图片

首先要保证队列不为空,为空的话不能取出元素。

然后返回首尾指针指向结点中保存的数据即可。 

队列全面解析(包含循环队列)_第5张图片 

 

六、元素数

队列全面解析(包含循环队列)_第6张图片

 直接返回size代表的元素个数即可。

七、销毁

队列全面解析(包含循环队列)_第7张图片

用cur从头开始遍历整个队列,保存next,将cur置空后,再指向下一个即可。 

八、循环队列

队列全面解析(包含循环队列)_第8张图片

实际中我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型
时可以就会使用循环队列。环形队列可以使用数组实现,也可以使用循环链表实现。

假设队列为空时,front和rear指向同一个位置,每次在rear位置尾插,然后rear+1,若总长度为k,插入k位置的元素后,rear又指向了front的位置,因此我们无法区分空队列和满队列。

因此在开始时,我们便多开一个位置,使得队列满时,有一个空的位置让rear指向,从而区分空/满

删除时让front指向下一个位置即可,front指向的是队头,rear指向的是队尾的后一个位置,因此取尾的数据是rear-1的位置。

有效数据的范围是front--rear-1,只要不为空,即front和rear指向位置不同时就可以删除。

同时入队的时候要保证队列不满。

在用数组模拟实现时,rear指向k位置时,arr[rear]=x;此时rear+1会越界,可以采用(rear+1)%(k+1),让rear回到0位置。判断是否队满时也要这样操作。

同时取尾元素时,要得到arr[rear-1],如果此时rear为0,就会导致越界,要让它经过处理变为k位置。而其它情况下保持不变仍为rear-1,可用(rear+k)% (k+1)n=0时,得到k,n=k时得到k-1


 

你可能感兴趣的:(数据结构-C,数据结构,c语言,算法)