欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

数据结构:队列

程序员文章站 2022-05-05 09:39:13
...

链队列的实现

链式存储结构

struct QNode
{
    int data;
    int* next;
}
struct LinkQueue;
{
    QNode* front;
    QNode* rear;
};

操作函数

构造

void InitQueue (LinkQueue &Q)
{
    Q.front = Q.rear = new QNode();
    Q.front->next = NULL;
}

销毁

void DestroyQueue (LinkQueue &Q)
{
    while (Q.front)
    {
        Q.rear = Q.front->next;
        delete Q.front;
        Q.front = Q.rear;
    }
}

清空队列

void ClearQueue (LinkQueue &Q)
{
    while (Q.front != Q.rear)//条件也可以是while (Q.front->next)
    {
        QNode* p = Q.front->next;
        if (p == Q.rear)
        {
            Q.rear = Q.front;
        }
        Q.front->next = Q.front->next->next;
    }
}

注意上面while里面的if条件,当要删除的结点是Q.rear的时候,将Q.rear指向头指针指向的位置,不然它指向的位置就要被删除了,Q.rear就无效了。

判空

bool QueueEmpty (LinkQueue Q)
{
    if (Q.front == Q.rear)
        return true;
    else
        return false;
}

表长

int QueueLength (LinkQueue Q)
{
    QNode* p = Q.front;
    int num = 0;
    while (p != Q.rear)
    {
        p = p->next;
        num++;
    }
    return num;
}

可以在LinkQueue中增加一个int类型变量来储存表长

查询队首的值

bool GetHead (LinkQueue Q, int &e)
{
    if (Q.front == Q.rear)
        return false;
    e = *(Q.front->next->data);
}

在队尾插入元素

void EnQueue (LinkQueue &Q, int e)
{
    QNode* p = new QNode();
    p->next = NULL;
    p->data = e;
    Q.rear->next = p;
    Q.rear = p;
}

因为是链式结构,插入的时候不需要判断是不是满了,插入位置是不是合法等

在队首删除元素

bool DeQueue (LinkQueue &Q, int &e)
{
    if (Q.front == Q.rear)
        return false;
    QNode* p = Q.front->next;
    e = p->data;
    Q.front->next = p->next;
    if (p == Q.rear)
        Q.rear = Q.front;
    delete p;
    return true;
}

和清空队列一样,注意Q.rear指向结点是不是要被删除的结点

顺序存储结构

在队列的顺序存储结构中,需要用front和rear两个指针来只是队列的头元素和尾元素的位置。在初始化的时候令
front = rear = 0;每当插入一个元素,rear++;每当删除一个元素,front++;
以存储的最大空间为7的队列为例
数据结构:队列
可以看到,队列在插入元素上和栈相同,都是尾指针指向的位置上插入元素,然后rear在指向这个元素的下一个位置;但是在删除上有很大差别,栈的删除是尾指针向下移动,而队列是头指针向上移动。
这样会有一个问题,就是在溢出的时候,会分真溢出和假溢出两种情况。

  1. 真溢出
    所谓真溢出就是front在0处,而rear指向上图中6的上面,即队列的空间被全部占用,此时rear的溢出就是真溢出。
  2. 假溢出
    所谓假溢出就是front不在0处,而rear溢出,这里队列并没有完全被占满,因为还有0——(front-1)没有放置元素。
    不论真溢出还是假溢出,都不能再向其中添加元素了,否则都会因为数组越界而遭至程序崩溃。但是对于假溢出这种情况,又不好像栈那样重新分配一个更大的空间来存储吧。毕竟,有很大一部分空间是还没有利用的,想想看,一个10000大小的数组,front = 9997,rear = 10000,这时再添加一个元素,就会越界,难道你还要去申请一个10010的空间来存这4个元素吗?
    一个比较好的方法就是循环队列。

循环队列

所谓循环队列就是当front指向上图(c)中的a7的下一个位置的时候,将他赋值为a1所在的位置,也就是取模7的结果。这样就将这个表首尾相连,形成一个环,如下图所示,将一个存储N个元素的数组,想象成环。
数据结构:队列
移动rear和front时都会进行mod N的运算,即编程中的%N。
这样就万事大吉了吗?有没有什么问题呢?
我们来看一下下面的图片,不知道能不能找到什么问题呢?
数据结构:队列
我们发现队列为空的判断是front == rear,而队列满了条件竟然也是front == rear,那么当front == rear到底是空的还是满的?
针对这个问题有两种解决方案。
方案一,是设置一个标志,当队列没有元素为0,队列有元素为1,这样就可以区分开了。具体编程的时候就是在添加元素遇到front == rear 的时候,设置为1;删除元素遇到front == rear的时候,设置为0。查找的时候,遇到front == rear就看这个标志是0还是1喽。
方案二,我们可以空出最后一个存储单元,不用它来放值,注意,这里的最后一个存储单元是相对的,相对与front的位置的,也就是说这个最后一个存储单元是在front前面的那个存储单元。当rear指向这个单元的时候,表示队列满了,也就是当( rear + 1)% N == front是队列满了的条件。当rear == front就是队列空的条件。