队列
一、什么是队列?
队列可类比为有两个特殊属性的简单链表:
- 新项只能添加到链表末尾
- 只能从链表头开始删除项
通常简称为先进先出,可类比为排队,后来的人跟在链表末尾,然后前面排完队的人先离开。
二、实现队列
清楚了队列的先进先出这一特性,我们似乎可以很快想到这其实不就是链表吗(而且是相当简单的链表。。。),只是赋予了链表一些固定的属性,因此我们可以采用链表实现队列,当然它的一些接口也受链表属性的限制。(这样一来,我们似乎更容易实现,因为少去了考虑如何实现添加到何处,以及从何处删除这些过程。因为队列限制了添加位置以及删除位置)
废话不多说,动手!!!上代码!!!
以下展示一些基本的队列查找、删除、添加等接口实现较之先前的双向链表实现来说是很简单了。
- Queue.h (头文件,类型定义及接口定义)
//Queue.h头文件,包括类型相关定义以及接口相关定义
#ifndef _MYQUEUE_H
#define _MYQUEUE_H
#include <stdbool.h>
/* 第一步、建立抽象数据类型 */
//定义类型,此处类型可依据具体需要更改,但是涉及相应接口实现可能也需要随之变化
typedef struct _item
{
int num;
int value;
}Item;
//定义结点
typedef struct _node
{
Item item;
struct _node * next;
}Node;
//定义队列
typedef struct _queue
{
Node * first; //队列头,指向第一个结点的指针
Node * last; //队列尾,指向最后一个结点的指针
}Queue;
/* 第二步、建立接口 */
//需要一个指向队列的指针类型,初始化队列为空
void InitializeQueue(Queue * ql);
//需要一个指向队列的指针类型,确定初始化为空,如果为空返回true,否则返回false
bool QueueIsEmpty(const Queue * ql);
//需要一个指向队列的指针类型,确定队列是否已满,如果满返回true,否则返回false
bool QueueIsFull(const Queue * ql);
//需要一个指向队列的指针类型,确定队列中的项数,返回一个unsigned int类型的值
unsigned int CountQueueSize(const Queue * ql);
//需要一个指向队列的指针类型,在队列的末尾添加项,返回一个状态表示成功添加,或失败
bool AddToQueue(Queue * ql, Item item);
//需要一个指向队列的指针类型,在队列的开头删除一些项
void DelInQueue(Queue * ql, int amount);
//需要一个指向队列的指针类型,将队列清空
void ClearQueue(Queue * ql);
//需要一个指向队列类型的指针,展示队列
void ShowQueue(const Queue * ql);
#endif
-
Queue.c(接口实现)
实现接口的时候我们可以添加一点printf验证我们的接口是否能完成操作,没问题的话我们再将其注释掉,也算一个调试接口的方法吧。
//Queue.c -- 实现接口
#include <stdio.h>
#include <stdlib.h>
#include "myqueue.h"
//文件内部需要用的辅助函数,static静态定义
static void CopyToItem(Node * p, Item item)
{
p->item = item;
}
void InitializeQueue(Queue * ql)
{
ql->first = ql->last = NULL;
//printf("Initialize successfully!\n");
}
bool QueueIsEmpty(const Queue * ql)
{
bool ret = false;
if((ql->first == NULL) && (ql->last == NULL))
ret = true;
//if(ret)
// printf("Queue is Empty!\n");
return ret;
}
bool QueueIsFull(const Queue * ql)
{
bool ret = false;
Node * p = (Node *) malloc (sizeof(Node));
if(p == NULL)
{
fprintf(stderr, "Queue is full!\n");
exit(1);
}
free(p);
//printf("Queue is not full\n");
return ret;
}
unsigned int CountQueueSize(const Queue * ql)
{
Node * p;
unsigned int cnt = 0;
for(p = ql->first;p;p = p->next)
cnt++;
return cnt;
}
bool AddToQueue(Queue * ql, Item item)
{
Node * p = (Node *) malloc (sizeof(Node));
p->next = NULL;
CopyToItem(p,item);
if(p == NULL)
return false;
//添加第一个结点
if(ql->first == NULL)
ql->first = ql->last = p;
//添加后续
else{
ql->last->next = p;
ql->last = p;
}
return true;
}
void DelInQueue(Queue * ql, int amount)
{
Node * p, *q; //q暂存p->next;
int i;
if(QueueIsEmpty(ql) && amount > CountQueueSize(ql))
exit(1);
for(i = 0, p = ql->first;i < amount;i++)
{
q = p->next;
free(p); //free(p)会让p消失无法访问到下一个结点,因此需要q暂存p->next
ql->first = p = q; //队列头也需要随之移动到下一个
if(i == CountQueueSize(ql)-1) //如果删除最后一个
ql->last = NULL; //队列尾需要设置为NULL
}
//printf("Delete %d item(s) successfully!\n",amount);
}
//或者这个接口可通过上一个删除辅助完成
void ClearQueue(Queue * ql)
{
Node * p, * q;
for(p = ql->first;p;p = q)
{
q = p->next;
free(p);
}
ql->first = ql->last = NULL;
//printf("ClearQueue successfully!\n");
}
//这个接口实现可按需要更改
void ShowQueue(const Queue * ql)
{
Node * p;
for(p = ql->first;p;p = p->next)
printf("(%d %d) ",p->item.num,p->item.value);
printf("\n");
}
三、更纯正的ADT
或许我们可以在队列类型中添加一个int items代表队列中的项数,这样其他接口实现起来会非常方便。有时间再回来优化一次。
- 更纯正的接口及类型定义
//队列头文件
#ifndef _QUEUE_H
#define _QUEUE_H
//定义类型
#define MAXQUEUE 10
typedef struct _item
{
/* code */
}Item;
typedef struct _node
{
Item item;
struct _node * next;
}Node;
typedef struct _queue
{
Node * front;
Node * rear;
int items; //存储目前队列有多少项
}Queue;
//定义接口
//初始化队列
void InitializeQueue(Queue * ql);
//判断队列是否为空
bool QueueIsEmpty(Queue * ql);
//判断队列是否已满
bool QueueIsFull(Queue * ql);
//返回队列中的项数
int QueueItemCount(const Queue * ql);
//添加到队列中,返回是否添加成功
bool EnQueue(Queue * ql, Item item);
//从队列中删除,如果删除的最后一个则需重置队列为空
bool DeQueue(Queue * ql, Item * pitem);
//清空队列
void ClearQueue(Queue * ql);
#endif
- 更纯正的实现接口
//接口实现
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include "Queue.h"
//静态辅助函数,这两个函数后面会用到,作用嘛见内容
static void CopyToNode(Node * p, Item item)
{
p->item = item;
}
static void CopyToItem(Node * p, Item *pitem)
{
*pitem = p->item;
}
void InitializeQueue(Queue * ql)
{
ql->front = ql->rear = NULL;
ql->items = 0;
}
bool QueueIsEmpty(Queue * ql)
{
return ql->items == 0;
}
bool QueueIsFull(Queue * ql)
{
return ql->items == MAXQUEUE;
}
int QueueItemCount(const Queue * ql)
{
return ql->items;
}
bool EnQueue(Queue * ql, Item item)
{
if(QueueIsFull(ql))
{
fprintf(stderr, "The queue is full!\n");
return false;
}
Node * p = (Node *) malloc (sizeof(Node));
if(p == NULL)
{
fprintf(stderr, "Can't allocate memory\n");
exit(1);
}
p->next = NULL;
CopyToNode(p,item);
if(QueueIsEmpty(ql))
ql->front = ql->rear = p;
else
{
ql->rear->next = p;
ql->rear = p;
}
ql->items++;
return true;
}
//此处我们似乎没必要保留队列头item信息
bool DeQueue(Queue * ql, Item * pitem)
{
if(QueueIsEmpty(ql))
{
fprintf(stderr, "Can't del a empty queue!\n");
return false;
}
//保留即将被删除的队列头的item信息到一个Item类型指针
CopyToItem(ql->front,pitem);
Node * p;
p = ql->front;
ql->front = ql->front->next;
free(p);
ql->items--;
if(QueueItemCount(ql) == 0)
ql->rear = NULL;
return true;
}
void ClearQueue(Queue * ql)
{
//由于DeQueue()接口定义的限制,需要一个Item类型指针暂存被删除的队列头信息,但是此处我们似乎不需要保留队列头信息的副本
Item dummy;
while(!QueueIsEmpty(ql))
DeQueue(ql,&dummy);
}
我们似乎没有必要在出列的时候保留队列头的相关item信息,但是万一有时候需要呢,这样的接口定义似乎更方便保留出列项的信息。但是方法不唯一。对于用户端来说想保留出列项item信息,其实也可以先查找队列头保留信息,再用出列函数将头移出,没有必要在接口里面设置这样一个功能,因为有的时候我们出列也不需要保留信息的副本,这样的设计就显得有些蹩脚了。