【数据结构】4.1队列Queue

在前一节的学习中,我们学习了线性表的一种特殊形式: 这一节我们会学习线性表的另一种特殊形式 : 队列

什么是队列

队列是一种特殊的线性表,它的特点就和它的名字一致:

排队! 

第一个进来的,第一个出去;最后一个进来的,最后一个出去

FIRST-IN-FIRST-OUT

在栈中,我们需要一个top来指向栈顶,但是在队列中,需要两个指针 front头指针和rear尾指针

入队只能从队列尾部入,出队只能从队列头部出


队列的顺序表示 

1.顺序表示会出现的问题

front指针永远指向第一个元素,rear指针永远指向最后一个元素的下一个结点; 

最开始队列为空时front = rear = 0;

有元素入队 rear++; 有元素出队head++;

很快你就会发现一个问题 有元素入队出队head 和 rear指针都在向后移动,如果元素空间是一定的,那前面的空间不就被浪费了吗?可以看到最后一副图,rear已经指向空,但实际上数组中有空位置,但只能又申请新的空间来储存新的元素,随着出栈入栈的元素越来越多,造成的空间浪费就会越来越多。

2.解决方案:循环队列

有两种解决方法:

(1)在每一次有元素出列时,前面都会空出一个新的位置,将所有的元素都往前移一个位置;

但是如果队列中元素很多,那移动次数就会很多,算法效率太低;

(2)循环队列(Circular queue)

队列的首元素和末尾的元素连在一起

有人会问:数组的头和尾不能连接在一起呀! 而且如果front一直加,那最终就会超过队列的最大容量呀!

这里的循环队列不是真的让数组的头和尾连在一起,而是通过一种运算,实现空间的循环利用:

而这种运算 就是取余

1.入队时,rear  = (rear + 1) % MAXISIZE 

2.出队时,front = (front + 1)%MAXISIZE

使用模运算,可以使rear  和 front的值始终保持在数组范围内,实现空间的循环利用


但是还是有一个问题 当rear == front时,我们怎么知道队列到底是空还是满呢?

 我们可以少用一个存储空间:

当队列为空时: front == rear;

当队列为满时, (rear+1)%MAXISIZE == front;

队列中的元素有多少个呢?

即问即答: rear - front

但是这个答案是错误的! 再仔细看看上面的那副图 你会发现 rear 有时候会比 front小

正确的答案应该是|rear-front| ,那转换成表达式就是 (rear - front + m) %m

假设 m = 5front = 3rear = 1

  • 直接计算:rear - front = 1 - 3 = -2
  • 使用模运算:(rear - front + m) % m = (-2 + 5) % 5 = 3 % 5 = 3

 3.基本操作

队列的定义

typedef struct {
	int* base;
	int rear;
	int front;
}cqueue;

队列的初始化

void initQueue(cqueue* cq) {
	//申请INITSIZE个空间
	cq->base = (int*)malloc(INITSIZE * sizeof(int));
	if (!cq) exit(1);
	cq->front = cq->rear = 0;
}

入队

int enQueue(cqueue* cq, int x) {
	//队列已经满了,需要扩展队列长度!
	if ((cq->rear + 1) % cq->capacity == cq->front) {
		cq->base = (int*)realloc(cq->base, (cq->capacity + 1) * sizeof(int));
		if (!cq->base) exit(1);
		cq->capacity++;
	}
	cq->base[cq->rear] = x;
	//更新rear
	cq->rear = (cq->rear + 1) % cq->capacity;
	return 1;
}

出队

int outQueue(cqueue* cq, int* x) {
	//队列为空
	if (cq->front == cq->rear) return 0;
	//带回出队的值
	*x = cq->base[cq->front];
	//更新front
	cq->front = (cq->front + 1) % cq->capacity;
	return 1;
}

打印队列

void printQueue(cqueue* cq) {
	int m; //遍历队列
	m = cq->front;
	while (m != cq->rear) {
		printf("%d ", cq->base[m]);
		m = (m + 1) % cq->capacity;
	}

}

求队列长度

int getLen(cqueue* cq) {
	return ((cq->rear - cq->front + cq->capacity) % cq->capacity);
}

获取队列头部元素

int getFront(cqueue* cq,int*x) {
	//检查是否为空
	if (cq->front == cq->rear) return 0;
	*x = cq->base[cq->front];
	return 1;
}

队列的链式表示

链队列只需要用一个单链表和一个结构体去储存rear和front指针

要注意front指针是指向头结点,rear指针是指向尾结点

这里我会在学习中产生一个疑问 为什么需要单独用一个结构体去储存rear和front指针,不能用两个单独的指针直接指向这个链队列?

在书写了一遍方法后,我内心有了一个答案: 如果没有rear和front指针,在做入队和出队操作时,每次都需要遍历一遍链表找到首元素和尾元素,就会比较麻烦

基本操作 

1.定义

需要定义一个结构体储存rear和front指针,链队列的结点

typedef struct node {
	struct node* next;
	int data;
}qlink;
typedef struct {
	qlink* front;
	qlink* rear;
}linkQueue;

2.初始化

void initQueue(linkQueue* Q) {
	Q->front = Q->rear =  (qlink*)malloc(sizeof(qlink));
	if (!Q->front ||!Q->rear) exit(1);
	Q->front->next = NULL;
}

3.入队

void enQueue(linkQueue* Q,int x) {
	//不存在满队列的情况
	qlink* m;
	m = (qlink*)malloc(sizeof(qlink));
	m->data = x;
	m->next = NULL;
	if (!m) exit(1);
	Q->rear->next = m;
    //要记得修改尾指针
	Q->rear = m;
}

4.出队

int outQueue(linkQueue* Q, int* x) {
	if (Q->rear == Q->front) return 0; //队列为空
	qlink* p = Q->front->next;   //要注意Q的front是指向头结点而不是有效结点
	*x = p->data;
    //出队修改头指针
	Q->front->next = p->next;  
	if (Q->rear == p) Q->rear = Q->front; //如果队列中只有一个结点
	                                      //删除后队列为空,要重置rear
	free(p);
	return 1;
}

5.获取长度

int getLen(linkQueue* Q) {
	int x = 0;
	qlink* m = Q->front->next; //从有效结点开始
	while (m) {
		m = m->next;
		x++;
	}
	return x;
}

还有其他的一些诸如打印队列(和获取长度方法差不多,遍历一遍队列即可),获取首元素都比较简单,这里就不介绍了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

因兹菜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值