c学习笔记 第17章 高级数据表示 20210410

队列ADT

c语言中以抽象数据类型方法变成有以下三步:

  1. 以抽象通用的方式描述一个类型, 包括该类型的操作
  2. 设计一个函数接口表示这个新类型
  3. 编写具体的代码实现接口

定义队列抽象数据类型

队列(queue)是具有特殊属性的链表

  1. 只能将新项添加到链表的末尾
  2. 只能从链表的开头移除项。

队列是一种“先进先出”(first in, first out, 缩写为FIFO)的数据形式
| 类型名 | 队列 |
| 类型属性 | 可以储存一系列项 |
| 类型操作 | 初始化队列为空 |
确定队列为空
确定队列已满
确定队列中的项数
在队列末尾添加项
在队列开头删除或恢复项
清空队列

定义一个接口

接口定义放在queue.h中, 利用typedef创建两个类型名: Item 和 Queue。
接下来考虑函数的原型:

void InitializeQueue(Queue * pq);
bool QueueIsFull(const Queue * pq);
bool QueueIsEmpty(const Queue * pq);
int QueueItemCount(const Queue * pq);
bool EnQueue(Item item, Queue * pq);   //在队列末尾添加项
bool DeQueue(Item * pitem, Queue * pq);
void EmptyTheQueue(Queue * pq);

实现接口数据表示

/*queue.h -- Queue的接口*/
#ifndef _QUEUE_H_
#define _QUEUE_H_
#include <stdbool.h>

// 这里插入Item类型的定义
typedef int Item;     // 用于use_q.c
//或者是typedef struct item {int gumption; int charisma; } Item;
#define MAXQUEUE 10

typedef struct node
{
	Item item;
	struct node* next;
}Node;
typedef struct queue
{
	Node* front;  // 指向队列首项的指针
	Node* rear;   //指向队列尾项的指针
	int items;		//队列中的项数
}Queue;

//操作:		初始化队列
//前提条件:	pq指向一个队列
//后置条件:	队列被初始化为空
void InitializeQueue(Queue* pq);

//操作:		检查队列是否已满
//前提条件:	pq指向之前被初始化的队列 
//后置条件:		满则返回true否则返回false
bool QueueIsFull(const Queue* pq);


//操作: 确定队列是否为空
//前提条件:  pq指向之前被初始化的队列
//后置条件: 队列为空返回true, 否则返回false
bool QueueIsEmpty(const Queue* pq);

//操作:确定队列项数
// 前提条件: pq指向之前被初始化的队列
//后置条件: 返回队列中的项数
int QueueItemCount(const Queue * pq);

//操作: 在队列末尾添加项
//前提条件: pq指向之前被初始化的队列
//			item是要被添加在队列末尾的项
//后置条件: 如果队列不为空, item将被添加在队列的末尾, 
//				该函数返回true, 否则队列不改变, 该函数返回false
bool EnQueue(Item item, Queue * pq);

//操作: 从队列的开头删除项
//前提条件: pq指向之前被初始化的队列
//后置条件: 如果队列不为空, 队列首端的item将被拷贝到*pitem中
//			并被删除, 且函数返回true;
//			如果该操作使得队列为空, 则重置队列为空
//			如果队列在操作之前为空, 该函数返回false
bool DeQueue(Item* pitem, Queue* pq);

//操作: 清空队列
// 前提条件: pq指向之前被初始化的队列
//后置条件: 队列被清空
void EmptyTheQueue(Queue* pq);
#endif

实现接口函数

void InitializeQueue (Queue * pq)
{
	pq->front = pq->rear = NULL;
	pq->items = 0;
}
bool QueueIsFull(const Queue * pq)
{
	return pq->items == MAXQUEUE;
}
bool QueueIsEmpty(const Queue *pq)
{
	return pq->items == 0;
} 
int QueueItemCount(const Queue * pq)
{
return pq->items;
}

把项添加入队列需要以下几步

  1. 创建一个新结点
  2. 把项拷贝入节点
  3. 设置当前节点next指针为NULL
  4. 设置当前尾节点next指针指向新节点, 把新节点连接到队列中;
  5. 把rear指针指向新节点, 以便找到最后的节点
  6. 项数加一

