像栈一样,队列(queue)也是表。然而,使用队列时插入在一端进行而删除则在另一端进行。
队列模型
队列的基本操作是 Enqueue(入队)-- 在表的末端(叫作队尾(rear))插入一个元素,还有Dequeue(出队)-- 删除(或返回)在表的开头(叫作队头(front))的元素。
队列的链表实现
对于链表实现队列,我们要使用循环链表,这是为了保证每个操作都是运行时间。否则入队需要时间。
类型声明:
typedef struct Node* PtrToNode;
typedef PtrToNode Queue;
typedef int ElementType;
struct Node
{
ElementType element;
PtrToNode prev;
PtrToNode next;
};
表头创建:
Queue QueueCreate()
{
Queue q = (Queue)malloc(sizeof(struct Node));
if (q != NULL)
{
q->element = 0;
q->prev = q;
q->next = q;
}
return q;
}
入队与出队:
void Enqueue(Queue q, ElementType x)
{
PtrToNode tmpNode = (PtrToNode)malloc(sizeof(struct Node));
PtrToNode lastNode = q->prev;
if (tmpNode == NULL)
{
printf("error in creating node\n");
return;
}
tmpNode->element = x;
lastNode->next = tmpNode;
tmpNode->prev = lastNode;
tmpNode->next = q;
q->prev = tmpNode;
}
void Dequeue(Queue q)
{
if (IsEmpty(q))
{
printf("queue is already empty");
return;
}
PtrToNode tmpNode = q->next;
q->next = tmpNode->next;
free(tmpNode);
}
是否为空、置空与释放:
int IsEmpty(Queue q)
{
return q->next == q;
}
void MakeEmpty(Queue q)
{
while (!IsEmpty(q))
{
Dequeue(q);
}
}
void DisposeQueue(Queue q)
{
MakeEmpty(q);
free(q);
}
返回队头和返回队头并出队,同样这里用 int 最大值表示无队头,当然直接报错是更好的。
ElementType Front(Queue q)
{
if (!IsEmpty(q))
{
return q->next->element;
}
return INT_MAX;
}
ElementType FrontAndDequeue(Queue q)
{
ElementType tmp;
if (!IsEmpty(q))
{
tmp = q->next->element;
Dequeue(q);
return tmp;
}
return INT_MAX;
}
队列的数组实现
对于每一个队列数据结构,我们保留一个数组 Queue[] 以及 位置 front 和 rear,它们代表队列的两端。我们还要记录实际存在于队列中的元素的个数 size
操作应该是很清楚地,为了使一个元素 x 入队,我们让 rear 和 size 增1,然后置 Queue[rear] = x。 若使一个元素出队,我们置返回值为 Queue[front],front 增1 然后使 size 减1。
这种实现存在一个潜在的问题,若实现队列的数组大小为10,则经过10次入队时队列就满了,rear就变成了10,再经过出队,可以让前面的元素出队腾出空间,现在我们再想要入队就会发现rear此时的位置不在队列之中。像栈一样,即使在有许多操作的情况下队列也常常不是很大。所以这种情况会经常发生。
简单的解决方法就是,只要 front 或 rear 到达数组的尾端,它就又绕回到开头。这叫作循环数组实现。
关于队列的循环实现,有两件事情要警惕
第一,检测队列是否为空是很重要的,因为当队列为空时,一次Dequeue操作将会不知不觉返回一个不确定的值。
第二,某些设计人员使用不同方法来表示队列的队头和队尾。例如有些并不用一个单元来表示队列大小,因为他们依靠的是基准情形,当 front = rear + 1时,队列为空。队列的大小通过front和rear隐式算出,例如rear-front = 2,表示有三个元素。如果队列大小不是结构的一部分,这样的话写代码就需要特别仔细,如果数组大小为10,则当存在9个元素时队列就满了。比如一开始为空队列则 rear = 0,front = 1。先入队9个元素,rear = 9 ,此时存了9个元素应该就算满了,如果我们允许存储10个元素的话,那么再入队一个元素,rear = 0,此时发现 rear = 0,front = 1 又表示十个元素存在,那么这种情况到底是表示有十个元素呢还是表示队列为空呢,为了不出现这种分歧,我们就只存9个元素,这种情况下则始终表示空队列,所以实际上这种方法队列的最大元素数量为实现队列的数组大小减1。但是如果我们使用size表示大小,当出现 rear 为 9,front 为 0 的情况时,我们就能直接知道,当size=0时表示空队列,当size=10时表示队列有十个元素。由此来看如果不用size字段,就有必要在代码中插入一些注释。
类型声明
#define MinQueueSize (5)
typedef int ElementType;
typedef struct QueueRecord* Queue;
struct QueueRecord
{
ElementType* array;
int size;
int front;
int rear;
int capacity;
};
队列创建:
Queue QueueCreate(int maxElements)
{
Queue q = (Queue)malloc(sizeof(struct QueueRecord));
int realSize = maxElements > MinQueueSize ? maxElements : MinQueueSize;
if (q != NULL)
{
q->front = 1;
q->rear = 0;
q->size = 0;
q->capacity = realSize;
q->array = (ElementType*)malloc(realSize * sizeof(ElementType));
if (q->array == NULL)
{
free(q);
return NULL;
}
}
return q;
}
入队和出队:
void Enqueue(Queue q, ElementType x)
{
if (IsFull(q))
{
printf("queue is already full\n");
return;
}
q->rear++;
if (q->rear == q->capacity)
q->rear = 0;
q->array[q->rear] = x;
q->size++;
}
void Dequeue(Queue q)
{
if (IsEmpty(q))
{
printf("queue is already empty\n");
return;
}
q->front++;
if (q->front == q->capacity)
q->front = 0;
q->size--;
}
判断队列为空和队列满:
int IsEmpty(Queue q)
{
return q->size == 0;
}
int IsFull(Queue q)
{
return q->size == q->capacity;
}
置空队列和释放队列
void MakeEmpty(Queue q)
{
q->front = 1;
q->rear = 0;
q->size = 0;
}
void DisposeQueue(Queue q)
{
free(q->array);
free(q);
}
返回队头
ElementType Front(Queue q)
{
if (IsEmpty(q))
return INT_MAX;
return q->array[q->front];
}
打印队列
void PrintQueue(Queue q)
{
int i = 0;
int size = q->size;
for (i = q->front; size > 0; size--, i++)
{
printf("%d ", q->array[i]);
}
}
如此队列就实现了。