数据结构论坛

首页 » 分类 » 定义 » C语言数据结构之队列
TUhjnbcbe - 2020/12/12 19:06:00
一、队列是什么

队列是一种可以实现“先进先出”的存储结构。

队列通常可以分为两种类型:

一、顺序队列,采用顺序存储,当长度确定时使用。顺序队列又有两种情况:


  ①使用数组存储队列的称为静态顺序队列。


  ②使用动态分配的指针的称为动态顺序队列。

二、链式队列,采用链式存储,长度不确定时使用(由链表实现)。

由于链式队列跟链表差不多,所以在这里只针对循环(环形)队列来说明并实践。循环队列的两个参数:
  ①front,front指向队列的第一个元素。(front==head)
  ②rear,rear指向队列的最后一个有效元素的下一元素。(rear==tail)

队列的两个基本操作:出队和入队。

二、队列的结构

下面是一个循环队列(基于数组实现)的结构图:

三、队列的操作

入队(尾部入队)
  ①将值存入rear所代表的位置。
  ②rear=(rear+1)%数组的长度。出队(头部出队)
  front=(front+1)%数组的长度。队列是否为空
  front和rear的值相等,则该队列就一定为空。队列是否已满

在循环队列中,“队满”和“队空”的条件有可能是相同的,都是front==rear,这种情况下,无法区别是“队满”还是“队空”。

针对这个问题,有3种可能的处理方法:

(1)另设一个标志以区别是“队满”还是“队空”。(即入队/出队前检查是否“队满”/“队空”)

(2)设一个计数器,此时甚至还可以省去一个指针。

(3)少用一个元素空间,即约定队头指针在队尾指针的下一位置时就作为“队满”的标志,即“队满”条件为:(pQueue-rear+1)%MAX_SIZE==pQueue-front。

四、队列的一些问题以及解决办法

从上图可以看出,随着入队、出队的进行,会使整个队列整体向后移动,就会出现上图中的现象:队尾指针已经移到了最后,即队尾出现溢出,无法再进行入队操作,然而实际上,此时队列中还有空闲空间,这种现象称为“假溢出”。

解决“假溢出”的三种办法:

方法一:每次删除队头元素后,把整个队列向前移动一个位置,这样可保证队头元素在存储空间的最前面。但每次删除元素时,都要把表中所有元素向前移动,效率太低。

方法二:当队尾指针出现溢出时,判断队头指针位置,如果前部有空闲空间,则把当前队列整体前移到最前方。这种方法移动元素的次数大为减少。

方法三:将队列看成头尾相接的循环结构,当队尾指针到队尾后,再从队头开始向后指,这样就不需要移动队列元素了,显然,第三种方法最经济、应用最多,这种顺序队列被称为“循环队列”或“环形队列”。

采用了这种头尾相接的循环队列后,入队的队尾指针加1操作及出队的队头指针加1操作必须做相应的修改,以确保下标范围为0~Max_Size-1。对指针进行取模运算,就能使指针到达最大下标位置后回到0,符合“循环”队列的特点。

因此入队时队尾指针加1操作改为:pQueue-tail=(pQueue-tail+1)%MAX_SIZE;

入队时队尾指针加1操作改为:pQueue-head=(pQueue-head+1)%MAX_SIZE;

五、数组实现动态顺序队列

基于数组的动态顺序循环队列的具体实现:

5.1MyQueue.h

#includestdio.h

#defineOK1

#defineERROR0

#defineTRUE1

#defineFALSE0

#defineMAXSIZE11//初始容量

typedefintStatus;

typedefintQElemType;//定义数据类型

//循环队列的顺序存储结构

typedefstruct{

  QElemTypedata[MAXSIZE];

  intfront;//头指针

  intrear;//尾指针,队列非空时,指向队尾元素的下一个位置

}SqQueue;

Statusvisit(QElemTypeitem){

  printf("%d",item);

  returnOK;

}

//初始化空队列

StatusInitQueue(SqQueue*sQ){

  sQ-front=0;

  sQ-rear=0;

  returnOK;

}

//将队列清空

StatusClearQueue(SqQueue*Q){

  Q-front=Q-rear=0;

  returnOK;

}

//判断队列是否为null

StatusQueueEmpty(SqQueueQ){

  if(Q.front==Q.rear)

    returnTRUE;

  else

    returnFALSE;

}

//返回队列中的元素个数

intQueueLength(SqQueueQ){

  return(Q.rear-Q.front+MAXSIZE)%MAXSIZE;

}

//返回队头元素

StatusGetHead(SqQueueQ,QElemType*e){

  if(Q.front==Q.rear)//是否为空队列

    returnERROR;

  *e=Q.data[Q.front];

  returnOK;

}

//在队尾插入元素

StatusEnQueue(SqQueue*Q,QElemTypee){

  if((Q-rear+1)%MAXSIZE==Q-front)//队列已满

    returnERROR;

  Q-data[Q-rear]=e;//插入队尾

  Q-rear=(Q-rear+1)%MAXSIZE;//尾部指针后移,如果到最后则转到头部

  returnOK;

}

//元素出队

