avatar

队列的基本概念介绍以及典型应用示例

一、队列的概念介绍

        提到队列这个词,或许你不会感到陌生,在我们的生活中,应用到队列这个概念的场景非常之多。我们日常的排队买饭,总是第一个到达窗口的人先买到然后离开,后来的人总是后来离去;再有,去医院挂号排队,也总是遵循一般的先来先就医服务的原则。计算机中的队列数据结构的设计,也是为了更好地解决这类先来先服务问题的。
        队列是一种采用先来先服务(First in First Out,FIFO)思想的抽象数据结构。和栈一样,它也是一种受限制的线性表。队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。允许插入的一端称为队尾,允许删除的一端称为队头。队列的插入操作称之为入队列或进队列,队列的删除操作称之为退队列或出队列。当线性表中没有元素的时称为空队列。示意图如下:


栈示意图
        在队列上的基本操作有:①队列初始化②判断队空③入队操作④出队操作⑤读取队头元素⑥销毁队列。根据在物理实现方式上的不同,分为顺序队列和链式队列,基于数组实现的顺序存储的队列叫作顺序队列,基于链表实现的队列叫作链式队列。

二、顺序队列的操作实现

        采用顺序存储的队列称为顺序队列,在使用队列前,要分配一块连续的存储空间来存放队列里面的元素。由于队列的头尾都是会变化移动的,因此,就有队头队尾两个指示变量,指向队列的头尾。顺序队列的类型定义如下:

#define MAXSIZE 100
typedef struct{
    ElementType data[MAXSIZE];
    int front,rear;
}SeqQuene,* PSeqQueue;

        定义一个指向队列的指针:

1
PSeqQueue queue = (PSeqQueue)malloc(sizeof(SeqQuene));

        从队列的类型定义上可以看到,队列的数据区为 queue->data[0] ~ queue->data[MAXSIZE-1]。队头变量queue->front取值范围(0<= queue->front <=MAXSIZE-1),队尾变量queue->rear取值范围(0<= queue->rear <=MAXSIZE-1)。
        队列是使用数组进行元素的存储,它在内存分配上是静态分配,一旦初始化之后,内存空间的大小就确定了。而队列的操作是一个动态的过程,随着入队的进行,queue->rear的值会超过MAXSIZE,随着出队的进行,queue->front也会逐渐增加,就会导致数组空间实际上还有空闲的空间,但是不能进行元素的入队了,从而出现假溢出的现象。为了解决这种假溢出的问题,可以采用一种称为”循环队列”的特殊队列,循环队列是将队列的数据区data[0…MAXSIZE-1]看成头尾相接的循环结构,头尾的指示变量front、rear关系不变。循环队列的示意图如下:循环队列示意图
    对这种首尾相接的循环队列结构来说,进队出队的操作会有一些小的修改,以满足循环的需求。

入队: queue->rear = (queue->rear + 1) % MAXSIZE;
出队: queue->front = (queue->front +1) % MAXSIZE;

    了解了入队和出队的操作,我们需要判断,什么时候队列满了,什么时候队列为空。空的情况很好判断,只要 queue->front = queue->rear就可以认为队列为空。队满的情况的讨论,当队列中元素逐渐增多,queue->rear 会最终追上queue->front的,这时候两者也是相等,但此时队列非空,相反却是队满的情况,为了方便判断队列的队满队空情况,可以采用少用一个元素空间的方法,使得rear永远追不上front,也就是queue->front所指的位置不用,当 (rear+1)%MAXSIZE = front 的时候,队列是满的。

2.1 队列初始化

        队列在使用之前需要进行初始化。初始化的过程包括分配队列需要占用的内存空间,以及给队头队尾变量赋初值。

//初始化队列
PSeqQueue init_seqqueue(){
 //申请队列需要的内存空间
 PSeqQueue queue = (PSeqQueue)malloc(sizeof(SeqQueue));
 if(queue){
  queue->front = 0;
  queue->rear = 0;
 }
 return queue;
}

2.2 判断队列是否为空

        在出队或者获取队首元素的时候,都要进行队列非空判断

//判断队列是否为空。1代表空,0代表非空
int empty_seqqueue(PSeqQueue queue){
 if(queue && (queue->front == queue->rear)){
  return 1;
 }else{
  return 0;
 }
}

2.3 入队列

int in_seqqueue(PSeqQueue queue , ElementType x){
 //判断队列是否满了
 if((queue->rear+1)%MAXSIZE == queue->front){
  printf(“队列满了”);
   return 0;
 }else{
  //入队列.
  queue->rear = (queue->rear +1) % MAXSIZE;
  queue->data[queue->rear] = x;
  return 1;
 }
}

2.4 出队列

