首页 > 其他分享 >【数据结构】队列

【数据结构】队列

时间:2024-10-29 19:49:10浏览次数:7  
标签:结点 链队 队列 元素 front 数据结构 rear

目录

3.3.1 队列的定义

思考题

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

1.队列的顺序存储结构定义

2.顺序队中实现队列的基本运算

(1)初始化队列InitQueue(q) 构造一个空队列q

(2)销毁队列DestroyQueue(q)

(3)判断队列是否为空QueueEmpty(q)    

(4)进队列enQueue(q,e)    

(5)出队列deQueue(q,e)  

3.在环形队中实现队列的基本运算(或循环队列)

代码实现:

例题:

3.顺序队列和循环队列的不同

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

1.队列的链式存储结构定义

2.队列的链式存储结构基本运算的实现

(1)链队中数据结点的类型DataNode声明如下:

链队的4要素:

(2)初始化队列InitQueue(q)    

(3)销毁队列DestroyQueue(q)

(4)判断队列是否为空QueueEmpty(q)

(5) 进队enQueue(q,e)

(6)出队deQueue(q,e)

(7)对应的队列基本运算算法如下:

3.思考题        

4.小结:


3.3.1 队列的定义

  • 队列简称队,它也是一种运算受限的线性表
  • 队列只能选取一个端点进行插入操作,另一个端点进行删除操作
  • 把进行插入的一端称做队尾(rear), 进行删除的一端称做队首或队头(front)
  •  向队列中插入新元素称为进队或入队,新元素进队后就成为新的队尾元素
  •  从队列中删除元素称为出队或离队,元素出队后,其后继元素就成为队首元素

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

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

  • InitQueue(&q):初始化队列。构造一个空队列q。
  • DestroyQueue(&q):销毁队列。释放队列q占用的存储空间。
  • QueueEmpty(q):判断队列是否为空。若队列q为空,则返回真;否则返回假。
  • enQueue(&q,e):进队列。将元素e进队作为队尾元素。
  • deQueue(&q,&e):出队列。从队列q中出队一个元素,并将其值赋给e。

思考题

队列线性表有什么不同? 队列和栈有什么不同?

解析:

相同点不同点
都是线性结构

①运算规则不同

  • 线性表为随机存取, 而栈是只允许在栈顶进行插入、删除运算,是后进先出表;
  • 队列是只允许在队尾进行插入、队头进行删除运算,是先进先出表;
都是逻辑结构的概念
都可以用顺序存储或链表存储

② 用途不同

  • 堆栈用于子程调用和保护现场;
  • 队列用于多道作业处理、指令寄存及其他运算等等
栈和队列是两种特殊的线性表,对插入、删除运算加以限制

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

1.队列的顺序存储结构定义

顺序队类型SqQueue声明如下:

typedef struct 
{  ElemType data[MaxSize]; //存放队中元素
   int front,rear; //队首和队尾指针
}  SqQueue;//顺序队类型

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

                  

2.顺序队中实现队列的基本运算

(1)初始化队列InitQueue(q) 构造一个空队列q

将front和rear指针均设置成初始状态即-1值,(初始值-1,即当前下标为0的位置)算法如下:

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)
{
    if(q!=NULL)       //如果q = NULL,则运行报错
    return(q->front==q->rear);
}

(4)进队列enQueue(q,e)    

在队列不满的条件下,先将队尾指针rear循环增1,然后将元素添加到该位置,算法如下:

bool enQueue(SqQueue *&q,ElemType e)
{   if (q->rear==MaxSize-1)	//队满上溢出,q->rear==MaxSize-1表示堆队满
	return false;
    q->rear++;          //队尾增一
    q->data[q->rear]=e;//在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;
}

3.在环形队中实现队列的基本运算(或循环队列)

  环形列队操作主要解决假上溢问题!!!

这是因为采用rear==MaxSize-1作为队满条件的缺陷,

当队满条件为真时,队中可能还有若干空位置, 这种溢出并不是真正的溢出,称为假溢出

如何利用下面空闲的0和1呢?

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

环形队列(循环队列):

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

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

上面这样做是为了保证不越界

  • 如图说明了环形列队操作的几种状态,在这里假设MaxSize等于5

       

环形队列的4要素:

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

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

代码实现:

(1)进队列:enQueue(&q,e)

在列队不满的条件下先将队尾指针rear循环增一,然后将元素插到该位置,算法如下:

bool enQueue(SqQueue *&q,ElemType e)
{   if ((q->rear+1)%MaxSize==q->front)	//队满上溢出
	return false;
    q->rear=(q->rear+1)%MaxSize;          
    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 = (q->front+1) % MaxSize;          
    e = q->data[q->front];
    return true;
}
例题:

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

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

解析:

     

以上是求数据元素个数的方法分析,公式:count = (rear-front+MaxSize)%MaxSize

全部代码如下:

3.顺序队列和循环队列的不同

  • 显然环形队列比非环形队列更有效利用内存空间,即环形队列会重复使用已经出队元素的空间。不会出现假溢出。
  • 但如果算法中需要使用所有进队的元素来进一步求解(在环形队列中,出队元素空间可能被新进队的元素覆盖),此时可以使用非环形队列。

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

1.队列的链式存储结构定义

采用链表存储的队列称为链队,这里采用不带头结点的单链表实现

