数据结构与算法学习笔记——队列

本文详细介绍了队列的概念,包括其先进先出的特性,并使用C语言实现了链式存储和顺序存储的队列。链队列通过头结点简化了操作,而顺序存储的队列则利用循环结构解决了空间利用率问题。文中提供了队列的基本操作如初始化、销毁、清空、判断队列状态、获取队头元素、入队、出队以及遍历的函数实现。
摘要由CSDN通过智能技术生成

数据结构与算法学习笔记(C语言)

1.定义:队列是一种先进先出(FIFO)的线性表,它只允许从一端插入,而在另一端删除删除元素。

这和我们日常生活中的排队是一样的,排在第一个的人先办事,后来的人排在队尾。

在队列中,允许插入的一段叫队尾(rear),允许删除的一端叫队头(front)。
插入元素到队尾叫入队,删除队头的元素叫出队。

队列的抽象数据类型定义:

ADT Queue {
	数据对象:D = {a1, a2, a3...}
	数据关系:R = {<a1, a2>,<a2, a3>...}
	基本操作:
	InitQueue(*Q) /*初始化队列*/
	DestroyQueue(*Q) /*销毁队列*/
	ClearQueue(*Q) /*清空队列*/
	QueueEmpty(Q) /*若队列为空,返回true,否则返回false*/
	QueueLength(Q) /*返回队列元素的个数*/
	GetHead(Q, *e) /*获取队头元素*/
	EnQueue(*Q, e) /*入队,元素插入队尾*/
	DeQueue(*Q, *e) /*出队,删除队头元素*/
	QueueTraverse(Q) /*遍历队列*/
}ADT Queue

2.队列的链式存储

一个链队列需要两个分别指示队头和队尾的指针唯一确定,为了操作方便,我们也给链队列添加一个头结点
如果没有头节点,那么初始化之后,入队第一个元素的时候,要把头指针指向新添加的节点,出队最后一个元素的时候,又和出队其他节点的操作不一样
链队列
链队列的C语言描述

typedef struct QNode {
	QElemType data;
	struct QNode *next;
}QNode, *QueuePtr;
typedef struct {
	QueuePtr front; /*队头指针*/
	QueuePtr rear; /*队尾指针*/
}LinkQueue;

基本操作的函数原型说明

Status InitQueue(LinkQueue *Q) /*构建一个空队列*/
Status DestroyQueue(LinkQueue *Q) /*销毁队列*/
Status ClearQueue(LinkQueue *Q) /*清空队列*/
bool QueueEmpty(LinkQueue Q) /*返回链队列是否为空*/
int QueueLength(LinkQueue Q) /*返回元素的个数*/
Status GetHead(LinkQueue Q, QElemType *e) /*获取队头元素*/
Status EnQueue(LinkQueue *Q, QElemType e) /*插入元素e为队尾元素*/
Status DeQueue(LinkQueue *Q, QElemType *e) /*队头元素出队*/
void QueueTraverse(LinkQueue Q) /*遍历队列元素并打印*/

1.队列初始化

Status InitQueue(LinkQueue *Q) 
{
	Q->front = (QueuePtr)malloc(sizeof(QNode));
	if (!Q->front) exit(OVERFLOW);
	Q->rear = Q->front;
	Q->front->next = NULL;
	return OK;
} 

2.销毁队列

Status DestroyQueue(LinkQueue *Q)
{
	while(Q->front) {
	/*俩指针一前一后,逐节点释放空间*/
		Q->rear = Q->front->next;
		free(Q->front);
		Q->front = Q->rear;
	}
	return OK;
}

3.清空队列

Status ClearQueue(LinkQueue *Q)
{
	QueuePtr p;
	if (Q->front == Q->rear) return OK;
	/*已经是空队列*/
	p = Q->front;
	/*保存头节点的位置*/
	Q->rear = p->next;
	/*从头节点开始释放队列节点的元素,最后只剩头节点*/
	while (Q->rear) {
		Q->front = Q->rear->next;
		free(Q->rear)
		Q->rear = Q->front;
	}
	Q->front = p;
	/*头指针指回头节点*/
	Q->front->next = NULL;
}

4.判断队列是否为空

bool QueueEmpty(LinkQueue Q)
{
	if (Q.front == Q.rear) return true;
	else return false;
}

5.求队列长度

int QueueLength(LinkQueue Q)
{
	int i = 0;
	/*不用担心改变队尾指针的指向,值传递是实参的拷贝,不会改变传入的队列*/
	Q.rear = Q.front->next;
	while(Q.rear) {
		++i;
		Q.rear = Q.rear->next;
	}
	return 0;
}

6.取队头元素

Status GetHead(LinkQueue Q, QElemType *e)
{
	if (Q.front == Q.rear) return ERROR;
	*e = Q.front->next->data;
	return OK;
}

7.入队