还要处理两种特殊情况
7. 队列为空, 应把front指针设置为指向新节点, 且该节点既是新节点也是尾节点
8. 如果函数不能为节点分配所需内存时

bool EnQueue (Item item, Queue * pq)
{
	Node * pnew;
	if (QueueIsFull(pq))
		return false;
	pnew = (Node * )malloc(sizeof(Node));
	if (pnew == NULL)
	{
		fprintf(stderr, "Unable to allocate memory!\n");
		exit(1);
	}
	CopyToNode(item, pnew);
	pnew->next ==NULL;
	if(QueueIsEmpty(pq))
		pq->front = pnew;
	else
		pq->rear->next = pnew;
	pq->rear = pnew;
	pq->items++;
	return true;
}

CopyToNode()是静态函数

static void CopyToNode(Item item, Node * pn)
{
	pn->item = item;
}

从队列首段删除项, 涉及以下步骤

  1. 把项拷贝到给定的变量中
  2. 释放空出的结点的使用内存
  3. 重置首指针指向队列中的下一个项
  4. 如果删除最后一项, 收指针和尾指针都重置为NULL
  5. 项数减一
bool DeQueue (Item * pitem, Queue * pq)
{
	Node * pt;
	if(QueueIsEmpty(pq))
		return false;
	CopyToItem(pq->front, pitem);
	pt = pq->front;
	pq->front = pq->front->next;
	free(pt); 
	pq->items--;
	if (pq->items == 0)
		pq->rear = NULL;
		return true;
}

删除最后一项时, 设置的front指针已经指向被删除的节点的next指针, 即NULL
可以循环使用DeQueue函数直到队列为空

void EmptyQueue (Queue * pq)
{
	Item dummy;
	while (!QueueIsEmpty(pq))
		DeQueue(&dummy, pq);
}

** 定义ADT接口后, 应该只使用函数处理数据类型, 如果直接操控队列的某些部分, 可能会破坏接口包中函数之间的协作关系 **

//queue.c --- Queue类型的实现
#include <stdio.h>
#include <stdlib.h>
#include "queue.h"

//局部函数
static void CopyToNode(Item item, Node* pn);
static void CopyToItem(Node* pn, Item* pi);

void InitializeQueue(Queue* pq)
{
	pq->front = pq->rear = NULL;
	pq->items = 0;
}

bool QueueIsFull(const Queue* pq)
{
	return pq->items == MAXQUEUE;
}

bool QueueIsEmpty(const Queue* pq)
{
	return pq->items == 0;
}

int QueueItemCount(const Queue* pq)
{
	return pq->items;
}

bool EnQueue(Item item, Queue* pq)
{
	Node* pnew;
	if (QueueIsFull(pq))
		return false;
	pnew = (Node*)malloc(sizeof(Node));
	if (pnew == NULL)
	{
		fprintf(stderr, "Unable to allocate memory!\n");
		exit(1);
	}
	CopyToNode(item, pnew);
	pnew->next == NULL;
	if (QueueIsEmpty(pq))				//项位于队列的首端
		pq->front = pnew;	
	else
		pq->rear->next = pnew;			//连接到队列的尾端
	pq->rear = pnew;					//记录队列尾端的位置
	pq->items++;						//队列项数+1
	return true;
}

bool DeQueue(Item* pitem, Queue* pq)
{
	Node* pt;
	if (QueueIsEmpty(pq))
		return false;
	CopyToItem(pq->front, pitem);
	pt = pq->front;
	pq->front = pq->front->next;
	free(pt);
	pq->items--;
	if (pq->items == 0)
		pq->rear = NULL;
	return true;
}

// 清空队列
void EmptyQueue(Queue* pq)
{
	Item dummy;
	while (!QueueIsEmpty(pq))
		DeQueue(&dummy, pq);
}

//局部函数
static void CopyToNode(Item item, Node* pn)
{
	pn->item = item;
}
static void CopyToItem(Node* pn, Item* pi)
{
	*pi = pn->item;
}

测试队列

