队列的基本概念详解,循环队列、链式队列的C++详细实现。

队列

队列的基本概念

1.队列的定义:

队列(Queue)简称队,也是一种操作受限的线性表,只允许在表的一端进行插入,而在表的另一端进行删除。向队列中插入元素称为入队或进队;删除元素称为出队或离队。这和我们日常生活中的排队是一致的,最早排队的也是最早离队的,其操作的特性是先进先出**(First In First Out, FIFO)**,如图所示

image-20220707173504662

队头(Front):允许删除的一端,又称队首。

队尾(Rear):允许插入的一端。

空队列:不含任何元素的空表。

2.队列常见的基本操作:

image-20220707174755611

需要注意的是,栈和队列是操作受限的线性表,因此不是任何对线性表的操作都可以作为栈和队列的操作。比如,不可以随便读取栈或队列中间的某个数据。

队列的顺序存储结构

1.队列的顺序存储:

队列的顺序实现是指分配一块连续的存储单元存放队列中的元素,并附设两个指针:队头指针front指向队头元素,队尾指针rear指向队尾元素的下一个位置(不同教材对front和rear的定义可能不同,例如,可以让rear 指向队尾元素、front指向队头元素。对于不同的定义,出队入队的操作是不同的)。

队列的顺序存储类型可描述为:

#define MaxSize 50          //定义队列中元素的最大个数

template<typename T>
struct SqQueue {
    T data[MaxSize];        //存放队列元素
    int front, rear;        //队头指针和队尾指针
};

初始状态(队空条件):Q.front==Q.rear==O

进队操作:队不满时,先送值到队尾元素,再将队尾指针加1。

出队操作:队不空时,先取队头元素值,再将队头指针加1。

图(a)所示为队列的初始状态,有Q.front==Q.rear==0成立,该条件可以作为队列判空的条件。但能否用Q.rear==Maxsize作为队列满的条件呢?显然不能,图(d)中,队列中仅有一个元素,但仍满足该条件。这时入队出现“上溢出”,但这种溢出并不是真正的溢出,在data数组中依然存在可以存放元素的空位置,所以是一种“假溢出”。

image-20220707180902717

2.循环队列:

前面已指出了顺序队列的缺点,这里引出循环队列的概念。将顺序队列臆造为一个环状的空间,即把存储队列元素的表从逻辑上视为一个环,称为循环队列。当队首指针Q.front=MaxSize-1后,再前进一个位置就自动到0,这可以利用除法取余运算(%)来实现。

  • 初始时: Q.front=Q.rear=0
  • 队首指针进1: Q.front=(Q.front+1)%Maxsize
  • 队尾指针进1: Q.rear=(Q.rear+1)%MaxSize
  • 队列长度: (Q.rear+MaxSize-Q.front)%MaxSize

那么,循环队列队空和队满的判断条件是什么呢?显然,队空的条件是Q.front==Q.rear。若入队元素的速度快于出队元素的速度,则队尾指针很快就会赶上队首指针,如图(d1)所示,此时可以看出队满时也有Q.front==Q.rear。循环队列出入队示意图如下图所示。

image-20220707201217897

为了区分队空还是队满的情况,有三种处理方式(首选第1种方式):

1)牺牲一个单元来区分队空和队满,入队时少用一个队列单元,这是一种较为普遍的做法,约定以“队头指针在队尾指针的下一位置作为队满的标志”,如图(d2)所示。

  • 队满条件: (Q.rear+1)%MaxSize==Q.front
  • 队空条件仍:Q.front==Q.rear
  • 队列中元素的个数:(Q.rear-Q.front+MaxSize)% MaxSize

2)类型中增设表示元素个数的数据成员。这样,队空的条件为Q.size==0;队满的条件为Q.size==MaxSize。这两种情况都有Q.front==Q.rear

3)类型中增设tag数据成员,以区分是队满还是队空。tag 等于0时,若因删除导致Q.front==Q.rear,则为队空;tag等于1时,若因插入导致Q.front==Q.rear,则为队满。

3.循环队列的实现:

#include <iostream>

using namespace std;

#define MaxSize 8          //定义队列中元素的最大个数

template<typename T>
struct SqQueue {
    T data[MaxSize];        //存放队列元素
    int front, rear;        //队头指针和队尾指针
};

