❤️《画解数据结构》全网最全队列总结,九张动图搞懂队列 ❤️(文末有投票)

Posted 英雄哪里出来

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了❤️《画解数据结构》全网最全队列总结,九张动图搞懂队列 ❤️(文末有投票)相关的知识,希望对你有一定的参考价值。

零、前言

  「 数据结构 」「 算法 」 是密不可分的,两者往往是「 相辅相成 」的存在,所以,在学习 「 数据结构 」 的过程中,不免会遇到各种「 算法 」
  到底是先学 数据结构 ,还是先学 算法,我认为不必纠结这个问题,一定是一起学的。
  数据结构 常用的操作一般为:「 增 」「 删 」「 改 」「 查 」。基本上所有的数据结构都是围绕这几个操作进行展开的。
  那么这篇文章,作者将用 「 九张动图 」 来阐述一种 「 先进先出 」 的数据结构

「 队列 」



🙉饭不食,水不饮,题必须刷🙉

C语言免费动漫教程,和我一起打卡!
🌞《光天化日学C语言》🌞

LeetCode 太难?先看简单题!
🧡《C语言入门100例》🧡

数据结构难?不存在的!
🌳《画解数据结构》🌳

LeetCode 太简单?算法学起来!
🌌《夜深人静写算法》🌌

   队列可以用 顺序表 实现,也可以用 链表 实现,浓缩为以下三张图:

队列操作三部曲

队列的链表实现

队列的顺序表实现

  看不懂没有关系,我会把它拆开来一个一个讲,首先来看一下今天要学习的内容目录。

一、概念

1、队列的定义

  队列 是仅限在 一端 进行 插入另一端 进行 删除线性表
  队列 又被称为 先进先出 (First In First Out) 的线性表,简称 FIFO 。

2、队首

  允许进行元素删除的一端称为 队首。如下图所示:

3、队尾

  允许进行元素插入的一端称为 队尾。如下图所示:

二、接口

1、可写接口

1)数据入队

  队列的插入操作,叫做 入队。它是将 数据元素队尾 进行插入的过程,如图所示,表示的是 插入 两个数据(绿色 和 蓝色)的过程:

2)数据出队

  队列的删除操作,叫做 出队。它是将 队首 元素进行删除的过程,如图所示,表示的是 依次 删除 两个数据(红色 和 橙色)的过程:

3)清空队列

  队列的清空操作,就是一直 出队,直到队列为空的过程,当 队首队尾 重合时,就代表队尾为空了,如图所示:

2、只读接口

1)获取队首数据

  对于一个队列来说只能获取 队首 数据,一般不支持获取 其它数据。

2)获取队列元素个数

  队列元素个数一般用一个额外变量存储,入队 时加一,出队 时减一。这样获取队列元素的时候就不需要遍历整个队列。通过 O ( 1 ) O(1) O(1) 的时间复杂度获取队列元素个数。

3)队列的判空

  当队列元素个数为零时,就是一个 空队空队 不允许 出队 操作。

三、队列的顺序表实现

1、数据结构定义

对于顺序表,在 C语言 中表现为 数组,在进行 队列的定义 之前,我们需要考虑以下几个点:
  1)队列数据的存储方式,以及队列数据的数据类型;
  2)队列的大小;
  3)队首指针;
  4)队尾指针;

  • 我们可以定义一个 结构体,C语言实现如下所示:
#define DataType int       // (1)
#define maxn 100005        // (2)

struct Queue {             // (3)
    DataType data[maxn];   // (4)
    int head, tail;        // (5)
};
  • ( 1 ) (1) (1)DataType这个宏定义来统一代表队列中数据的类型,这里将它定义为整型,根据需要可以定义成其它类型,例如浮点型、字符型、结构体 等等;
  • ( 2 ) (2) (2) maxn代表我们定义的队列的最大元素个数;
  • ( 3 ) (3) (3) Queue就是我们接下来会用到的 队列结构体
  • ( 4 ) (4) (4) DataType data[maxn]作为队列元素的存储方式,即 数组,数据类型为DataType,可以自行定制;
  • ( 5 ) (5) (5) head即队首指针,tail即队尾指针,head == tail代表空队;当队列非空时,data[head]代表了队首元素(而队尾元素是不需要关心的);

2、入队

1、动画演示

  如图所示,绿色元素 为新插入队尾的数据,执行完毕以后,队尾指针加一,队首指针不变。需要注意的是,顺序表实现时,队尾指针指向的位置是没有数据的,具体来看下代码实现。

2、源码详解

  • 入队 操作,算上函数参数列表,总共也才几句话,代码实现如下:
