3.2队列【顺序队和链队】

目录

3.2.1 队列的定义

3.2.2  队列的顺序存储结构及其基本运算实现

1、顺序队中实现队列的基本运算算法

2、环形队列(或循环队列)中实现队列的基本运算

 3、相关例题

3.2.3 队列的链式存储结构及其基本运算的实现 

3.2.4 队列的应用

1. 求解报数问题 

 2. 求解迷宫问题

 3.2.5 双端队列


3.2.1 队列的定义

 队列简称,它也是一种运算受限的线性表。      

 队列只能选取一个端点进行插入操作,另一个端点进行删除操作。

队列的几个概念:

        1、进行插入的一端称做队尾(rear)。

        2、进行删除的一端称做队首或队头(front)。

         3、向队列中插入新元素称为进队或入队,新元素进队后就成为新的队尾元素。

        4、从队列中删除元素称为出队或离队,元素出队后,其后继元素就成为队首元素。

 队列的主要特点是先进先出,所以又把队列称为先进先出表。 

队列抽象数据类型=逻辑结构+基本运算(运算描述)

队列的基本运算如下:

 InitQueue(&q):初始化队列。构造一个空队列q。

 DestroyQueue(&q):销毁队列。释放队列q占用的存储空间。

 QueueEmpty(q):判断队列是否为空。若队列q为空,则返回真;否则返回假。

 enQueue(&q,e):进队列。将元素e进队作为队尾元素。

 deQueue(&q,&e):出队列。从队列q中出队一个元素,并将其值赋给e。 

3.2.2  队列的顺序存储结构及其基本运算实现

队列中元素逻辑关系与线性表的相同,队列可以采用与线性表相同的存储结构。

顺序队类型SqQueue声明如下: 

(因为队列两端都在变化,所以需要两个指针来标识队列的状态)

typedef struct 
{  ElemType data[MaxSize]; 
   int front,rear;      //队首和队尾指针
}  SqQueue;

总结:

  • 约定rear总是指向队尾元素
  • 元素进队,rear增1
  • 约定front指向当前队中队头元素的前一位置
  • 元素出队,front增1
  • 当rear=MaxSize-1时不能再进队 

顺序队的4要素(初始时front=rear=-1):

  • 队空条件:front = rear  
  • 队满条件:rear = MaxSize-1  
  • 元素e进队:rear++; data[rear]=e;  
  • 元素e出队:front++; e=data[front];

注意:rear指向队尾元素;front指向队头元素的前一个位置。 

1、顺序队中实现队列的基本运算算法

 (1)初始化队列InitQueue(q)    构造一个空队列q。将front和rear指针均设置成初始状态即-1值。

void InitQueue(SqQueue *&q)
{  q=(SqQueue *)malloc (sizeof(SqQueue));
  q->front=q->rear=-1;
}

 (2)销毁队列DestroyQueue(q)      释放队列q占用的存储空间。

void DestroyQueue(SqQueue *&q)
{
    free(q);
}

 (3)判断队列是否为空QueueEmpty(q)      若队列q满足q->front==q->rear条件,则返回true;否则返回false。

bool QueueEmpty(SqQueue *q)
{
    return(q->front==q->rear);
}

 (4)进队列enQueue(q,e)     在队列不满的条件下,先将队尾指针rear循环增1,然后将元素添加到该位置。

bool enQueue(SqQueue *&q,ElemType e)
{   if (q->rear==MaxSize-1)		//队满上溢出
	return false;
    q->rear++;
    q->data[q->rear]=e;
    return true;
}

  (5)出队列deQueue(q,e)    在队列q不为空的条件下,将队首指针front循环增1,并将该位置的元素值赋给e。 

bool deQueue(SqQueue *&q,ElemType &e)
{   if (q->front==q->rear)  	//队空下溢出
	return false;
    q->front++;
    e=q->data[q->front];
    return true;
}

2、环形队列(或循环队列)中实现队列的基本运算

    这是因为采用rear==MaxSize-1作为队满条件的缺陷。当队满条件为真时,队中可能还有若干空位置。这种溢出并不是真正的溢出,称为假溢出。 

 解决方案

        把数组的前端和后端连接起来,形成一个环形的顺序表,即把存储队列元素的表从逻辑上看成一个环,称为环形队列或循环队列。 

 环形队列(循环队列):

实际上内存地址一定是连续的,不可能是环形的,这里是通过逻辑方式实现环形队列,也就是将rear++和front++改为:

  • rear=(rear+1)%MaxSize  
  • front=(front+1)%MaxSize

现在约定rear=front为队空,以下两种情况都满足该条件:

 

环形队列的4要素:

  • 队空条件:front = rear  
  • 队满条件:(rear+1)%MaxSize = front  
  • 进队e操作:rear=(rear+1)%MaxSize;  将e放在rear处  
  • 出队操作:front=(front+1)%MaxSize; 取出front处元素e; 

    在环形队列中,实现队列的基本运算算法与非环形队列类似,只是改为上述4要素即可。

 3、相关例题

    【例3.7】对于环形队列来说,如果知道队头指针和队列中元素个数,则可以计算出队尾指针。也就是说,可以用队列中元素个数代替队尾指针

     设计出这种环形队列的初始化、入队、出队和判空算法。 

 

 解

