问题引入
在数据结构中,队列也是一种重要的线性结构,常和栈放在一起进行学习。队列分为多种类型,常见的如循环队列、链队列等,如果此时此刻你对“链队列”感到困惑,那就继续看下去,我们一步一步去剖析。
问题分析
如果想要搞明白队列,首先你要明白两个问题:
1.什么是队列?
答:“队列”(queue)是一种“先进先出”的线性表。
2.队列的特点是什么?
答:队列只允许在表的一端进行插入元素,在另一端删除元素。
通过上面两个问题,综合到一起就是,队列也是一种线性表,有两端,一端是队列的开头,一端是队列的结尾,例如:& 1 2 3 4 5 6 #,“&”就相当于队列的头,“#”就相当于队列的尾。同时,队列是“先进先出”的特点,一端进行插入,一端进行删除,例如:& 1 2 3 4 5 6 #,我们要插入元素必须从队尾#后进行插入,删除元素必须从&开始删除,只有这种顺序才能满足队列“先进先出”的特点,因为排在队头的一定是先到来的。(如果迷糊,不妨再多看一遍。)
如下图所示,顺序由0-8,0是队头,元素从队尾进入,从队头删除。
那么,现在明白了什么是队列,他的特点是什么,紧接着你要想到一个问题,“线性表”你疑惑吗?如果你对线性表不疑惑,那么你对线性表中的“链式结构”即“链表”的结构熟悉吗?如果你熟悉,那么就接着往下看;如果你不熟悉,先暂停在这里,看一看线性表中的“链式结构”,请看文章“链表(详解)”(点击查看),如果明白了链表,那么对“链队列”的理解会很顺利。
链队列整体结构分析
链队列与循环队列很相似,都具有“队头指针、队尾指针”,我们通过两个指针来实现队列中元素的进出。
涉及到“链式结构”就必定要使用“结点”的方式储存元素,那么最开始没有元素入队列的时候,队头、队尾指针均指向“头结点”,之后若有元素要入队列,我们就动态分配一个新的结点并存入数据,之后连接这个结点到头结点上,队尾指针再后移,就实现了队列队头删除、队尾插入的特点。如下图,如果仍然感到困惑,接下来我们分过程去看元素入链队列和出链队列的过程。
元素入链队列分析
学习过链表的我们知道,链式结构主要是将一个一个存储信息的结点通过结点前驱记录结点的地址信息并连接在一起,形成一种链式结构,其本质上又是一个线性表,因此称为“链表”。
那么,对于链队列而言,实际上也是一种“链式结构”的表,因为它具有“先进先出”的特点,像排队一样,先排的人先离开,因此删除元素只能在“队头”,插入元素只能在“队尾”。另一方面,我们知道,对于链式结构,我们是必须要知道“头结点”的位置,只有通过头结点才可以找到后续结点,因此链队列分别设置队头指针、队尾指针,令队头指针一直记录头结点位置,队尾指针后移指向插入的元素。
例如:
在链队列中插入元素66。
分析:首先需要分配一个“新结点的存储空间”,其次将数据输入结点数据域中,之后将结点的指针域next置为NULL,最后将结点链接到队列中。如图1分配了一个新结点s,s->data=66,将元素放入结点数据域中,s->next=NULL,将结点的指针域next置为空。接下来看图2。
图1
结点s信息输入后,接下来就要考虑将结点s连接到队列中。因为每次队尾指针rear是指向队尾元素的,因此令队尾元素的指针域指向结点s,即rear->next=s,那么如何将队尾指针rear指向结点s呢?接下来看图3。
图2
如果要使队尾指针指向结点s,通过将结点s的地址赋给队尾指针rear即可,Q.front=s;
图3
元素出链队列分析
例如:
在链队列中删除元素11。
明白了元素入队列,那么元素的出队列与入队列相似,我们也是通过设置一个辅助结点p用于指向要删除的队头元素,将队头元素划分出来,进行删除。如图1,通过p=Q.front->next,令新结点p指向队头元素,并返回值e=p->data。下一步就是令头结点指向p结点的下一结点而舍弃p结点,请看图2。
图1
我们通过Q.front->next=p->next;语句,令头结点指针域指向队头元素下一元素的结点位置,从而舍弃结点p。接下来对结点p进行删除,请看图3。
图2
我们通过库函数中的free(p)或者delete(p)删除p所指向的结点,即实现了将该结点删除,避免该结点占用空间,浪费系统资源。
图3
代码实现
说明:采用C++语言,编译环境为DevC++。
//导入头文件
#include<stdio.h>
#include<malloc.h>
#include<iostream>
using namespace std;
//定义队列
typedef struct qNode{
int data;//整型数据(可以根据需要修改数据类型)
struct qNode *next;
}qNode,*queue;
typedef struct{
queue front;
queue rear;
}Queue;
//初始化队列
void initQueue(Queue &Q){
Q.front=Q.rear=(queue)malloc(sizeof(qNode));//队头指针、队尾指针指向同一空结点
if(!Q.front){
exit(-1);//内存分配失败,退出
}
Q.front->next=NULL;
}
//判断队列是否为空
bool emptyQueue(Queue &Q){
if(Q.front==Q.rear){
return false;
}else{
return true;
}
}
//入队列
void inQueue(Queue &Q,int data){
queue p;
p=(queue)malloc(sizeof(qNode));//分配一个结点空间
p->data=data;
p->next=NULL;
Q.rear->next=p;//队尾指针指向p
Q.rear=p;//队尾指针后移一位
cout<<"数据 "<<data<<" 已经入队列"<<endl<<endl;
}
//出队列
void outQueue(Queue &Q,int &data){
queue p;
p=(queue)malloc(sizeof(qNode));//分配一个结点空间
p=Q.front->next;//指定一个新的结点指向队头指针指向的结点
data=p->data;//返回结点数据值
Q.front->next=p->next;//将p指向的下一数据给队头指针,令下一数据前移到队头
cout<<"数据 "<<data<<" 已经出队列"<<endl<<endl;
if(Q.rear==p){
Q.rear=Q.front;//使队尾指针回到初始位置
cout<<"请注意:队列中已经没有数据!"<<endl<<endl;//提示用户队列无数据
}
delete(p);//释放p所指结点空间
}
//输出链队列中储存的全部字符
void outQ(Queue Q){
queue q;//辅助变量
q=Q.front->next;
//如果链队列不为空,输出其全部字符
while(q){
cout<<q->data<<' ';
q=q->next;
}
cout<<endl;
}
int main(){
int data;//入队列的数据
int choose;//控制循环的执行
Queue Q;
initQueue(Q);
cout<<"请选择操作:"<<endl<<
"1.元素入队列"<<endl<<
"2.元素出队列"<<endl<<
"3.输出队列中的全部元素"<<endl<<
"0.退出"<<endl<<endl;
cin>>choose;
while(choose!=0){
switch(choose){
case 1:{
cout<<"请输入数据:";
cin>>data;
inQueue(Q,data);
break;
}
case 2:{
outQueue(Q,data);
break;
}
case 3:{
cout<<"链队列中全部的数据元素为:"<<endl;
outQ(Q);
cout<<endl;
break;
}
default:
cout<<"您输入的选择不正确,请重新进行选择!"<<endl;
}
cin>>choose;//输入选择
}
}
运行结果
写在最后:
读两遍下来,如果仍然有不清楚的地方,可在评论区留言。
如果你有其他感到困惑的问题,欢迎留言。