链队的进队和出队操作演示:

2.队列的链式存储结构基本运算的实现

(1)链队中数据结点的类型DataNode声明如下:

typedef struct qnode
{  ElemType data;	//数据元素
   struct qnode *next;//下一个结点指针
}  DataNode;//链队数据结点类型

链队头结点(或链队结点)的类型LinkQuNode声明如下:

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

链队的4要素:

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

(2)初始化队列InitQueue(q)    

构造一个空队列,即只创建一个链队头结点,其front和rear域均置为NULL,不创建数据元素结点,算法如下:

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

(3)销毁队列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);		 	//释放链队结点占用空间
}

(4)判断队列是否为空QueueEmpty(q)

若链队结点的rear域值为NULL,表示队列为空,返回true;否则返回false,算法如下:

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

(5) 进队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;
   }
}

核心代码示意图如下:q->rear->next=p; 
                                    q->rear=p;

(6)出队deQueue(q,e)

考虑情况:1.原队列为空

                  2.原队列只有一个结点

                  3.其他情况

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;
}

核心代码示意图如下:q->front=q->front->next;

(7)对应的队列基本运算算法如下:

运行结果如下:

3.思考题        

链队顺序队两种存储结构有什么不同?

解析:

队列分为顺序存储结构和链式存储结构。

(1)链式存储结构其实就是线性表的单链表,只是只能在对头出元素,队尾进元素而已。 队列的顺序存储结构的缺点——“假溢出”。 循环队列的顺序结构能解决“假溢出”,但是循环队列必须指定出队列的长度,所以说它并不完美。

(2)我们可以用链式存储结构的方式来弥补上述的不足

4.小结:

标签:结点,链队,队列,元素,front,数据结构,rear
From: https://blog.csdn.net/2301_80031203/article/details/143312752

相关文章

  • 复杂度分析,数据结构的数组与链表
    复杂度分析,数据结构的数组与链表参考书籍:Hello算法目录复杂度分析,数据结构的数组与链表复杂度分析时间复杂度空间复杂度数据结构数组与链表数组链表列表复杂度分析复杂度分析是用来判断一个算法效率的手段,执行时所需的时间和空间资源则是正好对应时间和空间复杂度。考虑到执......
  • 栈和队列(一)
    目录一、栈(后入先出)1、概念和结构2、栈的实现(顺序表)主程序(test.c)头文件(stack.h)调用函数(stack.c)二、队列(先入先出)1、队列的概念及结构2、队列的实现主程序(test.c)头文件(queue.h)调用函数(queue.c)一、栈(后入先出)1、概念和结构栈是一种特殊的线性表,只允许在固定......
  • Day20 数据结构
    队列(Queue)队列是一种运算受限的线性结构,遵循先进先出(FIFO)原则。队列是一种受限的线性结构受限之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作  普通队列 queue.Queue,适用于多线程环境。  双端队列 collections.deque,具有队列和......
  • 队列与树 数据结构复习笔记
    2.3队列队列(Queue),它是一种运算受限的线性表,先进先出(FIFOFirstInFirstOut)队列是一种受限的线性结构受限之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作Python标准库中的queue模块提供了多种队列实现,包括普通队列、双端队列、优先队......
  • 数据结构-栈的顺序存储结构
    第三章栈3.1栈的定义                                   线性表                       栈只能选取同一个端点进行插入和删除操作允许进行插入......
  • c语言-数组队列-学习笔记
    数组队列#include<stdio.h>#include<stdlib.h>/*数组顺序队列*/typedefstructSqQueue{ intdata[10]; intfront; intrear;}SqQueue;voidInitQueue(SqQueue*Q){ Q->front=Q->rear=0;}voidEnQueue(SqQueue*Q,inta){ Q->data[Q->rear......
  • 数据结构之——选择树
    一、选择树的概念选择树是一种完全二叉树,在数据处理和排序等领域有着重要的应用。其中,胜者树和败者树是选择树的两种常见形式。胜者树的每个中间结点记录的是胜者的标号。例如在一个有多个选手(叶子节点)的胜者树中,每个非叶子节点表示一场比赛,记录胜者的标号,每一层相当于一轮比......
  • 代码随想录-栈与队列6、7
    逆波兰表达式思路用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中这里记录string类型相关操作:判断token是否是数字,不可像char类型用string重载的>=,<=,前者由于用ASCII码表示,后者按字典序比较,例如1<2所以字符串比较"10"<"2"。所以直接......
  • 数据结构 之 哈夫曼树及其应用(八)
    提示:本节重点是哈夫曼树的构造文章目录哈夫曼树的基本概念举例※构造哈夫曼树基本思想※构造哈夫曼树过程哈夫曼树的应用1------用于最佳判断过程哈夫曼树应用2----用于通信编码哈夫曼编码总结哈夫曼树的基本概念路径长度:由树中一个结点到另一结点的分支构成这......
  • 算法与数据结构——计数排序
    计数排序计数排序(countingsort)通过统计元素数量来实现排序,通常应用于整数数组。简单实现给定一个长度为n的数组nums,其中的元素都是“非负整数”,计数排序的整体流程如下:遍历数组,找出其中最大的数组,记为m,然后创建一个长度为m+1的辅助数组counter。借助counter统计nums中各......