首页 > 其他分享 >队列(Queue)

队列(Queue)

时间:2023-09-14 10:33:53浏览次数:37  
标签:queue 结点 队列 Queue front rear 指针

一、队列的概念

队列是一个先进先出的数据结构。
联想一下链表,在单链表中,只能对表尾进行插入,对表头进行结点的删除,这样强限制性的链表,就是所说的队列。也就是说,队列是限定在表的一端进行插入,表的另一端进行删除的数据结构。

如图去买票排队,每一列队伍都有一个队尾和队首,先来的先买票,后来的后买,买好的就从队首出去,新来买票的就需要从队尾继续排队。

通常,称进数据的一端为队尾,出数据的一端为队首,数据元素进队列的过程称为入队,出队列的过程称为出队。

队列是一个线性的数据结构,并且这个数据结构只允许在一端进行插入,另一端进行删除,禁止直接访问除这两端以外的一切数据,且队列是一个先进先出的数据结构。

如上图,队列就像一个两端相通的水管,只允许一端插入,另一端取出,取出的球就不在水管里面了,而先放入管中的球就会先从管中拿出。

队列存储结构的实现有以下两种方式:
顺序队列:在顺序表的基础上实现的队列结构。
链队列:在链表的基础上实现的队列结构。

两者的区别仅是顺序表和链表的区别,即在实际的物理空间中,数据集中存储的队列是顺序队列,分散存储的队列是链队列。

二、队列的结点设计与初始化

队列只有链式的设计方法,其本身分为多种队列,如顺序队列和循环队列,还有衍生的优先队列等等,以顺序队列的设计为例。

首先是队列的结点设计,可以设计出两个结构体,一个结构体 Node 表示结点,其中包含有 data 域和 next 指针,如图:

其中 data 表示数据,其可以是简单的类型,也可以是复杂的结构体。next 指针表示,下一个的指针,其指向下一个结点,通过 next 指针将各个结点链接。

然后再添加一个结构体,其包括了两个分别永远指向队列的队尾和队首的指针,看到这里是不是觉得和栈很像?

主要的操作只对这两个指针进行操作,如图所示:

其结构体设计的代码可以表示为:

//结点定义
typedef struct node{ 
  int data;
  struct node *next;
}
  node;
//队列定义,队首指针和队尾指针
typedef struct queue{ 
  node *front;
//头指针 
  node *rear; 
//尾指针
}
  queue;

 

 

对于初始化需要初始化两个类型,一个是初始化结点,一个是初始化队列。代码中的描述,初始化队列有些不同,当初始化队列的时候,需要将头尾两个结点指向的内容统统置为空,表示是一个空队列,两个创建的函数代码可以表示为:

//初始化结点
node *init_node{
  node *n=(node*)malloc(sizeof(node)); 
  if(n==){
//建立失败,退出 
    exit(0);
  }
  return n;
}

 

//初始化队列
queue *init_queue { 
  queue *q=(queue*)malloc(sizeof(queue)); 
  if(q==) { 
//建立失败,退出 
    exit(0);
  } 
//头尾结点均赋值 
  q->front=;
  q->rear=; 
  return q;
}

三、判断队列是否为空

这是一个既简单也很要紧的操作,判断队列是否为空直接就是判断队列头指针是否是空值即可。其代码可以表示为:

//队列判空
int empty(queue *q) { 
  if(q->front==) { 
    return 1; 
//1--表示真,说明队列非空 
  }else{
    return 0;
//0--表示假,说明队列为空 
  }
}

 

或者直接利用返回值进行更简单的判断也可以,代码如下:

int empty (queue *q) { 
  return q->front==;
}

 

 

四、入队操作

入队操作变化图:

进行入队(push)操作的时候,同样的,首先需要判断队列是否为空,如果队列为空的话,需要将头指针和尾指针一同指向第一个结点,代码如下:

front=n;
rear=n;

 

如图:

如果队列不为空的时候,这时只需要将尾结点向后移动,通过不断移动 next指针指向新的结点构成队列即可。如图:

其代码可以表示为:

//入队操作
void push(queue *q,int data) { 
  node *n =init_node;
  n->data=data;
  n->next=; 
//采用尾插入法 //if(q->rear==){ 
//使用此方法也可以 
  if(empty(q)) { 
    q->front=n; q->rear=n; 
  }else{ 
    q->rear->next=n; 
//n成为当前尾结点的下一结点 
    q->rear=n; 
//让尾指针指向n 
  }
}

 

 

五、出队操作

出队操作变化图:

出队(pop)操作,是指在队列不为空的情况下进行的一个判断,当然在此也一定要进行队列判空的操。

如图,如果队列只有一个元素了,也就是说头尾指针均指向了同一个结点,那么直接将头尾两指针置空,并释放这一个结点即可,如图:

当队列含有以上个元素时,需要将队列的头指针指向头指针当前指向的下一个元素,并释放掉当前元素即可,如图:

其代码可以表示为:

