3.5 队列的表示和操作的实现

3.5 队列的表示和操作的实现_第1张图片

思维导图:
3.5 队列的表示和操作的实现_第2张图片

 3.5 队列的表示和操作的实现_第3张图片3.5.1 队列类型 

3.5.1 队列的类型定义

1. 简介

  • 队列是一种特殊的线性表,它的特性是只能在表的一端进行插入操作,而在另一端进行删除操作。
  • 通常将允许插入操作的一端称为队尾,允许删除操作的一端称为队头。

2. 抽象数据类型定义

ADT Queue

数据对象:D = {a₁, a₂, ..., an | ai∈ElemSet, i=1,2,…,n, n≥0}

数据关系:R = { | ai-1, aj∈D, i=2,…,n}

约定:其中 a₁ 为队头, an 为队尾。

基本操作

  • InitQueue(&Q)

    • 操作结果:构造一个空队列Q。
  • DestroyQueue(&Q)

    • 初始条件:队列Q已存在。
    • 操作结果:销毁队列Q。
  • ClearQueue(&Q)

    • 初始条件:队列Q已存在。
    • 操作结果:将Q清为空队列。
  • QueueEmpty(Q)

    • 初始条件:队列Q已存在。
    • 操作结果:若Q为空队列,返回true;否则,返回false。
  • QueueLength(Q)

    • 初始条件:队列Q已存在。
    • 操作结果:返回Q的元素个数,即队列的长度。
  • GetHead(Q)

    • 初始条件:Q为非空队列。
    • 操作结果:返回Q的队头元素。
  • EnQueue(&Q, e)

    • 初始条件:队列Q已存在。
    • 操作结果:插入元素e为Q的新队尾元素。
  • DeQueue(&Q, &e)

    • 初始条件:Q为非空队列。
    • 操作结果:删除Q的队头元素,并用e返回其值。
  • QueueTraverse(Q)

    • 初始条件:队列Q已存在且非空。
    • 操作结果:从队头到队尾,依次对Q的每个数据元素进行访问。

3. 注意事项

  • 与栈相似,本书后文中引用的队列都基于以上定义的队列类型。
  • 队列的数据元素类型应根据应用程序的需要来定义。

我的理解:

在我看来他其实就是像计算机内存申请一段空间然后用函数限制该段内存空间的行为使其模拟成具有现实生活中队列的特点

3.5 队列的表示和操作的实现_第4张图片

3.5.2 循环队列——队列的顺序表示和实现

  1. 队列的存储表示

    • 顺序表示
    • 链式表示
  2. 队列的顺序存储结构

    • 与顺序栈类似
    • 使用连续的存储单元存放从队头到队尾的元素
    • 使用两个整型变量 frontrear(头指针和尾指针)来指示队头和队尾位置
  3. 队列的初始化

    • 创建空队列时,设定 front = rear = 0
    • 新增队尾元素,尾指针 rear 增1
    • 删除队头元素,头指针 front 增1
    • 在非空队中,头指针指向队头元素,尾指针指向队尾元素的下一个位置
  4. “假溢出”问题

    • 当队列没有完全满,但由于受限制的操作导致无法继续添加元素时,称为“假溢出”
    • 解决办法:将顺序队列转变为循环队列(环状空间)
  5. 循环队列的特点

    • 通过模运算实现头、尾指针在顺序表空间内的“循环”移动
    • 不能仅通过头、尾指针的值来判断队列是“满”还是“空”
  6. 判断循环队列的状态

    • 队空的条件:Q.front = Q.rear
    • 队满的条件:(Q.rear + 1) % MAXQSIZE = Q.front
  7. 处理方法

    • 少用一个元素空间:当队列空间为m时,有m-1个元素视为队满
    • 使用标志位:设置一个标志位来区分队列是“空”还是“满”
  8. 循环队列的初始化算法

    • 动态分配一个大小为 MAXQSIZE 的数组空间
    • 设置头、尾指针为0表示队列为空

概念介绍:

  • 队列的存储表示分为两种:顺序表示和链式表示。
  • 队列的顺序存储结构需要两个整型变量:front(头指针)和rear(尾指针),来分别指示队头和队尾的位置。

队列的顺序存储结构

typedef struct {
    QElemType *base;  // 存储空间的基地址
    int front;       // 头指针
    int rear;        // 尾指针
} sqQueue;
  • 初始化时: front = rear = 0。
  • 新增队尾元素: rear增1。
  • 删除队头元素: front增1。

循环队列:

  • 为解决"假溢出"问题,将顺序队列变为环状的空间。
  • 通过取模运算,使头尾指针在顺序表空间内“循环”移动。

循环队列操作:

  1. 求队列长度

    • 对于非循环队列:长度 = rear - front
    • 对于循环队列:长度 = (rear - front + MAXQSIZE) % MAXQSIZE
    int QueueLength(SqQueue Q) {
        return (Q.rear - Q.front + MAXQSIZE) % MAXQSIZE;
    }
    
  2. 入队操作 (EnQueue)

    • 判断队列是否满。
    • 将新元素插入队尾。
    • 队尾指针加1。
    Status EnQueue(SqQueue &Q, QElemType e) {
        if ((Q.rear + 1) % MAXQSIZE == Q.front)
            return ERROR;
        Q.base[Q.rear] = e;
        Q.rear = (Q.rear + 1) % MAXQSIZE;
        return OK;
    }
    
  3. 出队操作 (DeQueue)

    • 判断队列是否为空。
    • 保存队头元素的值。
    • 队头指针加1。
    Status DeQueue(SqQueue &Q, QElemType &e) {
        if (Q.front == Q.rear)
            return ERROR;
        e = Q.base[Q.front];
        Q.front = (Q.front + 1) % MAXQSIZE;
        return OK;
    }
    

  4. 取队头元素

    • 当队列非空时,获取队头元素。
    QElemType GetHead(SqQueue Q) {
        if (Q.front != Q.rear)
            return Q.base[Q.front];
    }
    

