C -- 队列

队列

一、什么是队列?

队列可类比为有两个特殊属性简单链表

  • 新项只能添加到链表末尾
  • 只能从链表头开始删除项

通常简称为先进先出,可类比为排队,后来的人跟在链表末尾,然后前面排完队的人先离开。

二、实现队列

清楚了队列的先进先出这一特性,我们似乎可以很快想到这其实不就是链表吗(而且是相当简单的链表。。。),只是赋予了链表一些固定的属性,因此我们可以采用链表实现队列,当然它的一些接口也受链表属性的限制。(这样一来,我们似乎更容易实现,因为少去了考虑如何实现添加到何处,以及从何处删除这些过程。因为队列限制了添加位置以及删除位置

废话不多说,动手!!!上代码!!!

以下展示一些基本的队列查找、删除、添加等接口实现较之先前的双向链表实现来说是很简单了。

  • 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信息,其实也可以先查找队列头保留信息,再用出列函数将头移出,没有必要在接口里面设置这样一个功能,因为有的时候我们出列也不需要保留信息的副本,这样的设计就显得有些蹩脚了。

发布了18 篇原创文章 · 获赞 4 · 访问量 847

猜你喜欢

转载自blog.csdn.net/GuoningningPro/article/details/103528889