队列
一种重要的数据结构。它常常和栈放在一起作比较,因为一个是先进先出,另一个是后进先出。
什么意思呢?就好比你去排队买票,那你越早排,也就越早买到票。这就像队列结构。也就是’‘先进先出’’。
怎么实现呢?可能你会说做一个数组,一个队头指针,一个队尾指针。
就像下面这样:
没错,这就是一个队列了。
但是,一般队列不这么做,为什么?
比如我们有了新元素要入队,又有元素要出对,怎么办?
那应该是下图这样:
看起来好像没什么毛病,可是一直这么出队,入队,再出队,再入队,结果会怎样呢?
如下图:
那么总有一天,队头指针会走到队尾。
也就是说,终有一天,队伍有效长度会为零。
如下图:
可能你要说,那每次出队后让所有元素前移一位不行?
可以是可以,但这就增加了代码的时间复杂度,是冗余的开销。
当然,理论上你还可以让队伍足够长,但这也是不现实,不合理的。
那怎么解决这个问题呢?
这就是我们经常用到的:
循环队列
换言之,一般队列的弊端在于,如果不进行前移操作,那么队列队头指针终会到头。
那如果我们的队列没有队尾呢?
没错,循环队列就是一个‘‘环’’,当然,这是逻辑结构,实际的物理存储方式还是和数组一样的。
如下图:
比如我们有元素要入对:
比如我们有元素出队:
我们可以随性所欲的进行入队,出队操作,而不必担心数组长度了。
而无论是时间复杂度,还是空间复杂度,我们都没有多余的增长。
循环队列(顺序存储)
初始设定:
#include<stdio.h>
#define MAXSIZE 20 //存储空间初始分配量
typedef int QElemType; //类型根据情况而定,这里设为int
typedef enum{ False = 0, True = 1 }Bool;
结构体:
typedef struct
{//循环队列的顺序存储结构
QElemType data[MAXSIZE];
int front; //头指针
int rear; //尾指针,若队列不空,指向队列尾元素的下一个位置
}SqQueue;
基本操作:
访问:
Bool visit(QElemType c)
{//访问输出
printf("%d ", c);
return True;
}
遍历:
Bool QueueTraverse(SqQueue* Q)
{//队列遍历
int i;
i = Q->front;
while (i != Q->rear)
{//遍历队列
visit(Q->data[i]);//访问输出
i = (i + 1) % MAXSIZE;//下标i每次加一
}
puts("");
return True;
}
初始化:
Bool InitQueue(SqQueue* Q)
{//初始化一个空队列Q
Q->front = 0;
Q->rear = 0;
return True;
}
是否为空:
Bool QueueEmpty(SqQueue* Q)
{/* 若队列Q为空队列,则返回True,否则返回False */
if (Q->front == Q->rear) /* 队列空的标志 */
return True;
else
return False;
}
返回长度:
int QueueLength(SqQueue* Q)
{/* 返回Q的元素个数,也就是队列的当前长度 */
return (Q->rear - Q->front + MAXSIZE) % MAXSIZE;
}
入队:
Bool EnQueue(SqQueue* Q, QElemType e)
{
if ((Q->rear + 1) % MAXSIZE == Q->front)/* 队列满的判断 */
return False;
Q->data[Q->rear] = e;/* 将元素e赋值给队尾 */
Q->rear = (Q->rear + 1) % MAXSIZE;/* rear指针向后移一位置 */
return True;
}
出队:
Bool DeQueue(SqQueue* Q, QElemType *e)
{
if (Q->front == Q->rear)/* 队列空的判断 */
return False;
*e = Q->data[Q->front];//将队头元素赋给e
Q->front = (Q->front + 1) % MAXSIZE;/* front指针向后移一位置 */
return True;
}
谢观~
再加一句,这儿有一个我在栈那一篇就介绍过的一个网站,可以实现队列的可视化操作:
https://visualgo.net/zh/list
(在网页上面菜单栏选择队列)
(:з)∠)