数据结构—队列

队列是线性表的一种,在操作数据元素时,和栈一样,有自己的规则:使用队列存取数据元素时,数据元素只能从表的一端进入队列,另一端出队列,如图。


队列.png

称进入队列的一端为“队尾”;出队列的一端为“队头”。数据元素全部由队尾陆续进队列,由队头陆续出队列。

队列的先进先出原则

队列从一端存入数据,另一端调取数据的原则称为“先进先出”原则。(first in first out,简称“FIFO”)
图1中,根据队列的先进先出原则,(a1,a2,a3,…,an)中,由于 a1 最先从队尾进入队列,所以可以最先从队头出队列,对于 a2 来说,只有 a1 出队之后,a2 才能出队。

队列的实现方式

队列的实现同样有两种方式:顺序存储和链式存储。
两者的区别同样在于数据元素在物理存储结构上的不同。

队列的顺序表示和实现

使用顺序存储结构表示队列时,首先申请足够大的内存空间建立一个数组,除此之外,为了满足队列从队尾存入数据元素,从队头删除数据元素,还需要定义两个指针分别作为头指针和尾指针。

当有数据元素进入队列时,将数据元素存放到队尾指针指向的位置,然后队尾指针增加 1;
当删除对头元素(即使想删除的是队列中的元素,也必须从队头开始一个个的删除)时,只需要移动头指针的位置就可以了。

顺序表示是在数组中操作数据元素,由于数组本身有下标,所以队列的头指针和尾指针可以用数组下标来代替,
既实现了目的,又简化了程序。

如,将队列(1,2,3,4)依次入队,然后依次出队并输出。

#include <stdio.h>
int enQueue(int *a,int rear,int data){
    a[rear]=data;
    rear++;
    return rear;
}
void deQueue(int *a,int front,int rear){
    //如果 front==rear,表示队列为空
    while (front!=rear) {
        printf("%d",a[front]);
        front++;
    }
}
int main() {
    int a[100];
    int front,rear;
    //设置队头指针和队尾指针,当队列中没有元素时,队头和队尾指向同一块地址
    front=rear=0;
    //入队
    rear=enQueue(a, rear, 1);
    rear=enQueue(a, rear, 2);
    rear=enQueue(a, rear, 3);
    rear=enQueue(a, rear, 4);
    //出队
    deQueue(a, front, rear);
    return 0;
}

顺序存储存在的问题

当使用线性表的顺序表示实现队列时,由于按照先进先出的原则,队列的队尾一直不断的添加数据元素,队头不断的删除数据元素。由于数组申请的空间有限,到某一时间点,就会出现 rear 队列尾指针到了数组的最后一个存储位置,如果继续存储,由于 rear 指针无法后移,就会出错。

在数组中做删除数据元素的操作,只是移动了队头指针而没有释放所占空间。

队头由于删除元素,front 后移, front 前边还会有可以使用的空间。所以为了充分利用这部分空间,可以考虑使用下面这种方式。

顺序存储的升级版
使用数组存取数据元素时,可以将数组申请的空间想象成首尾连接的环状空间使用。例如,在申请的内存空间大小为 5 的情况下,将数字 1-6 进队后再出队(普通方式中 6 是无法进队的):