注意点:

  • 循环队列中,队满和队空的判断不能仅根据头尾指针是否相等来进行。
  • 常见的两种处理方法:一是少用一个元素空间;二是另设一个标志位。

小结: 循环队列通过环形结构解决了顺序队列的"假溢出"问题,并通过模运算实现头尾指针的循环移动。循环队列的各种操作相对简单,但需要注意队空和队满的判断条件。

3.5 队列的表示和操作的实现_第5张图片

3.5.3 链队列 —— 队列的链式表示和实现

链队列概述:

  • 链队列是采用链式存储结构实现的队列。
  • 通常链队列使用单链表来表示。一个链队列需要两个分别指示队头和队尾的指针(称为头指针和尾指针)才能唯一确定。
  • 为了操作方便,链队列添加一个头节点,并使头指针始终指向头节点。

链队列的存储结构:

typedef struct QNode{
    QElemType data;
    struct QNode *next;
}QNode,*QueuePtr;

typedef struct{
    QueuePtr front;  // 队头指针
    QueuePtr rear;   // 队尾指针
}LinkQueue;

操作与实现:

  1. 初始化:

    • 生成新节点作为头节点,队头和队尾指针指向此节点。
    • 头节点的指针域置空。
Status InitQueue(LinkQueue &Q){
    Q.front = Q.rear = new QNode;
    Q.front->next = NULL;
    return OK;
}
  1. 入队:

    • 为入队元素分配节点空间,用指针p指向。
    • 将新节点数据域置为e。
    • 将新节点插入到队尾。
    • 修改队尾指针为p。
    Status EnQueue(LinkQueue &Q, QElemType e){
        QNode* p = new QNode;
        p->data = e;
        p->next = NULL;
        Q.rear->next = p;
        Q.rear = p;
        return OK;
    }
    
  2. 出队:

    • 判断队列是否为空。
    • 临时保存队头元素的值,以释放空间。
    • 修改头节点的指针域,指向下一个节点。
    • 若出队的是最后一个元素,将队尾指针重新赋值指向头节点。
    • 释放原队头元素的空间。
    Status DeQueue(LinkQueue &Q, QElemType &e){
        if(Q.front == Q.rear) return ERROR;
        QNode* p = Q.front->next;
        e = p->data;
        Q.front->next = p->next;
        if(Q.rear == p) Q.rear = Q.front;
        delete p;
        return OK;
    }
    
  3. 取队头元素:

    • 如果队列非空,返回当前队头元素的值,队头指针不变。
    QElemType GetHead(LinkQueue Q){
        if(Q.front != Q.rear)
            return Q.front->next->data;
    }
    

结论:

  • 链队列避免了循环队列设定最大队列长度的限制。如果无法预估所用队列的最大长度,链队列是一个更好的选择。

3.5 队列的表示和操作的实现_第6张图片 3.5 队列的表示和操作的实现_第7张图片

3.5 队列的表示和操作的实现_第8张图片 3.5 队列的表示和操作的实现_第9张图片

 总结:

重点:

  1. 链队列的定义: 链队列使用链式结构(单链表)来实现队列的功能。与数组实现的队列不同,它没有固定的大小限制。
  2. 存储结构: 由节点组成,每个节点有一个数据域和一个指针域。使用头指针和尾指针分别指向队列的头部和尾部。
  3. 基本操作:
    • 初始化: 创建一个空队列,通常由一个头节点组成,头尾指针均指向它。
    • 入队: 在尾部添加元素。
    • 出队: 从头部删除元素。
    • 取队头元素: 获取但不删除头部元素。

难点:

  1. 头尾指针的管理: 与单链表操作略有不同,需要注意在进行入队和出队操作时正确地更新头尾指针。
  2. 空间管理: 在出队操作时,不仅要更新指针,还要释放被删除节点的内存空间。
  3. 空队列与只有一个元素的队列的处理: 当队列为空或只有一个元素时,进行出队操作需特别注意头尾指针的变化。

易错点:

  1. 不更新尾指针: 在出队操作时,如果队列只有一个元素,出队后队列为空,此时需要将尾指针重新指向头节点。
  2. 内存泄漏: 出队操作时忘记释放节点的内存空间,会导致内存泄漏。
  3. 空队列操作: 在进行出队或取队头元素操作前,没有检查队列是否为空,可能导致错误或不确定的行为。
  4. 入队操作的指针更新顺序: 先连接新节点再移动尾指针,否则可能丢失队列的后续部分。

综上所述,链队列虽然提供了灵活性,但在实现时需要注意指针操作和内存管理,以确保正确性和效率。

3.5 队列的表示和操作的实现_第10张图片

你可能感兴趣的:(算法,数据结构)