介绍
队列是一种先进先出(First-IN First-OUT,FIFO)的结构。排队就是一种典型的队列。首先轮到的是排在队伍最前面的人,新入队的人总是排在队伍的最后。
队列接口——queue.h
/*
* Interface to a queue module
*/
#include <stdlib.h>
#define QUEUE_TYPE int /* The type of queue element */
/*
* insert
* Add a new element to the queue, and the parameter is the element that needs to be added.
*/
void insert(QUEUE_TYPE value);
/*
* delete
* Remove an element from the queue and discard it.
*/
void delete(void);
/*
* first
* Returns the value of the first element in the queue, without modifying the queue itseif.
*/
QUEUE_TYPE first(void);
/*
* is_empty
* Returns TRUE if the queue is empty, FALSE otherwise.
*/
int is_empty(void);
/*
* is_full
* Returns TRUE if the queue is full, FALSE otherwise.
*/
int is_full(void);
实现队列
队列的实现需要两个指针,一个指向队头,一个指向队尾。
假设用一个数组实现队列,front是队头,rear是队尾。
删除三个元素后:
数组未满,但它尾部已没有空间,无法插入新的元素。
一种解决方法是当一个元素删除后,队列中的其他元素整体前移一个位置,
但是当数组较大时,这种方法复制时所需的开销太大了。
比较好的方法是使用循环数组。
插入一个元素后:
当尾部下标移出数组尾部时,把它设置为0即可。如:
rear = (rear + 1) % QUEUE_SIZE;
但是,这种方法也导致了一个新问题。如何判断队列为空或满?假定队列已满:
此时front和rear的值分别是3和2。
删除4个元素后:
删除最后一个元素后:
现在队列为空时,front和rear的值也是3和2,这和队列满时的值相同。所以无法通过比较front和rear来测试队列是否为空。
解决这个问题的一种方法是如果使数组中的一个元素始终保留不用,这样当队列满时front和rear的值便不相同,可以和队列为空时的情况区分开来。
当队列为空时把rear的值设置为比front的值小1。
判断队列是否为空:
(rear + 1) % QUEUE_SIZE == front;
判断队列是否为满:
(rear + 2) % QUEUE_SIZE == front;
接口实现——a_queue.c
/*
* A queue implemented as a static array. The size of the array can only be adjusted
* by changing the #define definition and recompiling the module.
*/
#include "queue.h"
#include <stdio.h>
#include <assert.h>
#define QUEUE_SIZE 100 /* The maximum number of elements in the queue */
#define ARRAY_SIZE (QUEUE_SIZE + 1) /* The length of an array */
/*
* An array to store the elements of the queue and pointers to the head and tail of the queue.
*/
static QUEUE_TYPE queue[ARRAY_SIZE];
static size_t front = 1;
static size_t rear = 0;
把ARRAY_SIZE的值设置为比QUEUE_SIZE的值大1,实际存储的空间为QUEUE_SIZE,剩下的一个空间用于判断队列是否已满。初始化front的值为1,rear的值为0。
/*
* insert
*/
void insert(QUEUE_TYPE value)
{
assert(!isfull());
rear = (rear + 1) % ARRAY_SIZE;
queue[rear] = value;
}
插入函数判断队列是否已满,已满时终止程序,否则使rear的值移到下一个可用的位置,再把值存放到该位置。
/*
* delete
*/
void delete(void)
{
assert(!is_empty());
front = (front + 1) % ARRAY_SIZE;
}
删除函数判断队列是否为空,为空时终止程序,否则使front后移一个位置,达到删除一个值的效果。
/*
* first
*/
QUEUE_TYPE first(void)
{
assert(!is_empty());
return queue[front];
}
first函数返回队头的值。
/*
* is_empty
*/
int is_empty(void)
{
return (rear + 1) % ARRAY_SIZE == front;
}
is_empty函数用于判断队列是否为空。假设QUEUE_SIZE的值为10,ARRAY_SIZE的值为11。最多删除10个值。初始化时rear为10,front为1。此时队列已满。
删除第1个值时,调用删除函数执行assert(!is_empty());然后调用is_empty函数判断队列是否为空,执行return (rear + 1) % ARRAY_SIZE == front;此时(10+1)%11,0!=front,返回删除函数执行front = (front + 1) % ARRAY_SIZE;front=(1+1)%11,此时rear为10,front为2。
当删除第10个值时,调用删除函数执行语句assert(!is_empty());然后调用is_empty函数判断队列是否为空,执行return (rear + 1) % ARRAY_SIZE == front;此时(10+1)%11,0!=front,返回删除函数执行front = (front + 1) % ARRAY_SIZE;front=(10+1)%11,此时rear为10,front为0,把第10个值删除,此时队列为空。
当再次调用删除函数时,执行assert(!is_empty());然后调用is_empty函数判断队列是否为空,执行return (rear + 1) % ARRAY_SIZE == front;此时(10+1)%11,0==front,if_empty函数返回1,返回删除函数终止程序。
/*
* is_full
*/
int is_full(void)
{
return (rear + 2) % ARRAY_SIZE == front;
}
is_full函数用于判断队列是否为满。假设QUEUE_SIZE的值为10,ARRAY_SIZE的值为11。最多插入10个值。初始化时rear为0,front为1。此时队列为空。
插入第1个值时,调用插入函数执行assert(!is_full());然后调用is_full函数判断队列是否已满,执行return (rear + 2) % ARRAY_SIZE == front;此时(0+2)%11,2!=front,返回插入函数执行rear = (rear + 1) % ARRAY_SIZE;rear=(0+1)%11,此时rear为1,front为1。
当插入第10个值时,调用插入函数执行语句assert(!is_full());然后调用is_full函数判断队列是否已满,执行return (rear + 2) % ARRAY_SIZE == front;此时(9+2)%11,0!=front,返回插入函数执行rear = (rear + 1) % ARRAY_SIZE;rear=(9+1)%11,此时rear为10,front为1,把第10个值插入,此时队列已满。
当再次调用插入函数时,执行assert(!is_full());然后调用is_full函数判断队列是否已满,执行return (rear + 2) % ARRAY_SIZE == front;此时(10+2)%11,1==front,if_full函数返回1,返回插入函数终止程序。
总结:
程序的难点在于如何区分队列为空和满的情况,可以存放在数组中的元素总是比数组的空间少1。空下来的那个空间用于判断队列是否已满。
判断是否为空时,用front和rear之间是否相差1个元素来实现;
判断是否为满时,用front和rear之间是否相差2个元素来实现。
插入函数调用is_full()来判断是否还有空间可以插入,删除函数调用is_empty()来判断是否还有元素可以删除。队列是从尾部插入元素,头部删除元素的数据结构。