template<class T>
class SqQueueClass {
private:
    SqQueue<T> sqQueue;

public:
    //缺省构造函数
    SqQueueClass() {
        this->InitQueue();
    }

    //初始化
    void InitQueue() {
        //初始化队首队尾指针
        sqQueue.front = 0;
        sqQueue.rear = 0;
    }

    //判断队空
    bool isEmpty() {
        if (sqQueue.front == sqQueue.rear)
            return true;
        return false;
    }

    //判断队满
    bool isFull() {
        if ((sqQueue.rear + 1) % MaxSize == sqQueue.front)
            return true;
        return false;
    }

    //入队
    bool EnQueue(T x) {
        if (this->isFull())      //队满则报错
            return false;

        sqQueue.data[sqQueue.rear] = x;
        sqQueue.rear = (sqQueue.rear + 1) % MaxSize;
        return true;
    }

    //出队
    bool DeQueue(T &x) {
        if (this->isEmpty())     //队空则报错
            return false;

        x = sqQueue.data[sqQueue.front];
        sqQueue.front = (sqQueue.front + 1) % MaxSize;
        return true;
    }

    //查看队列中所有元素
    void display() {
        if (this->isEmpty()) {
            cout << "The SqQueue is empty!" << endl;
            return;
        }

        cout << "all elements in SqQueue:\t";
        int i = sqQueue.front;
        int k = sqQueue.rear;
        while (i != k) {
            cout << sqQueue.data[i] << "\t";
            i = (i + 1) % MaxSize;
        }
        cout << endl;
    }
};


int main() {
    //测试
    SqQueueClass<int> sqQueueInstance;
    int x;

    for (int i = 0; i < 10; i++)
        if (sqQueueInstance.EnQueue(i))
            cout << "enqueue " << i << " ok!" << endl;
        else
            cout << "enqueue error! The SqQueue is full!" << endl;


    sqQueueInstance.display();


    for (int i = 0; i < 10; i++)
        if (sqQueueInstance.DeQueue(x))
            cout << "dequeue " << x << " ok!" << endl;
        else
            cout << "dequeue error! The SqQueue is empty!" << endl;

    return 0;
}

运行结果

enqueue 0 ok!
enqueue 1 ok!
enqueue 2 ok!
enqueue 3 ok!
enqueue 4 ok!
enqueue 5 ok!
enqueue 6 ok!
enqueue error! The SqQueue is full!
enqueue error! The SqQueue is full!
enqueue error! The SqQueue is full!
all elements in SqQueue:        0       1       2       3       4       5       6
dequeue 0 ok!
dequeue 1 ok!
dequeue 2 ok!
dequeue 3 ok!
dequeue 4 ok!
dequeue 5 ok!
dequeue 6 ok!
dequeue error! The SqQueue is empty!
dequeue error! The SqQueue is empty!
dequeue error! The SqQueue is empty!

队列的链式存储结构

1.队列的链式存储:

队列的链式表示称为链队列,它实际上是一个同时带有队头指针和队尾指针的单链表。头指针指向队头结点,尾指针指向队尾结点,即单链表的最后一个结点(注意与顺序存储的不同)。队列的链式存储如图所示。

image-20220708185642209

队列的链式存储结构可描述为:

template<typename T>
struct LinkNode {                   //链式队列结点
    T data;
    LinkNode *next;
};

template<typename T>
struct LinkQueue {                  //链式队列
    LinkNode<T> *front, *rear;      //队列的队头和队尾指针
};
  • Q.front==NULLQ.rear==NULL时,链式队列为空。
  • 出队时,首先判断队是否为空,若不空,则取出队头元素,将其从链表中摘除,并让Q.front指向下一个结点(若该结点为最后一个结点,则置Q.front和 Q.rear都为NULL)。
  • 入队时,建立一个新结点,将新结点插入到链表的尾部,并改让Q.rear指向这个新插入的结点(若原队列为空队,则令Q.front也指向该结点)。

不难看出,不带头结点的链式队列在操作上往往比较麻烦,因此通常将链式队列设计成一个带头结点的单链表,这样插入和删除操作就统一了,如图所示。

image-20220708190826481

用单链表表示的链式队列特别适合于数据元素变动比较大的情形,而且不存在队列满且产生溢出的问题。另外,假如程序中要使用多个队列,与多个栈的情形一样,最好使用链式队列,这样就不会出现存储分配不合理和“溢出”的问题。

