4-队列

队列

董泽平 2019/8/9

如何理解队列?

队列这个概念非常好理解。你可以把它理解成排队买火车票,购票的人只能从队伍的最后面进入,并且每次出去的总是队头的那个。

我们知道栈支持入栈和出栈的操作,和栈类似,队列支持入队和出队操作。最基本的队列有两大类,顺序队列链式队列,顺序队列又可以细分为两类,普通顺序队列循环队列

接下来,就让我们一一分析这几大类队列吧。

第一类:顺序队列

原理:顺序队列是用数组模拟的,并且添加两个哨兵front和rear(大学课本称之为头指针和尾指针),front指向队列第一个元素,rear指向队列的最后一个元素(有的课本,或者网上把队尾指针指向了最后一个元素的下一个)其实队尾指针指向最后一个还是指向最后一个元素的下一个,都是可以的,没有标准的规范。只不过,当我们rear指向不同,最后判断队列满了的条件就不同了。

queue1.jpg

看上面的演示,队列头front一直指向队列头a,队尾rear指向的是队列元素的最后一个元素

queue2.jpg

上图演示的是我入队列元素e后的情况,直接将它放在最后,更新rear指针指向它

queue18.jpg

上图是继续出队一个元素,此时front指向了新的队头b。

下面是顺序队列代码(支持自动扩容)

//队列初始化
void Queue_Init(struct Queue * queue)
{
    queue->front = 0;
    queue->rear = 0;
    queue->size = 1;
    queue->data = (int*)malloc(sizeof(int)*queue->size);
}
//入队列(队尾指向队列最后一个元素下一个位置)
void inQueue(struct Queue * queue, int data)
{
    //队列满了,自动扩容
    if (queue->rear == queue->size)
    {
        int temp;
        temp = queue->size;
        queue->size = queue->size * 2;
        int *q = (int*)malloc(sizeof(int)*queue->size);
        for (int i = queue->front; i < queue->rear; i++)
        {
            q[i] = queue->data[i];
        }
        free(queue->data);
        queue->data = NULL;
        queue->data = q;
        queue->data[queue->rear++] = data;
    }
    else {
        queue->data[queue->rear++] = data;
    }
}
//出队列元素
int outQueue(struct Queue * queue)
{
    if (queue->front == queue->rear)
    {
        return 999;
    }
    else {
        return queue->data[queue->front++];
    }
}

总结

  • 我们让队首指针总是指向队列的头(第一个元素),队尾指针总是指向队列的最后元素或者最后一个元素的下一个位置。
  • 此处的指针在实际代码中并不是指针,就是一个下标的标记,说指针是为了形象
  • 头指针指向队列头,队尾指针为什么要指向最后一个元素的下一个?
    这个不是必要条件,你完全可以自己设置队尾指针指向最后一个元素的。
  • 我们还会发现,这种队列存在"假溢出"现象,即随着元素的不断出队列,然后我们入队元素时,只能在后面进入,队列前面的空间无法被使用。

第二类:顺序队列(改进版)

queue3.jpg

上图所示,当队列出现假溢出到元素无法入队列,这个状态十分不乐观,明明前面有空间,可是现在队列就是满额状态,元素无法入队列。此时,我们就需要对顺序队列进行升级,下面升级策略。

  1. 我们可以先判断队头指针是否指向了数组下标0的元素,队头指针指向了0,且队尾指针指向了数组最后,那就证明队列是全满状态,确实无法继续入队列了,如下图。
queue19.jpg

2.如果队头指针指向不是0,而队尾指针指向了最后,证明存在假溢出现象,就是指前面还有空间可以入队列,此时我们可以将数据全部搬移到头部去,并修改队列的头指针和尾指针指向,具体操作如下图所示.

queue20.jpg

TIP.经过上述的操作,我们实现了空间的高效利用,但是带来了严重的性能问题,因为当发生了假溢出现象时,我们需要做数据搬移,这个时候,入队列的事件复杂度由之前的0(1)改变成了0(n),而根本问题就出在队列的数据搬迁问题上,我们继续探究。

队列升级版代码