#include <stdio.h>
#define max 5
int enQueue(int *a,int front,int rear,int data){
    //循环队列中,如果尾指针和头指针重合,证明数组存放的数据已满
    if ((rear+1)%max==front) {
        printf("空间已满");
        return rear;
    }
    a[rear%max]=data;
    rear++;
    return rear;
}
int  deQueue(int *a,int front,int rear){
    //如果front==rear,表示队列为空
    if(front==rear) {
        printf("队列为空");
        return front;
    }
    printf("%d",a[front]);
    front=(front+1)%max;// 如果指针超出了范围将返回到初始位置从头开始
    return front;
}
int main() {
    int a[max];
    int front,rear;
    //设置队头指针和队尾指针,当队列中没有元素时,队头和队尾指向同一块地址
    front=rear=0;
    //入队
    rear=enQueue(a,front,rear, 1);
    rear=enQueue(a,front,rear, 2);
    rear=enQueue(a,front,rear, 3);
    rear=enQueue(a,front,rear, 4);
    //出队
    front=deQueue(a, front, rear);
   
    rear=enQueue(a,front,rear, 5);
   
    front=deQueue(a, front, rear);
    rear=enQueue(a,front,rear, 6);
    front=deQueue(a, front, rear);
    front=deQueue(a, front, rear);
    front=deQueue(a, front, rear);
    front=deQueue(a, front, rear);
    return 0;
}
image.png

在使用循环队列判断数组是否已满时,出现下面情况:

  • 当队列为空时,队列的头指针等于队列的尾指针
  • 当数组满员时,队列的头指针等于队列的尾指针

要将空队列和队列满的情况区分开,办法是:牺牲掉数组中的一个存储空间,
判断数组满员的条件是:尾指针的下一个位置和头指针相遇,就说明数组满了。

队列的链式表示和实现——“链队列”

队列的链式存储是在链表的基础上,按照“先进先出”的原则操作数据元素。

如,将队列(1,2,3,4)依次入队,然后再依次出队。

#include <stdio.h>
#include <stdlib.h>
typedef struct QNode{
    int data;
    struct QNode * next;
}QNode;
QNode * initQueue(){
    QNode * queue=(QNode*)malloc(sizeof(QNode));
    queue->next=NULL;
    return queue;
}
QNode* enQueue(QNode * rear,int data){
    QNode * enElem=(QNode*)malloc(sizeof(QNode));
    enElem->data=data;
    enElem->next=NULL;
    //使用尾插法向链队列中添加数据元素
    rear->next=enElem;
    rear=enElem;
    return rear;
}
void DeQueue(QNode * front,QNode * rear){
    if (front->next==NULL) {
        printf("队列为空");
        return ;
    }
    QNode * p=front->next;
    printf("%d\n",p->data);
    front->next=p->next;
    if (rear==p) {
        rear=front;
    }
    free(p);
}
int main() {
    QNode * queue,*front,*rear;
    queue=front=rear=initQueue();//创建头结点
    //向链队列中添加结点,使用尾插法添加的同时,队尾指针需要指向链表的最后一个元素
    rear=enQueue(rear, 1);
    rear=enQueue(rear, 2);
    rear=enQueue(rear, 3);
    rear=enQueue(rear, 4);
    //入队完成,所有数据元素开始出队列
    DeQueue(front, rear);
    DeQueue(front, rear);
    DeQueue(front, rear);
    DeQueue(front, rear);
    DeQueue(front, rear);
    return 0;
}

image.png

链队列注意事项

在使用链队列时,最简便的方法就是链表的表头一端表示队列的队头,表的另一端表示队列的队尾,这样的设置会使程序更简单。

反过来的话,队列在增加元素的时候,要采用头插法,在删除数据元素的时候,由于要先进先出,
需要删除链表最末端的结点,就需要将倒数第二个结点的next指向NULL,这个过程是需要遍历链表的。

另外需要注意的是,在删除队列中数据元素的时候,每次都需要判断队列是否为空,这就需要寻找一个判断队列为空的条件:
1、如果头结点的指针域为NULL,说明队列为空。
2、如果队头和队尾指针都指向头结点,说明队列为空。

使用链队列解决问题时,要避免“野指针”的出现:

  1. 当删除最后一个数据元素时,由于一贯地认为数据元素出队列只跟队头指针有关系,会忽略队尾指针。
  2. 当链队列中只剩有一个数据元素时,队尾指针指向的就是这个数据元素,被删除后,队尾指针指向的内存空间被释放,还有可能给别的程序使用。这时候,队尾指针如果不进行重定义,就会变成“野指针”。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容