StatusDeQueue(SqQueue*Q,QElemType*e){

  if(Q-front==Q-rear)//队列空

    returnERROR;

  *e=Q-data[Q-front];//返回队头元素

  Q-front=(Q-front+1)%MAXSIZE;//队头指针后移,如到最后转到头部

  returnOK;

}

//遍历队列元素

StatusQueueTraverse(SqQueueQ){

  inti=Q.front;

  while((i+Q.front)!=Q.rear){

    visit(Q.data);

    i=(i+1)%MAXSIZE;

  }

  printf("\n");

  returnOK;

}

intmain(){

  inti=0,l;

  QElemTyped;

  SqQueueQ;

  InitQueue(Q);

  //入队10个元素

  for(inti=0;iMAXSIZE-1;i++){

    EnQueue(Q,i);

  }

  QueueTraverse(Q);

  printf("依次出队:");

  for(l=1;l=MAXSIZE;l++)

  {

    DeQueue(Q,d);

    printf("d=%d,",d);

  }

  return0;  

}

六、链表实现动态顺序队列

循环队列要事先申请好空间,整个过程都不能释放,而且要有固定的长度,如果长度事先无法估计,这种方式显然不够灵活;所以就引入了链式存储队列,其实就是线性表的单链表,只是它只能对尾进,队头出。并且规定队头指针指向链队列的头结点,对尾指针指向终端节点,当队列为空时,front和rear都指向头结点。

入队操作,就是在链表尾部插入结点;出队操作就是头结点的后继结点出队,然后将头结点的后继后移。如果最后除了头结点外,只剩一个元素了,就把rear也指向头结点。

#include"stdio.h"

#include"stdlib.h"

#include"io.h"

#include"math.h"

#include"time.h"

#defineOK1

#defineERROR0

#defineTRUE1

#defineFALSE0

#defineMAXSIZE20

typedefintStatus;

typedefintQElemType;

//结点结构

typedefstructQNode{

QElemTypedata;

structQNode*next;

}QNode,*QueuePtr;

//队列的链表结构

typedefstruct{

QueuePtrfront;//队头

QueuePtrrear;//对尾

}LinkQueue;

Statusvisit(QElemTypee)

{

printf("%d",e);

returnOK;

}

//初始化空的队列

StatusInitQueue(LinkQueue*Q){

Q-front=Q-rear=(QueuePtr)malloc(sizeof(QNode));

if(!Q-front)

exit(OVERFLOW);

Q-front-next=NULL;

returnOK;

}

//销毁队列

StatusDestroyQueue(LinkQueue*Q){

while(Q-front){

Q-rear=Q-front-next;//从队头开始销毁

free(Q-front);

Q-front=Q-rear;

}

returnOK;

}

//清空队列,队头指针还在

StatusClearQueue(LinkQueue*Q){

QueuePtrp,q;

Q-rear=Q-front;//跟初始状态相同,Q-rear指向头结点

p=Q-front-next;//开始销毁队头元素,队头,对尾依然保留

Q-front-next=NULL;

while(p){

q=p;

p=p-next;

free(q);

}  

returnOK;

}

//队列是否空

StatusQueueEmpty(LinkQueueQ){

if(Q.front==Q.rear)

returnTRUE;

else

returnFALSE;

}

//取队列长度

intQueueLength(LinkQueueQ){

inti=0;

QueuePtrp=Q.front;

while(Q.rear!=p){

i++;

p=p-next;

}

returni;

}

//获取队头元素

StatusGetHead(LinkQueueQ,QElemType*e){

QueuePtrp;

if(Q.front==Q.rear)//队空

returnERROR;

p=Q.front-next;

*e=p-data;

returnOK;

}

//对尾插入元素

StatusEnQueue(LinkQueue*Q,QElemTypee){

QueuePtrs=(QueuePtr)malloc(sizeof(QNode));

if(!s)

exit(OVERFLOW);

s-data=e;

s-next=NULL;

Q-rear-next=s;//原来对尾的next指向新的元素

Q-rear=s;//将新元素变为对尾

returnOK;

}

//队头元素出队

StatusDeQueue(LinkQueue*Q,QElemType*e){

QueuePtrp;

if(Q-front==Q-rear)

returnERROR;

p=Q-front-next;//p指向队头元素

*e=p-data;

Q-front-next=p-next;//头结点的后继指向队头的下一个元素

if(Q-rear==p){//队头等于对尾了

Q-rear=Q-front;//对尾指向头结点

}  

free(p);

returnOK;

}

//遍历元素

StatusQueueTraverse(LinkQueueQ){

QueuePtrp;

p=Q.front-next;

while(p){

visit(p-data);

p=p-next;

}

printf("\n");

returnOK;

}

intmain(){

inti;

QElemTyped;

LinkQueueq;

i=InitQueue(q);

  

//入队10个元素

for(intindex=0;indexMAXSIZE;index++){

EnQueue(q,index);

}

QueueTraverse(q);

DestroyQueue(q);

printf("队列已经销毁,q.front=%pq.rear=%p\n",q.front,q.rear);

  

return0;

}

学到这里,在学习的你理解了吗?有问题欢迎大家一起讨论!

预览时标签不可点收录于话题#个上一篇下一篇
1
查看完整版本: C语言数据结构之队列