int out_seqqueue(PSeqQueue queue , ElementType *x){
 //出队列的时候要判断队列是否为空
 if(empty_seqqueue(queue)){
  printf(“队列为空,不可出队”);
  return 0;
 }else{
  //由于是循环队列,queue->front指示位置的元素不用,因此要+1才是要出队的元素
  queue->front = (queue->front + 1) % MAXSIZE;
  *x = queue->data[queue->front];
  return 1;
 }
}

2.5 读取队头元素

int getFront_seqqueue(PSeqQueue queue , ElementType *x){
 //出队列的时候要判断队列是否为空
 if(empty_seqqueue(queue)){
  printf(“队列为空,不可获取队头元素”);
  return 0;
 }else{
  //由于是循环队列,queue->front指示位置的元素不用,因此要+1才是队头的元素
  *x = queue->data[(queue->front+1)%MAXSIZE];
  return 1;
 }
}

2.6 销毁队列

        由于是动态申请的空间,因此,在使用完成之后,要进行内存的释放。

void destory_seqqueue(PSeqQueue queue){
 if(
queue){
  free(*queue);
 }
 *queue = NULL;
}

三、链式队列的操作实现

        队列是线性结构的一种,因此,也可以用链式结构的方式来实现。并且,链式结构的队列,由于节点空间都是在入队的时候动态申请的 ,因此,在计算机内存空间足够的情况下,一般不需要考虑队满的情况,也就不存在溢出的情况,所以不需要使用循环链式队列来处理假溢出的情况。 链式队列示意图如下:链式队列示意图
        链队节点的结构和单链表一样,同时为了操作上的方便,可以重新定义一个链队的结构体,包含有头尾指针指向队列的头部和尾部。链队的结构体描述如下:

//节点结构
typedef struct node{
 ElementType data;
 struct node next;
}Qnode,
PQnode;
//链栈结构
typedef struct {
 PQnode front,rear;
}LinkQueue,*PLinkqueue;

        定义一个指向链队的指针

1
PLinkqueue queue = (PLinkqueue)malloc(sizeof(LinkQueue));

        在使用链表来存储队列数据的时候,带不带头结点完全看我们的需求,如果在操作上需要使用头结点 来方便统一操作,就是用头结点 ,否则就可以不用加头结点。
        queue->front指向链队的队头元素,queue->rear指向链队的队尾元素。出队的时候删除链表的首元结点,让后继节点成为首元节点,然后修改队头指针使其指向新的首元节点即可,入队是在链表末尾插入元素,然后修改队尾指针指向新的链尾即可。

3.1 初始化链式队列

        队列的初始化就是为队列结构分配内存空间,同时给front、rear指针置空值,为入队列做准备。

PLinkqueue init_linkqueue(){
PLinkqueue queue = (PLinkqueue)malloc(sizeof(LinkQueue));
 if(queue){
  queue->front = NULL;
  queue->rear = NULL;
 }
 return queue;
}

3.2 判断队列是否为空

//判断链队为空;1空队列,0非空。
int empty_linkqueue(PLinkqueue queue){
 if(queue && (queue->front == NULL) && (queue->rear ==NULL)){
  return 1;
 }else{
  return 0;
 }
}

3.3 入队

        链式队列的入队,就是在链表的链尾插入元素,然后让队尾指针指向链尾元素。

int in_linkqueue(PLinkqueue queue, ElementType x){
 PQnode p = (PQnode)malloc(sizeof(Qnode));
 if(!p){
  printf(“内存溢出,不能申请新的节点空间\n”);
  return 0;
 }
 p->data = x;
 p->next = NULL;
 //要先判断队列中是否有元素,两者的的入队是有一些差别的
 if(empty_linkqueue(queue)){
  queue->rear = p;
  queue->front = p;
 }else{
  //入队的时候,在队尾插入元素
  queue->rear->next = p;
  queue->rear = p;
 }
 return 1;
}

3.4 出队

        链式队列的出队,就是将链表的首元节点从链表中删去,让其后继节点成为首元节点,然后让队头指针指针指向该节点。

int out_linkqueue(PLinkqueue queue, ElementType *x){
 if(empty_linkqueue(queue)){
  printf(“队空,无法出队\n”);
  return 0;
 }
 //出队首元素节点
 *x = queue->front->data;
 PQnode temp_front= queue->front;
 //队首的下一个元素作为队首
 queue->front = temp_front->next;
 //释放出队节点
 free(temp_front);
 //如果队首为空,说明此时队列中没有元素节点了,则设置队尾也为空。
 if(!queue->front){
  queue->rear = NULL;
 }
 return 1;
}

3.5 获取队头元素

        即获取front指针指向的节点。

int getFront_linkqueue(PLinkqueue queue, ElementType *x){
 if(empty_linkqueue(queue)){
  printf(“队空,无法获取队首 元素\n”);
  return 0;
 }
 *x = queue->front->data;
 return 1;
}