void QueueEnqueue(struct Queue *que, DataType dt) {  // (1)
    que->data[ que->tail ] = dt;                     // (2)
    que->tail = que->tail + 1;                       // (3)
}
  • ( 1 ) (1) (1) que是一个指向队列对象的指针,由于这个接口会修改队列对象的成员变量,所以这里必须传指针,否则,就会导致函数执行完毕,传参对象没有任何改变;
  • ( 2 ) (2) (2) 将传参的元素 入队
  • ( 3 ) (3) (3)队尾指针 自增 1;
  • 注意,这个接口在调用前,需要保证 队尾指针 小于 队列元素最大个数,即que->tail < maxn
  • 如果 C语言 写的熟练,我们可以把 ( 2 ) (2) (2) ( 3 ) (3) (3) 合成一句话,如下:
void QueueEnqueue(struct Queue *que, DataType dt) {
    que->data[ que->tail++ ] = dt;
}
  • que->tail++表达式的值是自增前的值,并且自身进行了一次自增。

3、出队

1、动画演示

  如图所示,橙色元素 为原先的 队首元素,执行 出队 操作以后,黃色元素 成为当前的 队首元素,执行完毕以后,队首指针加一。由于是线性表实现,队首元素前面的那些元素已经变成无效的了,具体来看下代码实现。

2、源码详解

  • 出队 操作,只需要简单的改变,将 队首指针 加一 即可,代码实现如下:
void QueueDequeue(struct Queue* que) {
    ++que->head;
}

4、清空队列

1、动画演示

  如图所示,对于数组来说,清空队列的操作只需要将 队首指针队尾指针 都置零 即可,数据不需要清理,下次继续 入队 的时候会将之前的内存重复利用。

2、源码详解

  • 清空队列的操作只需要将 队首指针队尾指针 都归零即可,代码实现如下:
void QueueClear(struct Queue* que) {
    que->head = que->tail = 0;
}

5、只读接口

  • 只读接口包含:获取队首元素、获取队列大小、队列的判空,实现如下:
DataType QueueGetFront(struct Queue* que) {
    return que->data[ que->head ];      // (1)
}
int QueueGetSize(struct Queue* que) {
    return que->tail - que->head;       // (2)
}
int QueueIsEmpty(struct Queue* que) {
    return !QueueGetSize(que);          // (3)
}
  • ( 1 ) (1) (1) que->head代表了 队首指针,即 队首下标,所以真正的 队首元素que->data[ que->head ]
  • ( 2 ) (2) (2) 因为只有在 入队 的时候,队尾指针 加一;出队 的时候,队首指针 加一;所以 队列元素个数 就是两者的差值;
  • ( 3 ) (3) (3)队列元素 个数为 零 时,队列为空。

6、队列的顺序表实现源码

  • 队列的顺序表实现的源码如下:
/**************************** 顺序表 实现队列 ****************************/
#define DataType int
#define maxn 100005

struct Queue {
    DataType data[maxn];
    int head, tail;
};

void QueueClear(struct Queue* que) {
    que->head = que->tail = 0;
}
void QueueEnqueue(struct Queue *que, DataType dt) {
    que->data[ que->tail++ ] = dt;
}
void QueueDequeue(struct Queue* que) {
    ++que->head;
}

DataType QueueGetFront(struct Queue* que) {
    return que->data[ que->head ];
}
int QueueGetSize(struct Queue* que) {
    return que->tail - que->head;
}
int QueueIsEmpty(struct Queue* que) {
    return !QueueGetSize(que);
}

/**************************** 顺序表 实现队列 ****************************/

四、队列的链表实现

1、数据结构定义

对于链表,在进行 队列的定义 之前,我们需要考虑以下几个点:
  1)队列数据的存储方式,以及队列数据的数据类型;
  2)队列的大小;
  3)队首指针;
  4)队尾指针;

  • 我们可以定义一个 队列结构体,C语言实现如下所示:
typedef int DataType;               // (1)
struct QueueNode;                   // (2)
struct QueueNode {                  // (3)
    DataType data;
    struct QueueNode *next;
};

struct Queue {
    struct QueueNode *head, *tail;  // (4)
    int size;                       // (5)
};
  • ( 1 ) (1) (1) 队列结点元素的 数据域,这里定义为整型;
  • ( 2 ) (2) (2) struct QueueNode;是对 链表结点 的声明;
  • ( 3 ) (3) (3) 定义链表结点,其中DataType data代表 数据域struct QueueNode *next代表 指针域
  • ( 4 ) (4) (4) head作为 队首指针tail作为 队尾指针
  • ( 5 ) (5) (5) 由于 求链表长度 的算法时间复杂度是 O ( n ) O(n) O(n) 的, 所以我们需要记录一个size来代表现在队列中有多少元素。每次 入队size自增,出队size自减。这样在询问 队列 的大小的时候,就可以通过 O ( 1 ) O(1) O(1) 的时间复杂度。

2、入队

1、动画演示

  如图所示,head队首元素tail队尾元素vtx 为当前需要 入队 的元素,即图中的 橙色结点入队 操作完成后,队尾元素 变为 vtx,即图中 绿色结点

2、源码详解

  • 入队 操作,其实就是类似 尾插法,往链表尾部插入一个新的结点,代码实现如下:
