数据结构—循环队列与链队列
我们上一节已经提到过
栈和队列都是对插入和删除数据元素有具体要求的线性表
这一节我们就要介绍另一种数据结构:队列。
队列:是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
我们还是先举个例子,其实队列在我们的平时生活中最长见,食堂里排队打饭,后来的肯定是站在队伍最后,然后前面的人打好饭之后再离开;不可能后来的人站在最前面,这样就没人愿意去排队了。这其实就类似于队列的实现方式。
队列是一种先进先出的线性表。允许插入的一段叫队尾,允许删除的一端称为队头。
由于最普通的顺序结构队列几乎与前面讲过的顺序结构线性表相同,这里我们不再赘述,直接介绍效率更高的循环队列和链队列。
1.循环队列
如果我们使用的是普通顺序结构的队列,将数组下标为0的一端设为队头,最后一个数组元素的下标设为队尾,那么一个数据入队时,就只需要在队尾增加数组元素就可以。但是如果一个数据要出队时,在删去第一个数组元素后,后面的数据都要向前移动一位,我们知道这样的话时间复杂度会为O(n)。所以我们要想出一个更好的办法来解决时间效率低的问题。
队头可不可以不放在第一个数组元素的位置?当然是可以的,我们在一个元素出队后,让对头向后移动一位就可以
那前面出队后留下的空间怎么办呢?会不会造成浪费?我们可以这样做:在队尾到达数组最大限制后,让队尾回到第一个数组元素的位置,再向后推。
这样做的话就会出现对头在队尾前 和 队尾在对头前两种情况。问题又来了,如果表头表尾在同一位置时表示空队列的话,那么什么时候表示队列已经满了呢?下面是两种方法:
- 设置一个标志变量flag,当front == rear,且flag = 0时队列为空,当front == rear,且flag = 1时队列为满。
- 当队列空时,front == rear,当队列满时,我们修改其条件,保留一个元素空间。也就是说当队列满时,数组中还有一个空闲单元。
几个重要判断条件:
队空:front == rear
队满:(rear +1)%MAX = front
队列长度:(rear - front +MAX)%MAX
我们将要用到的是第二种方法。
以下是循环队列的代码实现:
#include<stdio.h>
#include<stdlib.h>
#define MAX 20
typedef struct{
int date[MAX];
int front;
int rear;
}SqQueue;
SqQueue *InitQueue(SqQueue *queue){
queue = (SqQueue *)malloc(sizeof(SqQueue));
queue->front = 0;
queue->rear = 0;
return queue;
}
void Enqueue(SqQueue *queue){
if((queue->rear+1) % MAX == queue->front){
printf("队列已满!\n");
return;
}
printf("请输入入队数据:");
scanf("%d",&queue->date[queue->rear]);
queue->rear = (queue->rear+1) % MAX;
printf("入队成功!\n");
}
void Dequeue(SqQueue *queue){
if(queue->front == queue->rear){
printf("队列为空!\n");
return;
}
printf("你要删除的数据元素为:%d",queue->date[queue->front]);
queue->front = (queue->front+1) % MAX;
printf("出队成功!\n");
}
void print_queue(SqQueue *queue){
if(queue->front == queue->rear){
printf("目前队列为空!\n");
return;
}
printf("目前队列中有%d个数据元素\n",(queue->rear-queue->front+MAX)%MAX);
if(queue->front < queue->rear){
int index = 1;
for(int i = queue->front; i <queue->rear; i++){
printf("第%d个数据为:",index++);
printf("%d\n",queue->date[i]);
}
}
else if(queue->front > queue->rear){
int index = 1;
int i;
for(i = queue->front; i < MAX; i++){
printf("第%d个数据为:",index++);
printf("%d\n",queue->date[i]);
}
for(i = 0; i < queue->rear; i++){
printf("第%d个数据为:",index++);
printf("%d\n",queue->date[i]);
}
}
}
int main(){
SqQueue *queue;
queue = InitQueue(queue);
int choice;
while(1){
printf("1.入队\n");
printf("2.出队\n");
printf("3.输出\n");
scanf("%d",&choice);
switch(choice){
case 1:Enqueue(queue);
break;
case 2:Dequeue(queue);
break;
case 3:print_queue(queue);
break;
}
}
return 0;
}
2.链队列
队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已,我们把它称为链队列。
当链队列为空时,front和rear都指向单链表的头节点。当有数据入队时,在链尾插入新节点,rear随之向后移动,当删除节点时,删除头节点后的第一个数据节点,注意:当删除的节点是链表中唯一一个数据节点时,删除之后链表中只留下了一个头节点,这时要让rear指向头节点。
以下是链队列的代码实现:
#include<stdio.h>
#include<stdlib.h>
typedef struct Qnode{
int date;
struct Qnode *next;
}Qnode,*ptr;
typedef struct{
ptr front;
ptr rear;
int count;
}linkqueue;
linkqueue *Initqueue(linkqueue *queue){
queue = (linkqueue *)malloc(sizeof(linkqueue));
ptr pHead = (ptr)malloc(sizeof(Qnode));
pHead->next = NULL;
queue->front = pHead;
queue->rear = pHead;
queue->count = 0;
return queue;
}
void Enqueue(linkqueue *queue){
ptr pnew = (ptr)malloc(sizeof(Qnode));
printf("请输入入队数据:");
scanf("%d",&pnew->date);
pnew->next = NULL;
queue->rear->next = pnew;
queue->rear = pnew;
queue->count++;
printf("入队成功!\n");
}
void Dequeue(linkqueue *queue){
if(queue->front == queue->rear){
printf("队列目前为空!\n");
return;
}
ptr temp = queue->front->next;
printf("你要删除的数据为:%d",temp->date);
queue->front->next = temp->next;
if(queue->rear == temp)
queue->rear = queue->front;
free(temp);
queue->count--;
printf("出队成功!\n");
}
void print_queue(linkqueue *queue){
if(queue->front == queue->rear){
printf("目前为空队列!\n");
return;
}
printf("目前队列中共有%d个数据\n",queue->count);
int index = 1;
ptr temp = queue->front->next;
while(temp != NULL){
printf("第%d个数据:",index++);
printf("%d\n",temp->date);
temp = temp->next;
}
}
int main(){
linkqueue *queue;
queue = Initqueue(queue);
int choice;
while(1){
printf("1.入队\n");
printf("2.出队\n");
printf("3.输出\n");
scanf("%d",&choice);
switch(choice){
case 1:Enqueue(queue);
break;
case 2:Dequeue(queue);
break;
case 3:print_queue(queue);
break;
}
}
return 0;
}