提到队列这个词,或许你不会感到陌生,在我们的生活中,应用到队列这个概念的场景非常之多。我们日常的排队买饭,总是第一个到达窗口的人先买到然后离开,后来的人总是后来离去;再有,去医院挂号排队,也总是遵循一般的先来先就医服务的原则。计算机中的队列数据结构的设计,也是为了更好地解决这类先来先服务问题的。(更好的阅读体验,请访问程序员在旅途) 队列是一种采用先来先服务(First in First Out,FIFO)思想的抽象数据结构。和栈一样,它也是一种受限制的线性表。队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。允许插入的一端称为队尾,允许删除的一端称为队头。队列的插入操作称之为入队列或进队列,队列的删除操作称之为退队列或出队列。当线性表中没有元素的时称为空队列。示意图如下: 在队列上的基本操作有:①队列初始化②判断队空③入队操作④出队操作⑤读取队头元素⑥销毁队列。根据在物理实现方式上的不同,分为顺序队列和链式队列,基于数组实现的顺序存储的队列叫作顺序队列,基于链表实现的队列叫作链式队列。
采用顺序存储的队列称为顺序队列,在使用队列前,要分配一块连续的存储空间来存放队列里面的元素。由于队列的头尾都是会变化移动的,因此,就有队头队尾两个指示变量,指向队列的头尾。顺序队列的类型定义如下:
#define MAXSIZE 100 typedef struct{ ElementType data[MAXSIZE]; int front,rear; }SeqQuene,* PSeqQueue;
定义一个指向队列的指针:
PSeqQueue queue = (PSeqQueue)malloc(sizeof(SeqQuene));从队列的类型定义上可以看到,队列的数据区为 queue->data[0] ~ queue->data[MAXSIZE-1]。队头变量queue->front取值范围(0<= queue->front <=MAXSIZE-1),队尾变量queue->rear取值范围(0<= queue->rear <=MAXSIZE-1)。 队列是使用数组进行元素的存储,它在内存分配上是静态分配,一旦初始化之后,内存空间的大小就确定了。而队列的操作是一个动态的过程,随着入队的进行,queue->rear的值会超过MAXSIZE,随着出队的进行,queue->front也会逐渐增加,就会导致数组空间实际上还有空闲的空间,但是不能进行元素的入队了,从而出现假溢出的现象。为了解决这种假溢出的问题,可以采用一种称为"循环队列"的特殊队列,循环队列是将队列的数据区data[0…MAXSIZE-1]看成头尾相接的循环结构,头尾的指示变量front、rear关系不变。循环队列的示意图如下: 对这种首尾相接的循环队列结构来说,进队出队的操作会有一些小的修改,以满足循环的需求。
入队: queue->rear = (queue->rear + 1) % MAXSIZE; 出队: queue->front = (queue->front +1) % MAXSIZE;
了解了入队和出队的操作,我们需要判断,什么时候队列满了,什么时候队列为空。空的情况很好判断,只要 queue->front = queue->rear就可以认为队列为空。队满的情况的讨论,当队列中元素逐渐增多,queue->rear 会最终追上queue->front的,这时候两者也是相等,但此时队列非空,相反却是队满的情况,为了方便判断队列的队满队空情况,可以采用少用一个元素空间的方法,使得rear永远追不上front,也就是queue->front所指的位置不用,当 (rear+1)%MAXSIZE = front 的时候,队列是满的。
队列在使用之前需要进行初始化。初始化的过程包括分配队列需要占用的内存空间,以及给队头队尾变量赋初值。
//初始化队列 PSeqQueue init_seqqueue(){ //申请队列需要的内存空间 PSeqQueue queue = (PSeqQueue)malloc(sizeof(SeqQueue)); if(queue){ queue->front = 0; queue->rear = 0; } return queue; }
在出队或者获取队首元素的时候,都要进行队列非空判断
//判断队列是否为空。1代表空,0代表非空 int empty_seqqueue(PSeqQueue queue){ if(queue && (queue->front == queue->rear)){ return 1; }else{ return 0; } }
int in_seqqueue(PSeqQueue queue , ElementType x){ //判断队列是否满了 if((queue->rear+1)%MAXSIZE == queue->front){ printf(“队列满了”); return 0; }else{ //入队列. queue->rear = (queue->rear +1) % MAXSIZE; queue->data[queue->rear] = x; return 1; } }
int out_seqqueue(PSeqQueue queue , ElementType *x){ //出队列的时候要判断队列是否为空 if(empty_seqqueue(queue)){ printf(“队列为空,不可出队”); return 0; }else{ //由于是循环队列,queue->front指示位置的元素不用,因此要+1才是要出队的元素 queue->front = (queue->front + 1) % MAXSIZE; *x = queue->data[queue->front]; return 1; } }
int getFront_seqqueue(PSeqQueue queue , ElementType *x){ //出队列的时候要判断队列是否为空 if(empty_seqqueue(queue)){ printf(“队列为空,不可获取队头元素”); return 0; }else{ //由于是循环队列,queue->front指示位置的元素不用,因此要+1才是队头的元素 *x = queue->data[(queue->front+1)%MAXSIZE]; return 1; } }
由于是动态申请的空间,因此,在使用完成之后,要进行内存的释放。
void destory_seqqueue(PSeqQueue *queue){ if(*queue){ free(*queue); } *queue = NULL; }
队列是线性结构的一种,因此,也可以用链式结构的方式来实现。并且,链式结构的队列,由于节点空间都是在入队的时候动态申请的 ,因此,在计算机内存空间足够的情况下,一般不需要考虑队满的情况,也就不存在溢出的情况,所以不需要使用循环链式队列来处理假溢出的情况。 链式队列示意图如下: 链队节点的结构和单链表一样,同时为了操作上的方便,可以重新定义一个链队的结构体,包含有头尾指针指向队列的头部和尾部。链队的结构体描述如下:
//节点结构 typedef struct node{ ElementType data; struct node *next; }Qnode,*PQnode; //链栈结构 typedef struct { PQnode front,rear; }LinkQueue,*PLinkqueue;
定义一个指向链队的指针
PLinkqueue queue = (PLinkqueue)malloc(sizeof(LinkQueue));在使用链表来存储队列数据的时候,带不带头结点完全看我们的需求,如果在操作上需要使用头结点 来方便统一操作,就是用头结点 ,否则就可以不用加头结点。 queue->front指向链队的队头元素,queue->rear指向链队的队尾元素。出队的时候删除链表的首元结点,让后继节点成为首元节点,然后修改队头指针使其指向新的首元节点即可,入队是在链表末尾插入元素,然后修改队尾指针指向新的链尾即可。
队列的初始化就是为队列结构分配内存空间,同时给front、rear指针置空值,为入队列做准备。
PLinkqueue init_linkqueue(){ PLinkqueue queue = (PLinkqueue)malloc(sizeof(LinkQueue)); if(queue){ queue->front = NULL; queue->rear = NULL; } return queue; }
//判断链队为空;1空队列,0非空。 int empty_linkqueue(PLinkqueue queue){ if(queue && (queue->front == NULL) && (queue->rear ==NULL)){ return 1; }else{ return 0; } }
链式队列的入队,就是在链表的链尾插入元素,然后让队尾指针指向链尾元素。
int in_linkqueue(PLinkqueue queue, ElementType x){ PQnode p = (PQnode)malloc(sizeof(Qnode)); if(!p){ printf(“内存溢出,不能申请新的节点空间\n”); return 0; } p->data = x; p->next = NULL; //要先判断队列中是否有元素,两者的的入队是有一些差别的 if(empty_linkqueue(queue)){ queue->rear = p; queue->front = p; }else{ //入队的时候,在队尾插入元素 queue->rear->next = p; queue->rear = p; } return 1; }
链式队列的出队,就是将链表的首元节点从链表中删去,让其后继节点成为首元节点,然后让队头指针指针指向该节点。
int out_linkqueue(PLinkqueue queue, ElementType *x){ if(empty_linkqueue(queue)){ printf(“队空,无法出队\n”); return 0; } //出队首元素节点 *x = queue->front->data; PQnode temp_front= queue->front; //队首的下一个元素作为队首 queue->front = temp_front->next; //释放出队节点 free(temp_front); //如果队首为空,说明此时队列中没有元素节点了,则设置队尾也为空。 if(!queue->front){ queue->rear = NULL; } return 1; }
即获取front指针指向的节点。
int getFront_linkqueue(PLinkqueue queue, ElementType *x){ if(empty_linkqueue(queue)){ printf(“队空,无法获取队首 元素\n”); return 0; } *x = queue->front->data; return 1; }
由于动态分配的内存空间 ,在使用完队列之后,要把申请的空间及时的释放掉。具体做法就是遍历单链表,释放每一个节点。
//销毁队列。由于使用的是链表,因此,要释放每一个链表节点的空间 void destory_linkqueue(PLinkqueue *queue){ PQnode p; if(*queue){ while((*queue)->front){ p = (*queue)->front; (*queue)->front = (*queue)->front->next; free(p ); } free(*queue); } *queue = NULL; }
有n个元素存储在数组A[n]中,设计一个算法,实现将这n个元素循环向左移动k(0<k<n)位。 思路分析:将数组的A[0] ~ A[k-1]元素先顺序放入一个队列中,然后再将数组A[k] ~A[n-1]元素依次左移k位,然后再将队列中保存的数组元素A[0] ~ A[k-1]顺序出队列,依次放入A[n-k] ~A[n-1]的位置。具体的算法如下:
void array_leftcircle_move(int a[], int n, int k){ int i; PLinkqueue queue = init_linkqueue(); for(i=0;i<k;i++){ in_linkqueue(queue,a[i]); } for(i=k;i<n;i++){ a[i-k] = a[i]; } i = n-k; while(!empty_linkqueue(queue)){ out_linkqueue(queue,&a[i]); i++; } }
完整的程序源码如下:
#include<stdio.h> #include<stdlib.h> typedef int ElementType; //节点结构 typedef struct node{ ElementType data; struct node *next; }Qnode,*PQnode; //链栈结构 typedef struct { PQnode front, rear; }LinkQueue,*PLinkqueue; //链队初始化 PLinkqueue init_linkqueue(){ PLinkqueue queue = (PLinkqueue)malloc(sizeof(LinkQueue)); if(queue){ queue->front = NULL; queue->rear = NULL; } return queue; } //判断链队为空;1空队列,0非空 int empty_linkqueue(PLinkqueue queue){ if(queue && (queue->front==NULL) && (queue->rear==NULL)){ return 1; }else{ return 0; } } //入队列 int in_linkqueue(PLinkqueue queue, ElementType x){ PQnode p = (PQnode)malloc(sizeof(Qnode)); if(!p){ printf("内存溢出,不能申请新的节点空间\n"); return 0; } p->data = x; p->next = NULL; //要先判断队列中是否有元素,两者的的入队是有一些差别的 if(empty_linkqueue(queue)){ queue->rear = p; queue->front = p; }else{ //入队的时候,在队尾插入元素 queue->rear->next = p; queue->rear = p; } return 1; } //出队 int out_linkqueue(PLinkqueue queue, ElementType *x){ if(empty_linkqueue(queue)){ printf("队空,无法出队\n"); return 0; } //出队首元素节点 *x = queue->front->data; PQnode temp_front= queue->front; //队首的下一个元素作为队首 queue->front = temp_front->next; //释放出队节点 free(temp_front); //如果队首为空,说明此时队列中没有元素节点了,则设置队尾也为空。 if(!queue->front){ queue->rear = NULL; } return 1; } //读取队首元素 int getFront_linkqueue(PLinkqueue queue, ElementType *x){ if(empty_linkqueue(queue)){ printf("队空,无法获取队首 元素\n"); return 0; } *x = queue->front->data; return 1; } //销毁队列。由于使用的是链表,因此,要释放每一个链表节点的空间 void destory_linkqueue(PLinkqueue *queue){ PQnode p; if(*queue){ while((*queue)->front){ p = (*queue)->front; (*queue)->front = (*queue)->front->next; free(p); } free(*queue); } *queue = NULL; } void array_leftcircle_move(int a[], int n, int k){ int i; PLinkqueue queue = init_linkqueue(); for(i=0;i<k;i++){ in_linkqueue(queue,a[i]); } for(i=k;i<n;i++){ a[i-k] = a[i]; } i = n-k; while(!empty_linkqueue(queue)){ out_linkqueue(queue,&a[i]); i++; } } int main(){ int a[10], i, k = 3; for(i=0;i<10;i++){ a[i] = i+1; } array_leftcircle_move(a,10,k); for(i=0;i<10;i++){ printf("%d ",a[i]); } printf("\n"); return 0; }