void QueueEnqueue(struct Queue *que, DataType dt) {
    struct QueueNode *insertNode = (struct QueueNode *) malloc( sizeof(struct QueueNode) );           
    insertNode->data = dt;                  // (1)
    insertNode->next = NULL;
    if(que->tail) {                         // (2)
        que->tail->next = insertNode;
        que->tail = insertNode;
    }else {
        que->head = que->tail = insertNode; // (3)
    }
    ++que->size;                            // (4)
}

  • ( 1 ) (1) (1) 利用malloc生成一个链表结点insertNode,并且填充 数据域指针域
  • ( 2 ) (2) (2) 如果当前 队尾 不为空,则将insertNode作为 队尾后继结点,并且更新insertNode作为新的 后继结点
  • ( 3 ) (3) (3) 否则,队首队尾 都为insertNode
  • ( 4 ) (4) (4) 队列元素 加一;

3、出队

1、动画演示

  如图所示,head队首元素tail队尾元素temp 为当前需要 出队 的元素,即图中的 橙色结点出队 操作完成后,队首元素 变为之前 head后继结点,即图中 绿色结点

2、源码详解

  • 出队 操作,由于链表头结点就是 队首,其实就是删除这个链表的头结点的过程。代码实现如下:
void QueueDequeue(struct Queue* que) {
    struct QueueNode *temp = que->head;  // (1)
    que->head = temp->next;              // (2)
    free(temp);                          // (3)
    --que->size;                         // (4)
    if(que->size == 0) {                 // (5)
        que->tail = NULL;
    } 
}
  • ( 1 ) (1) (1)队首 保存到temp中;
  • ( 2 ) (2) (2)队首后继结点 作为新的 队首
  • ( 3 ) (3) (3) 释放之前 队首 对应的内存;
  • ( 4 ) (4) (4) 队列元素减一;
  • ( 5 ) (5) (5) 当队列元素为空时,别忘了将 队尾 指针置空;

4、清空队列

1、动画演示

  清空队列 可以理解为:不断的 出队,直到 队列元素 个数为零为止。由于链表结点是动态申请的内存,所以在没有其它结点引用时,是需要释放内存的,不像数组那样直接将 队首指针队尾指针 置空就行的。

2、源码详解

  • 对于链表而言,清空队列 的操作需要删除每个链表结点,代码实现如下:
void QueueClear(struct Queue* que) {
    while(!QueueIsEmpty(que)) {     // (1)
        QueueDequeue(que);          // (2)
    }
}
  • ( 1 ) (1) (1) - ( 2 ) (2) (2) 的每次操作其实就是一个 出队 的过程,如果 队列 不为空;则进行 出队 操作,直到 队里 为空;

5、只读接口

  • 只读接口包含:获取队首元素、获取队列大小、队列的判空,实现如下:
DataType QueueGetFront(struct Queue* que) {
    return que->head->data;              // (1)
}
int QueueGetSize(struct Queue* que) {
    return que->size;                    // (2)
}
int QueueIsEmpty(struct Queue* que) {
    return !QueueGetSize(que);           // (3)
}

  • ( 1 ) (1) (1) que->head作为 队首指针,它的 数据域 data就是 队首元素的值,返回即可;
  • ( 2 ) (2) (2) size记录的是 队列元素 的个数;
  • ( 3 ) (3) (3)队列元素 个数为 零 时,队列为空。

6、队列的链表实现源码

/**************************** 链表 实现队列 ****************************/
typedef int DataType;

struct QueueNode;
struct QueueNode {
    DataType data;
    struct QueueNode *next;
};

struct Queue {
    struct QueueNode *head, *tail;
    int size;
};

void QueueEnqueue(struct Queue *que, DataType dt) {
    struct QueueNode *insertNode = (struct QueueNode *) malloc( sizeof(struct QueueNode) );
    insertNode->data = dt;
    insertNode->next = NULL;
    if(que->tail) {
        que->tail->next = insertNode;
        que->tail = insertNode;
    }else {
        que->head = que->tail = insertNode;
    }
    ++que->size;
}

void QueueDequeue(struct Queue* que) {
    struct QueueNode *temp = que->head;
    que->head = temp->next;
    free(temp);
    --que->size;
    if(que->size == 0) {
        que->tail = NULL;
    以上是关于❤️《画解数据结构》全网最全队列总结,九张动图搞懂队列 ❤️(文末有投票)的主要内容,如果未能解决你的问题,请参考以下文章

《画解数据结构》九张动图,画解队列

❤️《画解数据结构》七张动图,画解单调队列❤️(建议收藏)

❤️《画解数据结构》两万字,十张动图,画解双端队列❤️(建议收藏)

❤️《画解数据结构》两万字,十张动图,画解双端队列❤️(建议收藏)

❤️《画解数据结构》之 顺序表八大算法总结❤️(建议收藏)

❤️五万字《十大排序算法》动图讲解❤️(建议收藏)