Status EnQueue(LinkQueue *Q, QElemType e)
{
	QueuePtr p;
	p = (QueuePtr)malloc(sizeof(QNode));
	if (!p) exit(OVERFLOW);
	p->data = e;
	p->next = NULL;
	Q->rear->next = p;
	Q->rear = p;
	return OK;
}

8.出队

Status DeQueue(LinkQueue *Q, QElemType *e)
{
	QueuePtr p;
	if (Q->rear == Q->front) return ERROR;
	p = Q->front->next;
	*e = p->data;
	Q->front->next = p->next;
	/*当队列中最后一个元素被删除时,让队尾指针指向头节点*/
	if (p == Q->rear) Q->rear = Q->front;
	free(p);
	return OK;
}

9.打印队列中的元素

void QueueTraverse(LinkQueue Q)
{
	Q.rear = Q.front->next;
	while (Q.rear) {
		printf("%c ", Q.rear->data);
	}
	printf("\n");
}

队列的顺序存储
1.定义:用一组地址连续的存储单元依次存放从队列到队尾的元素,附设两个整形变量作为队头和队尾的标记,记录队头、队尾的下标

队列
队列为空时,Q.rear = Q.front = 0;队列满时,Q.rear = MAXSIZE;
但是,这里明显有一个大问题,因为元素出队,Q.front++,最后Q.front前面的存储单元都用不了了,造成队列“假空”,为了解决这个问题,我们定义循环队列

将申请的连续空间假想成环状的,0存储单元和最后一个存储单元首尾相接
循环队列
注意:指针malloc申请的空间一定是地址连续的存储单元,想象成环状是我们在逻辑上这么想,与之相反的就像地球是圆的,我们可以认为汽车是在平地上行驶(不太恰当,但就这样吧)

从图中,可以看出,当循环队列为空时,Q.front = Q.rear = 0;
当循环队列满的时候,在环状空间逻辑上,Q.front 在Q.rear的下一个存储单元
Q.front = (Q.rear + 1) % MAXSIZE;

循环队列的C语言描述

#define MAXSIZE 100 
/*循环队列的最大存储空间,由循环队列的特征,可以知道分配的存储空间不可扩大*/
typedef struct {
	QElemType *base;
	int front;
	int rear;
}SqQueue;

循环队列各种操作的实现
1.初始化队列

Status InitQueue(SqQueue *Q)
{
	Q->base = (QElemType *)malloc(MAXSIZE * sizeof(QElemType));
	if (!Q->base) exit(OVERFLOE);
	Q->front = Q->rear = 0;
	return OK;
}

2.销毁队列

Status DestroyQueue(SqQueue *Q)
{
	Q->front = Q->rear = 0;
	free(Q->base);
	Q->base = NULL; 
	return OK;
}

3.清空队列

Status ClearQueue(SqQueue *Q)
{
	Q->front = Q->rear = 0;
	return OK;
}

4.判断队列是否为空

bool QueueEmpty(SqQueue Q)
{
	if (Q.front == Q.rear) return true;
	else return false;
}

5.返回队列元素的个数

int QueueLength(SqQueue Q)
{
	/*这个式子不难理解,就是就算元素从屁股后面转到前面了,求余就能消除循环的影响而得到真实长度,
	想像钟表指针之间的时差,相减对12取余就是了*/
	return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}

6.取队头元素

Status GetHead(SqQueue Q, QElemType *e)
{
	if (Q.front == Q.rear) return ERROR;
	*e = Q.base[Q.front];
	return OK;
}

7.元素入队

Status EnQueue(SqQueue *Q, QElemType e)
{
	if (Q->front == (Q->rear + 1) % MAXSIZE) return ERROR;
	Q->base[Q->rear] = e;
	Q->rear = (Q->rear + 1) % MAXSIZE;
	/*元素没转过来的时候忽略取余,元素转到前面来后,取余得到就是从0开始的下标*/
	return OK;
}

8.元素出队

Status DeQueue(SqQueue *Q, QElemType *e)
{
	if (Q->front == Q->rear) return ERROR;
	*e = Q->base[Q->front];
	Q->front = (Q->front + 1) % MAXSIZE;
	/*元素没转过来的时候忽略取余,元素转到前面来后,取余得到就是从0开始的下标*/
	return OK;
}

9.遍历队列元素

void QueueTraverse(SqQueue Q)
{
	if (Q.rear == Q.front) return;
	/*循环队列为空,直接返回*/
	else if (Q.rear > Q.front) {
		for (i = Q.front; i < Q.rear; i++) 
			printf("%c ", Q.base[i]);
	}
	else {
	/*如果队尾的下标小于队头的,表示元素从尾部转过来了,有两部分元素需要打印*/
		for (i = Q.front; i < MAXSIZE; i++)
			printf("%c ", Q.base[i]);
		for (i = 0; i < Q.rear; i++)
			printf("%c ", Q.base[i]);
	}
}

队列这部分比较难以理解的可能就是循环队列这一部分了,结合钟表的表盘,理解一下取余对于求真实差值的意义,那么循环队列也不是很难理解,加油!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值