//出队操作
void pop(queue *q) { 
  node *n=q->front;
  if(empty(q)){
    return ; 
//此时队列为空,直接返回函数结束 
  } 
  if(q->front==q->rear){ 
    q->front=; 
//只有一个元素时直接将两端指向置为空即可 
    q->rear=; free(n); 
//记得归还内存空间 
  }else{ 
    q->front=q->front->next; free(n);
  }
}

 

六、打印队列元素(遍历)

打印队列的全部元素可以帮助调试,看到队列中具体的数据,在队列不为空的情况下,通过结点的 **next **指向依次遍历并输出元素既可。其代码可以表示为:

//打印队列元素
void print_queue(queue *q){ 
node *n = init_node; 
n=q->front; 
if(empty(q)){ return ; 
//此时队列为空,直接返回函数结束 } 
while (n!=){ 
printf("%d\t",n->data); 
n=n->next; 
} 
printf("\n");
 //记得换行}

 

遍历操作还有很多别的表示方法,比如说进行计算队列中含有多少元素,代码如下:

int calac(queue *q){ 
node *n = init_node; 
n=q->front; 
int cnt=0; 
//计数器设计 
if(empty(q)){ 
return 0; 
//此时队列为空,直接返回函数结束 
} 
while (n!=) {
 n=n->next; cnt++; 
} 
return cnt;
}

 

七、顺序队列的假溢出

什么是假溢出?这里需要考虑到顺序队列有什么缺点,对于顺序队列而言,其存在已经足够解决大多时候的设计问题了,但是其依旧存在一些缺陷和不足。

从上面的解析中看到,入队和出队操作均是直接在其后面进行结点的链接和删除,这种操作会造成其使用空间不断向出队的那一边偏移,产生假溢出。

来打一个比方,先看图:

上图所示,有一个顺序队列,这个队列的大小为5,其已经包含了四个元素 data1 , data2 , data3 , data4。

接着,对这个队列进行出队操作,出队2个元素,队列就变成了这个样子,如图:

从图上看到似乎没有什么问题,但是当接着再进行入队操作,比如入队2个元素,分别是 data5和** data6**。

此时已经发现问题了,尾指针移动到可以进行队列操作的范围之外去了,有没有发现?

这种现象称呼作为队列用的存储区还没有满,但队列却发生了溢出,把这种现象称为假溢出。如图:

那么有什么办法解决这个问题呢?这就要涉及到循环队列的性质了!

八、循环队列的概念

可能这个时候会产生一个疑问,学习的队列不是使用链表实现的动态队列么?没有空间的时候会开辟空间,这难道还会产生假溢出么?的确,当进行动态创建队列的时候,也只不过是向后继续不断的申请内存空间;即使前面出队操作释放掉了前面的空间,但是指针依旧会向后进行移动,直到达到系统预留给程序的内存上界被强行终止;这对于极为频繁的队列操作和程序而言是致命的,这时候,就需要对我们的队列进行优化,使用更为优秀的结构——循环队列。

循环队列就是将队列存储空间的最后一个位置转而绕到第一个位置,形成逻辑上的环状空间,以此来供队列循环使用,如图:

循环队列就是给定队列的大小范围,在原有队列的基础上,只要队列的后方满了,就从这个队列的前面开始进行插入,以达到重复利用空间的效果;由于循环队列的设计思维更像一个环,因此常使用一个环图来表示,但我们需要注意,实际上循环队列不是一个真正的环,它依旧是单线性的。

九、循环队列的结构设计

由于循环对列给定了数据范围的大小,所以不需要使用链式的动态创建方法了。因为如果使用链式存储,会无法确定何时再回到队头进行插入操作,所以采用模拟的方法,如图:

其中,data 表示一个数据域,int 为类型,其可以修改为任意自定义的类型,比如说简单的 char,float 类型等等,也可以是复杂的结构体类型。

  • maxsize表示循环队列的最大容纳量,其表示队列的全部可操作空间。

  • rear代表尾指针,入队时移动。

  • front代表头指针,出队时移动。

其代码可以表示为:

#define maxsize 10 
//表示循环队列的最大容量
//循环队列的结构设计
typedef struct cir_queue{ 
int data[maxsize]; 
int rear; 
int front;
}
cir_queue;

 

十、循环队列的初始化

循环队列的初始化核心就在于申请空间,并且将 front 指针和 rear 指针内容赋值为0,即指向第0个元素即可,这里要注意第 0个元素内容为空,如图:

其代码可以表示为:

//初始化
cir_queue *init{ 
cir_queue *q = (cir_queue*)malloc(sizeof(cir_queue)); 
if(q==){ exit(0); 
//申请内存失败,退出程序 
} 
q->front=0; 
q->rear=0; 
return q;
}

 

十一、循环队列的入队操作

入队操作同顺序队列的方法,直接将 rear 向后移动即可。但是要注意判断,如果 rea r达到了队列的空间上线,将要从头继续开始移动。这里推荐使用余数法,即无论如何求余都是在这片空间内进行操作,防止一次错误执行就直接整体崩溃,而且也相对而言更为简洁,不推荐使用 if 语句,这样显得比较累赘。

注意进行加一移动位置操作的时候,不能直接 **q->rear++ **这样的操作,这样计算机判断优先级会产生让自己意想不到的后果。此外这里还需要进行一次是否队列已满的判断,当我们 rear 指针的下一个位置就是front的位置的时候,即改循环队列已满。如图:

其代码可以表示为:

//入队操作
pushvoid push(cir_queue *q,int data){ 
if((q->rear+1)%maxsize==q->front){ 
printf("溢出,无法入队\n"); 
return; 
}else{ 
q->rear=(q->rear+1)%maxsize;
 q->data[q->rear]=data; 
}
}

 

十二、循环队列的出队操作

如果顺序队列的出队操作,直接将 front 进行后移一位即可。

这里上面很多地方都提过了,有一个需要留意的地方,即队列是否为空,当队列为空的时候是无法进行出队操作的。

其代码可以表示为:

//出队操作
popvoid pop(cir_queue *q){ 
if(q->rear==q->front){ 
printf("队列为空,无法出队\n"); 
return; 
}else{ 
q->data[q->front]=0; 
q->front=(q->front+1)%maxsize;
 }
}

 

十三、循环队列的遍历操作

遍历操作需要借助一个临时变量储存位置 front 的位置信息,利用i逐步向后移动,直到 i 到达了 rear 的位置即可宣告遍历的结束。

//遍历队列
void print(cir_queue *q){ 
int i=q->front; 
while(i!=q->rear){ i=(i+1)%maxsize; 
printf("%d\t",q->data[i]);  
} 
printf("\n"); 
//记得换行
}

 

标签:queue,结点,队列,Queue,front,rear,指针
From: https://www.cnblogs.com/--xiaoxiao/p/17701827.html

相关文章

  • 学习笔记之Redis消息队列-基于Stream的消息队列
    学习笔记之Redis消息队列-基于Stream的消息队列Stream是Redis5.0引入的一种新数据类型,可以实现一个功能非常完善的消息队列。其实只需要知道写入消息队列的命令和读取消息队列的命令就行了写入消息队列:XADD读取消息队列的方式之一:XREAD在业务开发中,我们可以循环的调用......
  • 深入研究消息队列05 各消息队列集群架构对比
    23RabbitMQ的集群架构集群构建数据可靠性身份认证资源鉴权可观测性......
  • Java有关队列的基本操作
    什么是队列?队列是一种线性数据结构,队列中的元素只能先进先出;队列的出口端叫做队头,入口端叫做队尾。队列的基本操作1.入队:只允许在队尾的位置放入元素,新元素的下一个位置将会成为新的队尾;publicvoidenQueue(intelement)throwsException{if((rea......
  • Java多线程____BlockingQueue阻塞队列使用
    packagecom.frame.base.thread;importjava.util.concurrent.BlockingQueue;importjava.util.concurrent.ArrayBlockingQueue;/***并发编程____阻塞队列*/publicclassBlockingQueueTest{ publicstaticvoidmain(String[]args)throwsInterruptedException{......
  • 队列——链式存储
    核心思路:1、首先定义队列结点,包含数据域和指针域;然后定义链式队列,包含队列节点类型的队头和队尾指针。2、初始化:带头结点:给头结点分配内存,然后队头和队尾指针指向头结点,同时队头指针的next指向NULL。不带头结点:队头和队尾指针都指向NULL。3、入队:带......
  • 消息队列 RabbitMQ
    发布者:生产者,消息的发送方。连接:网络连接。Channel:信道,多路复用连接中的一条独立的双向数据流通道。Exchange:交换器(路由器),负责消息的路由到相应队列。类型:direct、fanout、topicBinding:队列与交换器间的关联绑定。消费者将关注的队列绑定到指定交换器上,以便Exchange能准确分发消息......
  • 如何在Linux上设置高可用的消息队列
    消息队列是现代分布式系统中常用的一种通信方式,它可以在多个进程或者多台服务器之间传递数据,实现解耦和异步通信的目的。在Linux系统上,我们可以通过一些开源的消息队列软件来搭建高可用的消息队列系统。本文将以RabbitMQ为例,介绍如何在Linux上搭建和配置高可用的消息队列。步骤一:安......
  • C++ 优先队列 priority_queue
    既然是队列那么先要包含头文件#include<queue>,他和queue不同的就在于我们可以自定义其中数据的优先级,让优先级高的排在队列前面,优先出队优先队列具有队列的所有特性,包括基本操作,只是在这基础上添加了内部的一个排序,它本质是一个堆实现的和队列基本操作相同:top访问队头......
  • 队列 queue
         双端队列deque1.双端队列知识需知由于队列是一种先进先出(FIFO)的数据结构,因此无法直接从队列的底部删除元素。如果希望从队列的底部删除元素,可以考虑使用双端队列(deque)。双端队列(deque)是一种允许在两端插入和删除元素的数据结构。使用push_back()和push_fron......
  • 仲裁队列
          ......