3.6 销毁队列

        由于动态分配的内存空间 ,在使用完队列之后,要把申请的空间及时的释放掉。具体做法就是遍历单链表,释放每一个节点。

//销毁队列。由于使用的是链表,因此,要释放每一个链表节点的空间
void destory_linkqueue(PLinkqueue queue){
 PQnode p;
 if(
queue){
  while((queue)->front){
  p = (
queue)->front;
  (queue)->front = (queue)->front->next;
  free(p );
 }
  free(*queue);
 }
 *queue = NULL;
}

四、典型应用示例

        有n个元素存储在数组A[n]中,设计一个算法,实现将这n个元素循环向左移动k(0<k<n)位。
        思路分析:将数组的A[0] ~ A[k-1]元素先顺序放入一个队列中,然后再将数组A[k] ~A[n-1]元素依次左移k位,然后再将队列中保存的数组元素A[0] ~ A[k-1]顺序出队列,依次放入A[n-k] ~A[n-1]的位置。具体的算法如下:

void array_leftcircle_move(int a[], int n, int k){
 int i;
 PLinkqueue queue = init_linkqueue();
 for(i=0;i<k;i++){
  in_linkqueue(queue,a[i]);
 }
 for(i=k;i<n;i++){
  a[i-k] = a[i];
 }
 i = n-k;
 while(!empty_linkqueue(queue)){
  out_linkqueue(queue,&a[i]);
  i++;
 }
}

完整的程序源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#include<stdio.h>
#include<stdlib.h>

typedef int ElementType;
//节点结构
typedef struct node{
ElementType data;
struct node *next;
}Qnode,*PQnode;
//链栈结构
typedef struct {

PQnode front, rear;
}LinkQueue,*PLinkqueue;

//链队初始化
PLinkqueue init_linkqueue(){
PLinkqueue queue = (PLinkqueue)malloc(sizeof(LinkQueue));
if(queue){
queue->front = NULL;
queue->rear = NULL;
}
return queue;
}

//判断链队为空;1空队列,0非空
int empty_linkqueue(PLinkqueue queue){
if(queue && (queue->front==NULL) && (queue->rear==NULL)){
return 1;
}else{
return 0;
}
}

//入队列

int in_linkqueue(PLinkqueue queue, ElementType x){
PQnode p = (PQnode)malloc(sizeof(Qnode));
if(!p){
printf("内存溢出,不能申请新的节点空间\n");
return 0;
}
p->data = x;
p->next = NULL;
//要先判断队列中是否有元素,两者的的入队是有一些差别的
if(empty_linkqueue(queue)){
queue->rear = p;
queue->front = p;
}else{
//入队的时候,在队尾插入元素
queue->rear->next = p;
queue->rear = p;
}
return 1;
}

//出队
int out_linkqueue(PLinkqueue queue, ElementType *x){
if(empty_linkqueue(queue)){
printf("队空,无法出队\n");
return 0;
}

//出队首元素节点
*x = queue->front->data;
PQnode temp_front= queue->front;

//队首的下一个元素作为队首
queue->front = temp_front->next;
//释放出队节点
free(temp_front);
//如果队首为空,说明此时队列中没有元素节点了,则设置队尾也为空。
if(!queue->front){
queue->rear = NULL;
}
return 1;
}

//读取队首元素
int getFront_linkqueue(PLinkqueue queue, ElementType *x){
if(empty_linkqueue(queue)){
printf("队空,无法获取队首 元素\n");
return 0;
}
*x = queue->front->data;
return 1;
}

//销毁队列。由于使用的是链表,因此,要释放每一个链表节点的空间
void destory_linkqueue(PLinkqueue *queue){
PQnode p;
if(*queue){
while((*queue)->front){
p = (*queue)->front;
(*queue)->front = (*queue)->front->next;
free(p);
}
free(*queue);
}
*queue = NULL;
}

void array_leftcircle_move(int a[], int n, int k){
int i;
PLinkqueue queue = init_linkqueue();

for(i=0;i<k;i++){

in_linkqueue(queue,a[i]);
}
for(i=k;i<n;i++){

a[i-k] = a[i];
}

i = n-k;
while(!empty_linkqueue(queue)){

out_linkqueue(queue,&a[i]);
i++;
}
}

int main(){

int a[10], i, k = 3;

for(i=0;i<10;i++){

a[i] = i+1;

}

array_leftcircle_move(a,10,k);

for(i=0;i<10;i++){

printf("%d ",a[i]);

}
printf("\n");

return 0;

}
文章作者: IT之旅
文章链接: https://www.meijindong.top/posts/2450824891.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 程序员在旅途
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论