依题意设计的环形队列类型如下:

typedef struct
{  ElemType data[MaxSize];
   int front;		//队头指针
   int count;		//队列中元素个数
}  QuType;

对应的算法如下:

void InitQueue(QuType *&qu)	//初始化队运算算法
{  qu=(QuType *)malloc(sizeof(QuType));
   qu->front=0;
   qu->count=0;
}
bool EnQueue(QuType *&qu,ElemType x)   	//进队运算算法
{  int rear;		      	  		//临时队尾指针
   if (qu->count==MaxSize)	  		//队满上溢出
      return false;
   else
   {  rear=(qu->front+qu->count)%MaxSize;	//求队尾位置
      rear=(rear+1)%MaxSize;  		//队尾循环增1
      qu->data[rear]=x;
      qu->count++;		 		//元素个数增1
      return true;
   }
}
bool DeQueue(QuType *&qu,ElemType &x)  	//出队运算算法
{  if (qu->count==0)		         	//队空下溢出
	return false;
   else
   {	qu->front=(qu->front+1)%MaxSize; 	//队头循环增1
	x=qu->data[qu->front];
	qu->count--;			 	//元素个数减1
	return true;
   }
}
bool QueueEmpty(QuType *qu)	   //判队空运算算法
{
    return(qu->count==0);
}

3.2.3 队列的链式存储结构及其基本运算的实现 

 

单链表中数据结点类型DataNode声明如下:

typedef struct qnode
{  ElemType data;	//数据元素
   struct qnode *next;
}  DataNode;

 链队结点类型LinkQuNode声明如下:

typedef struct
{  DataNode *front;	//指向单链表队头结点
   DataNode *rear; 	//指向单链表队尾结点
}  LinkQuNode; 

链队的4要素:

  • 队空条件:front=rear=NULL  
  • 队满条件:不考虑  
  • 进队e操作:将包含e的结点插入到单链表表尾  
  • 出队操作:删除单链表首数据结点

 在链队存储中,队列的基本运算算法:

(1)初始化队列InitQueue(q)    构造一个空队列,即只创建一个链队头结点,其front和rear域均置为NULL,不创建数据元素结点。

void InitQueue(LinkQuNode *&q)
{  q=(LinkQuNode *)malloc(sizeof(LinkQuNode));
   q->front=q->rear=NULL;
}

  (2)销毁队列DestroyQueue(q)       释放队列占用的存储空间,包括链队头结点和所有数据结点的存储空间。

void DestroyQueue(LinkQuNode *&q)
{  DataNode *p=q->front,*r;  	//p指向队头数据结点
   if  (p!=NULL)			//释放数据结点占用空间
   {  r=p->next;
      while (r!=NULL)
      {  free(p);
	  p=r;r=p->next;
      }
   }
   free(p);  free(q);		 	//释放链队结点占用空间
}

 (3)判断队列是否为空QueueEmpty(q)      若链队结点的rear域值为NULL,表示队列为空,返回true;否则返回false。

bool QueueEmpty(LinkQuNode *q)
{
  return(q->rear==NULL);
}

  (4)进队enQueue(q,e)

考虑情况:

  • 原队列为空
  • 原队列非空
void enQueue(LinkQuNode *&q,ElemType e)
{  DataNode *p;
   p=(DataNode *)malloc(sizeof(DataNode));
   p->data=e;
   p->next=NULL;
   if (q->rear==NULL)   //若链队为空,新结点是队首结点又是队尾结点
	q->front=q->rear=p;
   else
   {   q->rear->next=p; //将p结点链到队尾,并将rear指向它
       q->rear=p;
   }
}

  (5)出队deQueue(q,e)

考虑情况:

  • 原队列为空
  • 原队列只有一个结点
  • 其他情况
bool deQueue(LinkQuNode *&q,ElemType &e)
{  DataNode *t;
   if (q->rear==NULL) return false;	//队列为空
   t=q->front;		   		//t指向第一个数据结点
   if (q->front==q->rear)  		//队列中只有一个结点时
       q->front=q->rear=NULL;
   else			   		//队列中有多个结点时
       q->front=q->front->next;
   e=t->data;
   free(t);
   return true;
}

    【例3.8】 采用一个不带头结点只有一个尾结点指针rear的循环单链表存储队列,设计队列的初始化、进队和出队等算法。

 链队的4要素:

  • 队空条件:rear=NULL  
  • 队满条件:不考虑  
  • 进队e操作:将包含e的结点插入到单链表表尾  
  • 出队操作:删除单链表首结点