//完整的入队操作       
void Queue_push(struct Queue *q,int data)
{
    if (q->rear == q->size)
    {
        //队列空间全利用--自动扩容
        if (q->front == 0)
        {
            q->size = 2 * q->size;
            int temp = q->size;
            int *p = (int*)malloc(sizeof(int)*q->size);
            for (int i = 0; i < temp; i++)
            {
                p[i] = q->data[i];
            }
            free(q->data);
            q->data = p;
            q->data[q->rear++] = data;
        }
        //队列空间出现假溢出,数据搬迁至前面
        else {
            int count = 0;
            for (int i = 0; i < (q->rear - q->front); i++)
            {
                q->data[i] = q->data[q->front++];
                count++;
            }
            q->front = 0;
            q->rear = count;
        }
    }
    else {
        q->data[q->rear++] = data;
    }
}

第三类:顺序队列(终极版本-循环队列)

上面我们依次讲了顺序队列存在假溢出的问题,并讲解了它的升级版,完美解决了它的假溢出问题,可是最后我们发现升级版的队列又出现了数据搬迁的问题,让原本入队列的操作变得复杂起来,这一节,我们将退出完美版本的顺序队列---循环队列。

queue21.jpg

循环队列完美解决了上面的两个问题,队列的假溢出现象和数据搬移带来的事件复杂度问题。上图是一个空的循环队列。

注意点:

前面我们讲front指向队列的头,rear指向队列的最后一个元素或者最后一个元素的下一个位置,到了循环队列,就变得严格了,它的front还是指向队列的第一个元素,它的尾巴rear只能是指向队列最后一个元素的下一个位置。这样设计的原因:为了区分队列空和满的条件.看下面的分析

queue22.jpg

比如说,我们在上面空循环队列基础上,添加元素a,b,c,d,如上图所示,此时rear指针指向了下标为4的,我们就可以认为队列为满,如果说我们让规则改变,让rear指针指向队列最后元素的位置,现在我们如果在一个空队列基础加入一个元素,此时front=rear都指向了这个元素,但是初始的队列空的条件也是front=rear,如何区分,所以循环队列的rear指向是严格的。

循环队列的判定

  • 下列公式的size是队列的长度,比如上图的size就是5
  • 队列空的条件fornt==rear
  • 队列满的条件(rear+1)%size = front
  • 队列当前元素个数(rear-front+size)%size
  • 入队操作rear = (q->rear + 1) % q->size;
  • 出对操作q->front = (q->front + 1) % q->size;
  • 综上,循环队列存储满时,会空出一个空间没有利用。(为了标记队列是否满)

第四类:链队

链队就是在链表的基础上模拟的,入队就是将元素添加到链表的尾部,出队列就是删除链表的第一个元素节点。

1.链表入队

queue5.jpg

2.链表出队

queue23.jpg

链队代码

//初始化
void Link_Queue_Init(struct Link_Queue *q)
{
    q->head = (struct Node*)malloc(sizeof(struct Node));
    q->head->next = NULL;
    q->front = q->head->next;
    q->rear = q->head;
}
//入队
void inQueue(struct Link_Queue *q, int data)
{
    struct Node *temp,*p;
    temp = (struct Node*)malloc(sizeof(struct Node));
    temp->data = data;
    temp->next = q->rear->next;
    q->rear->next = temp;
    q->rear = temp;
    q->front = q->head->next;
}
//出队
int outQueue(struct Link_Queue *q)
{
    if (q->front == NULL)
    {
        return 999;
    }
    else {
        struct Node *temp;
        temp = q->front;
        q->head->next = temp->next;
        free(temp);
        q->front = q->head->next;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容

  • 数据结构 第7讲 循环队列 过了一段时间,小张再也受不了...
    rainchxy阅读 8,916评论 4 16
  • 队列 队列的基本概念 队列是一种操作受限的线性表,只允许在表的一端进行插入,而在表的另一端进行删除;向队列中插入元...
    ribose阅读 586评论 0 2
  • 一、栈 1.1 栈的定义 栈(Stack):只允许在一端进行插入或删除操作的线性表。首先栈是一种线性表,但是限定这...
    末雨潮声阅读 623评论 0 0
  • 队列的基本概念 1 队列的定义 队列(Queue):队列简称队,也是一种操作受限的线性表,只允许在表的一端进行插入...
    1nvad3r阅读 481评论 0 0
  • 栈与列队 栈是限定仅在表尾进行插入和删除操作的线性表 队列是只允许在一端进行插入操作、而在另一端进行删除操作的线性...
    Longshihua阅读 1,123评论 0 3