【数据结构】队列

本文更新于个人博客Burnside Blog

在数据结构中,最重要且最基础的两项就是栈与队列。一提到队列(Queue),相信大家都不陌生,它不像栈这个名字一样抽象,而是是一种很形象的结构,因为它本身就可以理解成一个队列。考虑我们排一路纵队在某个窗口办事,办完事的人一定是从队首离开队列,而想要进来办事的人一定是在队尾入队(假设一队人均素质优良,没有插队现象)。队列也是如此,它是一种先入先出的数据结构。

为了方便起见,我们不使用动态方式生成一个队列,或销毁一个队列,我们只考虑一个静态的队列的功能实现。队列中的元素可以是任何的用户自定义类型,为了方便起见,我们假设元素的类型为int,如需要别的类型,只需要灵活处理即可。接下来我们申请一块长度为MaxSize的连续内存来存储这个队列,这时它又被称为顺序队。

int Queue[MaxSize];

这样我们就获得了一个最多能存储MaxSize个元素的队列,在队列中,我们称队首为front,队尾为rear,接下来我们只需要模拟整个入队出队的过程即可。

int enQueue(int x){

    Queue[++rear]=x;

    return rear;

}

int deQueue(){

    return ++front;

}

不难理解,和栈类似,队列在入队操作发生时也只是将队尾的元素赋成新值,出队时将队首前移,这里要注意,队首和队尾在数组中存储的逻辑正好是反着的,也就是队尾的数组下标要比队首大,当然,如果一个队列为空,则队尾和队首的下标相同。

这样我们也可以写一个函数来判断一个队列是否为空。

int empty(){

    return front==rear;

}

这就是队列最简单的实现方式。但注意一点,为什么我们一开始说这个队列最多只能存储MaxSize个元素呢?因为我们发现,这个数组在不断入队出队的过程中,实际上用到的空间只有队首到队尾的这一点空间,而我们申请下来的MaxSize的空间很多是被浪费掉了的,再者,这个队列最多只允许我们进行MaxSize-1次的入队和MaxSize-1次的合法出队操作,因为在进行完这些操作后,队首和队尾的值就都等于MaxSize了,如果再进行入队出队就要访问到非法内存了。

难道队列就是一种如此低效的数据结构吗?并不是这样的,因为我们发明了一种循环队列,这种队列有一个特性,就是如果我的队尾或队首已经到MaxSize而我们想继续操作的时候,下一次的入队或合法的出队操作会使得队首或队尾回到数组下标为0的位置,将申请到的空间循环利用起来,具体实现如下。

入队

bool enQueue(int x){

    if((rear+1)%MaxSize==front) return false;

    Queue[rear=(rear+1)%MaxSize]=x;

    return true;

}

因为循环队列特殊的性质,也就是它的数组下标是循环出现的,我们很容易想到用取余(Mod)来实现数组下标的变换,在此函数中,返回值代表了入队是否成功。

出队

bool deQueue(){

    if(rear==front) return false;

    front=(front+1)%MaxSize;

    return true;

}

与入队相似。

取队首元素

int QueueFront(){

    return Queue[front];

}

此函数的功能是调用队首的元素,书中好像把它和出队操作写在一起了,我不是很推荐这样做,因为调用队首的元素不一定要让其出队。

判断队列是否为空

bool Empty(){

    return front==rear;

}

和普通队列一样判断就可以了。

经过这样的操作,我们就可以得到一个空间利用更高效的队列。当然,还有一种队列叫双端队列(Deque),是支持队首进队出队、队尾进队出队的特殊队列,写法和循环队列类似,读者可以在闲暇时间独立完成,书中貌似也有相关的代码。

书中还介绍了队列的链式存储,也就是链队,实现方式和顺序队类似,不再赘述。此外,还有队内元素保持单调性的单调队列,常用于对动态规划问题(Dynamic Programming,DP)的优化,常用于竞赛,感兴趣可以自行了解。

此外,可能会有部分读者表示我在实现普通队列的时候,没有考虑队列的非法操作(队满时入队,队空时出队),是因为那种队列的实现形式现在已经不用了(因为有更好的循环队列可以使用),因此没有详细处理细节,若想判断的话只需要在两个函数里分别加入if语句判断即可。

你可能感兴趣的:(【数据结构】队列)