void InitQueue(LinkList *&rear)  //初始化队运算算法
{
  rear=NULL;
}
bool queueEmpty(LinkList *rear)   //判队空运算算法
{
   return(rear==NULL);
}
void enQueue(LinkList *&rear,ElemType x)   //进队运算算法
{  LinkList *p;
   p=(LinkList *)malloc(sizeof(LinkList));  //创建新结点
   p->data=x;
   if (rear==NULL)				//原链队为空
   {	p->next=p;				//构成循环链表
	rear=p;
   }
   else
   {  p->next=rear->next;			//将p结点插入rear结点之后
      rear->next=p;
      rear=p;					//让rear指向新插入的结点
   }
}
bool deQueue(LinkList *&rear,ElemType &x)  //出队运算算法
{  LinkList *q;
   if (rear==NULL) return false;  	  	//队空
   else if (rear->next==rear)	     	  	//原队只有一个结点
   {  x=rear->data;
      free(rear);
      rear=NULL;
   }
   else			     	     	  	//原队有两个或以上的结点
   {	q=rear->next;
	x=q->data;
	rear->next=q->next;
	free(q);
   }
   return true;
}

3.2.4 队列的应用

1. 求解报数问题 

问题描述      

        设有n个人站成一排,从左向右的编号分别为1~n,现在从左往右报数“1,2,1,2,…”,数到“1”的人出列,数到“2”的立即站到队伍的最右端。报数过程反复进行,直到n个人都出列为止。要求给出他们的出列顺序。

   例如,当n=8时,初始序列为         1  2  3  4  5  6  7  8    

                            则出列顺序为         1  3  5  7  2  6  4  8 

 运算算法设计

   算法思想:先将n个人的编号进队,然后反复执行如下操作,直到队列为空:

1、出队一个的元素,输出其编号—报数为1的人出列。

2、若队列不空,则再出队一个元素,并将刚出列的元素进队—报数为2的人站到队伍的最右端即队尾。

void number(int n)
{  int i;  ElemType e;
   SqQueue *q;				//环形队列指针q
   InitQueue(q);			//初始化队列q
   for (i=1;i<=n;i++)			//构建初始序列
      enQueue(q,i);
   printf("报数出列顺序:");
   while (!QueueEmpty(q))		//队列不空循环
   {  deQueue(q,e);			//出队一个元素e
      printf("%d ",e);		//输出元素编号
      if (!QueueEmpty(q))		//队列不空
      {  deQueue(q,e);		//出队一个元素e
         enQueue(q,e);		//将刚出列的元素进队
      }
   }
   printf("\n");
   DestroyQueue(q);			//销毁队列q
}

  设计求解程序

int main()
{  int i,n=8;
   printf("初始序列:");
   for (i=1;i<=n;i++)
     printf("%d ",i);
   printf("\n");
   number(n);
   return 1;
}
 2. 求解迷宫问题

数据组织

使用一个队列qu记录试探的方块,该队列的结构如下:    

typedef struct 
{  int i,j;		//方块的位置
   int pre		//本路径中上一方块在队列中的下标
} Box;			//方块类型

typedef struct
{  Box data[MaxSize];
   int front,rear;	  //队头指针和队尾指针
} QuType;		  //定义顺序队类型

     使用的队列qu不是环形队列(因为要利用出队的元素找路径),因此在出队时,不会将出队元素真正从队列中删除,因为要利用它输出路径。

 

用队列求一条迷宫路径的算法:(xi,yi)→ (xe,ye) 

bool mgpath1(int xi,int yi,int xe,int ye)
//搜索路径为:(xi,yi)  (xe,ye)
{  Box e;
   int i,j,di,i1,j1;
   QuType *qu;			//定义顺序队指针qu
   InitQueue(qu);		//初始化队列qu
   e.i=xi; e.j=yi; e.pre=-1;
   enQueue(qu,e);		//(xi,yi)进队
   mg[xi][yi]=-1;		//将其赋值-1,以避免回过来重复搜索
   while (!QueueEmpty(qu))		//队不空循环
   {	deQueue(qu,e);			//出队方块e
	i=e.i;   j=e.j;
	if (i==xe && j==ye)		//找到了出口,输出路径
	{  dispapath(qu,qu->front);	//调用dispapath函数输出路径
	   DestroyQueue(qu);		//销毁队列
	   return true;		//找到一条路径时返回真
	}
   for (di=0;di<4;di++)	//循环扫描每个方位
   {	switch(di)
	{
	   case 0:i1=i-1; j1=j;   break;
	   case 1:i1=i;   j1=j+1; break;
	   case 2:i1=i+1; j1=j;   break;
	   case 3:i1=i;   j1=j-1; break;
	}
	if (mg[i1][j1]==0)
	{     e.i=i1;  e.j=j1; 
	      e.pre=qu->front;	
	      enQueue(qu,e);	//(i1,j1)方块进队
	      mg[i1][j1]=-1;	//将其赋值-1
	}
     }
  }
  DestroyQueue(qu);		//销毁队列
  return false;
}

 3.2.5 双端队列

   所谓双端队列是指两端都可以进行进队和出队操作的队列,将队列的两端分别称为前端和后端,两端都可以入队和出队。    其元素的逻辑结构仍是线性结构。 

 

 【例3.10】某队列允许在两端进行入队操作,但仅允许在一端进行出队操作,若a、b、c、d、e元素进队,则不可能得到的顺序是(   )。      

A. bacde        B. dbace     C. dbcae    D. ecbad

   

答案为C。


本章完。 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

万叶学编程

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

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

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

打赏作者

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

抵扣说明:

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

余额充值