数据结构--队列

  我们之前提到过关于栈的相关知识以及操作了,现在我们学习在数据结构中与栈相类似的另一种结构---队列。

目录

一、什么是队列?

二、队列的实现及基本操作

3.1 结构体定义

3.2 初始化队列及销毁队列

3.3入队列

3.4出队列

3.5其他操作

四、关于循环队列

4.1循环队列如何判空?

五、小结


一、什么是队列?

与我们在生活中吃火锅时排队的方式相同,队列是一种先进先出的结构,同样是操作受限的线性表,在队列中只允许一端进,一端出,允许入队的方向称为队尾,允许出队的就是队头。

如上图所示,可以清晰的看到队列的形式,一端进,一端出。 

二、队列的实现及基本操作

首先我们来思考一下,队列适合用什么结构来实现呢?

介于队头队尾这种特殊的方式,我们考虑到如果使用数组这种方式,无疑会给我们增加很大的开销,所以还是使用链表实现最优。

3.1 结构体定义

在结构体的定义中,我们使用的是链表的方式,定义如下:

typedef struct QListNode{
struct QListNode* next;
QDataType val;
}QNode;

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

首先我们需要一个QListNode结构体来帮助我们实现链表的形式,其次,为了更好的利用上front和rear指针,我们可以再定义一个结构体用来存放,同时为了避免后期统计数据个数时重复遍历链表,我们增设size来随时记录队列长度。

3.2 初始化队列及销毁队列

函数声明:


// 初始化队列 
void QueueInit(Queue* q);
// 销毁队列 
void QueueDestroy(Queue* q);

函数实现:有了前面栈的铺垫相信这里也不是很困难,通通置空,通通销毁。

void QueueInit(Queue* q){
assert(q);
q->size = 0;
q->rear = q->front = NULL;
}

值得一提的是,在销毁队列时,销毁队列里的每个结点,他们占据了大量的空间。 

// 销毁队列 
void QueueDestroy(Queue* q) {
	assert(q);
	QNode* cur = q->front;
	while (cur) {
		QNode* node = cur->next;
		free(cur);
		cur = node;
	}
	q->front = q->rear = q->size = NULL;
}

3.3入队列

因为我们首先将rear指针是置空的,所以我们在准备插入时一定要检查rear是否为空,否则队列根本就连不上,像断了线的风筝一样嘤嘤嘤~~。同时也不要忘记了后面的size++;

// 队尾入队列 
void QueuePush(Queue* q, QDataType data) {
	assert(q);
	QNode* cur = (QDataType*)malloc(sizeof(QDataType));
	if (cur == NULL) {
		perror("malloc fail");
		return;
	}
	cur->next = NULL;
	cur->data = data;
	if (q->rear == NULL) {
		q->rear = q->front = cur;
	}
	else {
		q->rear->next = cur;
		q->rear = cur;
	}
	q->size++;
}

3.4出队列

出队列就非常简单了,首先记得判断q->size,为了防止里面是空的,然后就是修改头指针指向下一个节点即可,实现如下:

// 队头出队列 
void QueuePop(Queue* q) {
	assert(q);
	assert(q->size > 0);
	QNode* cur = q->front->next;
	free(q->front);
	q->front = cur;
	if (q->front == NULL)
		q->rear = NULL;
	q->size--;
}

3.5其他操作

除上述几种操作外,还有获取队头队尾元素,队列判空等问题,都非常简单。

// 获取队列头部元素 
QDataType QueueFront(Queue* q) {
	assert(q);
	assert(q->front);
	return q->front->data;
}
// 获取队列队尾元素 
QDataType QueueBack(Queue* q) {
	assert(q);
	assert(q->rear);
	return q->rear->data;
}
// 获取队列中有效元素个数 
int QueueSize(Queue* q) {
	assert(q);
	return q->size;
}
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue* q)
{
	assert(q);
	return q->front == q->rear;
}

四、关于循环队列

循环队列顾名思义这个队列是连起来的环状队列,大致形状如下图所示:

看起来就像甜甜圈一样,循环队列我们一般用数组实现,因为循环队列一般有一个限定长度,使用链表可能无法简单实现出队入队的操作,十分麻烦,所以采用数组实现。

4.1循环队列如何判空?

在空队列时,rear和front 的位置如下图所示,是一起重叠在相当于是数组0下标的位置的。

当队列中开始进元素时,rear指针会向前推进,出元素时front指针会向前推进,下面是循环队列入队五次,出对两次时的示意图:

 

当队列满了时,front和rear指针再次相遇 

那么问题来了,既然队列为空和队列为满时的条件都是Q.rear = Q.front,什么时候才是队满,什么时候才是队空呢?

为了解决这个问题,我们采用了多种方式来区分队满和队空。

1.牺牲一个存储单元来区分队空还是队满,入队时少用一个存储单元,这也是比较常用的做法,此时队满的条件就变成了(Q.rear + 1 )%MaxSize == Q.front,队中的元素个数为                     (Q.rear-Q.front+MaxSize)%MaxSize

2.类型中增设size数据成员表示元素个数,删除成功size-1,插入成功size+1,采用这种方式队满的条件为Q.size == MaxSize.

五、小结

总体来说,栈和队列这里的实现是比较简单,但是学到后面可能会要求用栈或者队列来实现某个操作,因此栈和队列的基础一定要牢固,Ok我们下次再见。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值