0
点赞
收藏
分享

微信扫一扫

构建高效学习组织:企业培训体系系统架构的全面解析

Greatiga 2天前 阅读 1
数据结构

1、队列的定义

        在用电脑时机器有时会处于疑似死机的状态,鼠标点什么似乎都没用,双击任何快捷方式都不动弹。突然它像酒醒了一样,把刚才点击的所有操作全部都按顺序执行了一遍。这是因为操作系统中的多个程序因需要通过一个通道输出,而按先后次序排队等待造成的。客服也是同样的道理,所有的客服人员都占线的情况下,客户被要求等待,直到有空余的客服人员才能让最先等待的客户接通电话。

操作系统和客服系统就是采用了先进先出的功能,就是队列。

  • 特点:队列是一种先进先出(First In First Out)的线性表,简称FIFO。

  • 允许插入的一端称为队尾,允许删除的一端称为队头。

 

2、队列的抽象数据类型

队列的插入数据只能在队尾进行,删除数据只能在对头进行

 

2、循环队列(顺序存储结构)

2.1 为什么要引入循环队列?

2.1.1入队

        假设一个队列有n个元素,则顺序存储的队列需建立一个大于n的数组,并把队列的所有元素存储在数组的前n个单元,数组下标为0的一端即是队头。所谓的入队列操作,其实就是在队尾追加一个元素,不需要移动任何元素,因此时间复杂度=O(1)

  1.  

2.1.2 出队

出队有两种方式:

方式一:队列元素的出列是在队头,即下标为0的位置,如果出列,那么队列中的所有元素都得向前移动,保证队列的队头(下标为0的位置)不为空,此时时间复杂度为O(n)。如下图所示

方式二:出列的时候还有另一种方法就是:队头向后移动,元素不动。队头不再固定在下标为0的地方,而是可动的。

  为了便于操作,引入两个指针:front(队头)和rear(队尾的下一个位置)

但是存在一个问题:如果数组在4的位置已经存在数据,然后front的位置也在向后移动,此时再加入数据时rear只能指向数组之外。但是在front之前的位置还是空闲的,管这种现象叫假溢出现象为了解决这个问题,引入了循环队列。顾名思义就是rear在指向最后一个位置的时候,如果再加入数据,就移动到下边为0的位置(再从头开始)。

2.3 循环队列

定义:队列的头尾相接的顺序存储结构

2.3.1 图解

假溢出问题解决后,还有一个问题:如何判断空队列和满队列?

  1. 办法一:设置标志遍历flag,当flag == rear&&flag=0 时为空队列等类似的

  2. 办法二:队列空:front == rear 。队列满:保留一个空闲空间,比如5个空间,占了4个就代表满了,避免5个空间都有数据存在。下面使用的是这种方法实现判断满队列

2.3.2 结构体
typedef int DATATYPE;        //队列中存储数据的类型
typedef struct  {
        DATATYPE *array;    //指向存储队列元素的数组的指针。
        int tlen;//队列的总长度,即数组的大小。
        int head;//头指针,指向队列中的第一个元素。
        int tail;//尾指针,指向下一个将要插入元素的位置。
}SeqQueue;
2.3.3 流程
2.3.3.1 创建空队列
// 创建一个新的顺序队列并返回指针
SeqQueue *CreateSeqQueue(int len)
{
    // 分配内存空间给顺序队列结构体
    SeqQueue* sq = (SeqQueue*)malloc(sizeof(SeqQueue));    
    if(NULL == sq)
    {
        perror("CreateSeqQueue malloc");
        return NULL;
    }

    // 分配内存空间给顺序队列的数据数组
    sq->array = (DATATYPE*)malloc(sizeof(DATATYPE) * len);
    if(NULL == sq->array)
    {
        perror("CreateSeqQueue");
        // 如果分配失败,释放顺序队列结构体的内存空间并返回NULL
        free(sq);
        return NULL;
    }
    
    // 初始化顺序队列的长度、头指针和尾指针
    sq->tlen = len;
    sq->head = 0;
    sq->tail = 0;
    return sq;
}
2.3.3.2 销毁队列
// 销毁顺序队列
int DestroySeqQueue(SeqQueue *queue)
{
    // 释放顺序队列数据数组的内存空间
    free(queue->array);
    // 释放顺序队列结构体的内存空间
    free(queue);
    return 0;
}
2.3.3.3 入队
// 入队操作
int EnterSeqQueue(SeqQueue *queue, DATATYPE *data)
{
    // 如果队列已满,返回1表示失败
    if(IsFullSeqQueue(queue))
    {
        return 1;
    }
    // 将数据拷贝到队列的尾部,并更新尾指针
    memcpy(&queue->array[queue->tail], data, sizeof(DATATYPE));
    queue->tail = (queue->tail + 1) % queue->tlen;
    return 0;
}
2.3.3.4 出队
// 出队操作
int QuitSeqQueue(SeqQueue *queue)
{
    // 如果队列为空,返回1表示失败
    if(IsEmptySeqQueue(queue))
    {
        return 1;
    }
    // 移动头指针
    queue->head = (queue->head + 1) % queue->tlen;
    return 0;
}
2.3.3.5 是否空
// 判断队列是否为空
int IsEmptySeqQueue(SeqQueue *queue)
{
    return queue->head == queue->tail;
}
2.3.3.6 是否满