重要程序中使用一个新的设计前, 应测试该设计, 其中方法之一为, 编写一个小程序, 这样的程序称为驱动程序(driver), 其唯一的用途是进行测试。

#include <stdio.h>
#include <stdlib.h>
#include "queue.h"
int main(void)
{
	Queue line;
	Item temp;
	char ch;

	InitializeQueue(&line);
	puts("Testing the Queue interface. Type a to add a value, ");
	puts("type d to delete a value, and type q to quit.");
	while ((ch = getchar()) != 'q')
	{
		if (ch != 'a' && ch != 'd')
			continue;
		if (ch == 'a')
		{
			printf("Integer to add: ");
			scanf("%d", &temp);
			if (!QueueIsFull(&line))
			{
				printf("Putting %d into queue\n", temp);
				Enqueue(temp, &line);
			}
			else
				puts("Queue is Full!");
		}
		else
		{
			if (QueueIsEmpty(&line))
				puts("Nothing to delete!");
			else
			{
				Dequeue(&temp, &line);
				printf("Removing %d from queue\n", temp);
			}
		}
		printf("%d items in queue\n", QueueItemCount(&line));
		puts("Type a to add, d to delete, q to quit:");
	}
	EmptyTheQueue(&line);
	puts("Bye!");
	return 0;
}

用队列进行模拟

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "queue.h"
#define MIN_PER_HR 60.0
bool newcustomer(double x);
Item customertime(long when);
int main(void)
{
	Queue line;
	Item temp;					//新的顾客数据
	int hours;					//模拟的小时数
	int perhour;				//每小时平均多少位顾客
	long cycle, cyclelimit;		//循环计数器, 循环计数器上限
	long turnaways = 0;			//被拒顾客数
	long customers = 0;			//加入队列的顾客数量
	long served = 0;			//模拟期间咨询过Sigmund的顾客数量
	long sum_line = 0;			//累计队列总长
	int wait_time = 0;			//从当前到sigmund空闲所需的时间
	double min_per_cust;		//顾客到来平均时间
	long line_wait = 0;			//队列累计等待时间

	InitializeQueue(&line);
	srand((unsigned int)time(0));
	puts("Case Study: Sigmund Lander's Advice Booth");
	puts("Enter the number of simulation hours:");
	scanf("%d", &hours);
	cyclelimit = MIN_PER_HR * hours;
	puts("Enter average number of cunstomer per hour:");
	scanf("%d", &perhour);
	min_per_cust = MIN_PER_HR / perhour;
	for (cycle = 0; cycle < cyclelimit; cycle++)
	{
		if (newcustomer(min_per_cust))
		{
			if (QueueIsFull(&line))
				turnaways++;
			else
			{
				customers++;
				temp = customertime(cycle);
				EnQueue(temp, &line);
			}
		}
		if (wait_time <= 0 && !QueueIsEmpty(&line))
		{
			DeQueue(&temp, &line);
			wait_time = temp.processtime;
			line_wait += cycle - temp.arrive;
			served++;
		}
		if (wait_time > 0)
			wait_time--;
		sum_line += QueueItemCount(&line);
	}
	if (customers > 0)
	{
		printf("customers accepted: %ld\n", customers);
		printf("  customers served: %ld\n", served);
		printf("		turnaways; %ld\n", turnaways);
		printf("average queue size: %.2f\n", (double)sum_line / cyclelimit);
		printf("average wait time: %.2f minutes\n", (double)line_wait / served);
	}
	else
		puts("No customers!");
	EmptyTheQueue(&line);
	puts("Bye!");

	return 0;
}

//x是顾客到来平均时间
//如果一分钟内有顾客来, 则返回true
bool newcustomer(double x)
{
	if (rand() * x / RAND_MAX < 1)
		return true;
	else
		return false;
}
//when是顾客到来时间
//该函数返回一个Item结构, 到达时间为when
//咨询时间设置为1-3的随机值
Item customertime(long when)
{
	Item cust;

	cust.processtime = rand() % 3 + 1;
	cust.arrive = when;

	return cust;
}

该程序通过用户指定模拟运行的小时数和每小时平均有多少位顾客而进行模拟

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值