2.链式队列的实现:

#include <iostream>

using namespace std;

template<typename T>
struct LinkNode {                   //链式队列结点
    T data;
    LinkNode *next;
};

template<typename T>
struct LinkQueue {                  //链式队列
    LinkNode<T> *front, *rear;      //队列的队头和队尾指针
};

template<class T>
class LinkQueueClass {
private:
    LinkQueue<T> linkQueue;

public:
    //缺省构造函数
    LinkQueueClass() {
        this->InitQueue();
    }

    //初始化
    void InitQueue() {
        linkQueue.front = linkQueue.rear = new LinkNode<T>; //建立头结点
        linkQueue.front->next = NULL;
    }

    //队判空
    bool IsEmpty() {
        if (linkQueue.front == linkQueue.rear)
            return true;
        return false;
    }

    //入队
    void EnQueue(T x) {
        LinkNode<T> *p = new LinkNode<T>;   //创建新结点,插入到链尾
        p->data = x;
        p->next = NULL;

        linkQueue.rear->next = p;
        linkQueue.rear = p;
    }

    //出队
    bool DeQueue(T &x) {
        if (this->IsEmpty())    //空队
            return false;

        LinkNode<T> *p = linkQueue.front->next;
        x = p->data;
        linkQueue.front->next = p->next;

        if (linkQueue.rear == p)
            linkQueue.rear = linkQueue.front;   //若原队列中只有一个结点,将队尾指针指向头结点

        delete p;
        return true;
    }

    //查看队列中所有元素
    void display() {
        if (this->IsEmpty()) {
            cout << "The LinkQueue is empty!" << endl;
            return;
        }

        cout << "all elements in LinkQueue:\t";
        LinkNode<T> *p = linkQueue.front->next;
        while (p != NULL) {
            cout << p->data << "\t";
            p = p->next;
        }
        cout << endl;
    }

};


int main() {
    //测试
    LinkQueueClass<int> linkQueueInstance;
    int x;

    for (int i = 0; i < 10; i++) {
        linkQueueInstance.EnQueue(i);
        cout << "enqueue " << i << " ok!" << endl;
    }

    linkQueueInstance.display();

    for (int i = 0; i < 13; i++)
        if (linkQueueInstance.DeQueue(x))
            cout << "dequeue " << x << " ok!" << endl;
        else
            cout << "dequeue error! The LinkQueue is empty!" << endl;

    linkQueueInstance.display();
    return 0;
}

运行结果

enqueue 0 ok!
enqueue 1 ok!
enqueue 2 ok!
enqueue 3 ok!
enqueue 4 ok!
enqueue 5 ok!
enqueue 6 ok!
enqueue 7 ok!
enqueue 8 ok!
enqueue 9 ok!
all elements in LinkQueue:      0       1       2       3       4       5       6       7       8       9
dequeue 0 ok!
dequeue 1 ok!
dequeue 2 ok!
dequeue 3 ok!
dequeue 4 ok!
dequeue 5 ok!
dequeue 6 ok!
dequeue 7 ok!
dequeue 8 ok!
dequeue 9 ok!
dequeue error! The LinkQueue is empty!
dequeue error! The LinkQueue is empty!
dequeue error! The LinkQueue is empty!
The LinkQueue is empty!

双端队列

双端队列是指允许两端都可以进行入队和出队操作的队列,如图所示。其元素的逻辑结构仍是线性结构。将队列的两端分别称为前端和后端,两端都可以入队和出队。

image-20220708201622906

在双端队列进队时,前端进的元素排列在队列中后端进的元素的前面,后端进的元素排列在队列中前端进的元素的后面。在双端队列出队时,无论是前端还是后端出队,先出的元素排列在后出的元素的前面。

输出受限的双端队列:允许在一端进行插入和删除,但在另一端只允许插入的双端队列称为输出受限的双端队列,如图所示。

image-20220708202047597

输入受限的双端队列:允许在一端进行插入和删除,但在另一端只允许删除的双端队列称为输入受限的双端队列,如图所示。若限定双端队列从某个端点插入的元素只能从该端点删除,则该双端队列就蜕变为两个栈底相邻接的栈。

image-20220708202234202
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值