当尾指针 tail 的下一个位置等于头指针 head 时,表示队列已满。在这种情况下,为了避免与队列为空的情况混淆,保留了一个空闲空间,使得队列最多只能存储 len - 1 个元素,其中 len 是队列的总长度。

// 判断队列是否已满
int IsFullSeqQueue(SeqQueue *queue)
{
    return (queue->tail + 1) % queue->tlen == queue->head;
}
2.3.3.7 获取头元素
// 获取队列头部元素的指针
DATATYPE* GetHeadSeqQueue(SeqQueue* queue)
{
    // 如果队列为空,返回NULL
    if(IsEmptySeqQueue(queue))
        return NULL;
    // 否则返回头指针所指向的元素的指针
    return &queue->array[queue->head];
}

3、链式队列(链式存储结构)

3.1 定义

队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已我们把它简称为链队列。

3.2 图解

 

3.3 链队结构

typedef int DATATYPE; // 定义数据类型别名为 int

typedef struct link_que_node {
    DATATYPE data;            // 数据域,存储具体数据
    struct link_que_node *next; // 指针域,指向下一个节点
} QueueNode; // 定义链式队列节点结构体别名为 QueueNode

typedef struct {
    QueueNode *head; // 指向队列头部的指针
    int clen;        // 记录队列长度
    QueueNode *tail; // 指向队列尾部的指针
} LinkQueue; // 定义链式队列结构体别名为 LinkQueue

3.4 流程

3.4.1 创建空队列

// 创建一个新的链式队列并返回指针
LinkQueue *CreateLinkQueue()
{
    // 分配内存空间给链式队列结构体
    LinkQueue* lq = (LinkQueue*)malloc(sizeof(LinkQueue));
    if(NULL == lq)
    {
        perror("CreateLinkQueue malloc");
        return NULL;
    }
    // 初始化链式队列的头指针、尾指针和长度
    lq->head = NULL;
    lq->tail = NULL;
    lq->clen = 0;
    return lq;
}

3.4.2 销毁队列

// 销毁链式队列
int DestroyLinkQueue(LinkQueue *queue)
{
    // 循环弹出队列中的元素直到队列为空
    while(!IsEmptyLinkQueue(queue))
    {
        QuitLinkQueue(queue);
    }
    // 释放链式队列结构体的内存空间
    free(queue);
    return 0;
}

3.4.3 入队

在链表尾部插入节点

 

// 入队操作
int EnterLinkQueue(LinkQueue *queue, DATATYPE *data)
{
    // 分配内存空间给新的节点
    QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
    if(NULL == newnode)
    {
        perror("EnterLinkQueue malloc");
        return 1;
    }
    // 将数据拷贝到新节点中
    memcpy(&newnode->data, data, sizeof(DATATYPE));
    newnode->next = NULL;
    // 如果队列为空,设置头尾指针为新节点
    if(IsEmptyLinkQueue(queue))
    {
        queue->head = newnode;
        queue->tail = newnode;
    }
    else 
    {
        // 否则将新节点插入到队列尾部并更新尾指针
        queue->tail->next = newnode;
        queue->tail = newnode;
    }
    // 队列长度加一
    queue->clen++;
    return 0;
}

3.4.4 出队

出队操作时,就是头结点的后继结点出队,将头结点的后继改为它后面的结点。若链表头结点为NULL,则尾节点也为空

// 出队操作
int QuitLinkQueue(LinkQueue *queue)
{
    // 如果队列为空,返回1表示失败
    if(IsEmptyLinkQueue(queue))
    {
        return 1;
    }
    // 保存头指针指向的节点,并更新头指针
    QueueNode* tmp = queue ->head ;
    queue->head = queue->head->next ;
    // 释放原头节点的内存空间
    free(tmp);
    // 队列长度减一
    queue->clen--;
    // 如果队列头部为空,则尾部也置为空
    if(NULL == queue->head)
    {
        queue->tail = NULL;
    }
    return 0;
}

3.4.5 获取头部元素

// 获取链式队列头部元素的指针
DATATYPE* GetHeadLinkQue(LinkQueue*queue)
{
    // 如果队列为空,返回NULL
    if(IsEmptyLinkQueue(queue))
    {
        return NULL;
    }
    // 否则返回头指针所指向的元素的指针
    return &queue->head->data;
}

3.4.6 是否空

// 判断队列是否为空
int IsEmptyLinkQueue(LinkQueue *queue)
{
    return 0 == queue->clen;
}
举报

相关推荐

0 条评论