66.队列(1)—存储实现
队列:一种特殊的线性表,插入在表尾,删除在表头。
队列类的特征
假设我们有一个Queue类的声明:
对于Queue的存储设计,可以基于数组,也可以基于链表:
应当补充以下声明:
链表的节点Node有两个元素:一个是Item,一个是指向Node结构类型的指针;
Queue类里面还需要声明链表的尾部指针rear和头部指针front。
这样定义符合队列的行为:从队头front删除元素,从队尾rear加入元素;
1.front指针移动到当前front指向的下一个Node;
2.rear从指向NULL改变成指向一个新的Node,并且让该Node的指针指向NULL,作为新的rear指针。
注意成员int items是队列的当前元素个数,这便于直接查询修改队列的长度,免得我们遍历,属于用空间换时间的做法(开辟额外变量省去遍历队列的时间)
67.队列(2) —成员函数的实现
队列类的声明如下:
#pragma once
//queue.h -- Queue class interface
//version 00
#include <iostream>
using namespace std;
class Item {
//Item根据我们的应用进行自定义
};
class Queue
{
private:
enum {Q_SIZE=10}; //57.类作用域常量: 用枚举型常量定义
struct Node { Item item; Node* next; };
Node* front;
Node* rear;
int items;
const int qsize;
public:
Queue(int qs = Q_SIZE);
~Queue();
bool isempty() const;
bool isfull() const;
int queuecount() const;
bool enqueue(const Item& item);
bool dequeue(Item& item);
};
对于构造函数,不能写成左边形式,要写成右边形式(用构造函数的初始化列表):
因为qsize是常量成员,常量只能初始化,不能赋值;
构造函数初始化列表:
比如:
定义构造函数时并没有在函数体中对成员变量一一赋值,其函数体为空(当然也可以有其他语句),而是在函数首部与函数体之间添加了一个冒号:
,后面紧跟m_name(name), m_age(age), m_score(score)
语句,这个语句的意思相当于函数体内部的m_name = name; m_age = age; m_score = score;
语句,也是赋值的意思。
构造函数初始化列表还有一个很重要的作用,就是初始化 const 成员变量。初始化 const 成员变量的唯一方法就是使用初始化列表。
队列的实现如下:
//queue.cpp -- implementing the Queue class
//version 00
#include "queue.h"
Queue::Queue(int qs):qsize(qs)
{
front = rear = NULL;
items = 0;
}
//入队:从队尾rear加入元素
bool Queue::enqueue(const Item& item)
{
//如果队列已满,则不能入队
if (isfull())
return false;
Node* add = new Node; //创建一个node
add->item = item;
//add会作为rear(rear=add),所以其next=NULL
add->next = NULL;
items++;
//队列为空时,加入node后,front和rear都指向该node:front = add和rear = add;
if (front == NULL)
front = add;
//队列已经有元素,原始rear指向add,再将add赋值到rear
else
rear->next = add;
rear = add;
return true;
}
//出队:从队头front删除元素
bool Queue::dequeue(Item& item)
{
//如果队列为空,则不能出队
if (front == NULL)
return false;
//将原始front的item保存到形参item
item = front->item;
items--;
//创建temp并指向front所指的node
Node* temp = front;
//front更新指向下一个node
front = front->next;
//销毁原始front所指的内存
delete temp;
//如果此时队列为空,更新rear也指向NULL
if (items == 0)
rear = NULL;
return true;
}
bool Queue::isempty() const { return items == 0; }
bool Queue::isfull() const { return items == qsize; }
int Queue::queuecount() const { return items; }
Queue::~Queue()
{
Node* tmp;
//依次顺着next所指node销毁内存
while (front != NULL)
{
tmp = front;
front = front->next;
delete tmp;
}
}
68.队列(3) —ATM排队模拟
问题描述
模拟ATM机的活动,统计在某段时间中的数据:
- 获得服务的客户数目;
- 被拒绝的客户数目;
- 排队等候的累积时间;
- 累积的队列长度;
ATM机运行的信息:
- 队列的最大长度
- 程序模拟的持续时间(单位为小时)
- 平均每小时的客户数
设计过程
模拟过程:模拟每一分钟发生的事情
- 判断是否来了新的客户,如果来了,并且此时队列未满,则将它添加到队列中,否则拒绝客户入队;
- 如果没有客户在进行交易,则选取队列的第一个客户。确定该客户的已等候时间,并将wait_time计数器设置为新客户所需的处理时间;
- 如果客户正在处理中,则将该客户剩余处理时间wait_time计数器减1;
- 记录其他数据;
问题:
- 如何表示排队:队列类对象;
- 每个客户的信息:到达时间,服务时间;
- 客户的服务时间有多长:随机数;
- 如何获知某一时刻有新客户到达:随机数;
客户类型定义(即Item类):
#pragma once
//queue.h -- Queue class interface
//version 00
#include <iostream>
using namespace std;
typedef class Customer
{
private:
long arrive; //顾客的到达时间
int processtime; //顾客的处理时间
public:
Customer():arrive(0),processtime(0){}//构造函数的初始化列表
void set(long when);
long when() const { return arrive; }
int ptime() const { return processtime; }
}Item;
class Queue
{
private:
enum {Q_SIZE=10}; //57.类作用域常量: 用枚举型常量定义
struct Node { Item item; Node* next; };
Node* front;
Node* rear;
int items;
const int qsize;
public:
Queue(int qs = Q_SIZE);
~Queue();
bool isempty() const;
bool isfull() const;
int queuecount() const;
bool enqueue(const Item& item);
bool dequeue(Item& item);
};
注意到:typedef class Customer
,关键字typedef
为自定义数据类型定义简洁的类型名称;
typedef struct tagPoint
{
double x;
double y;
double z;
} Point;
等价于:
struct tagPoint
{
double x;
double y;
double z;
} ;
//使用 typedef 为这个新的结构起了一个别名,叫 Point
typedef struct tagPoint Point
关于随机数,我们可以使用随机数函数rand()
,在<cstdlib>
中,每次调用会产生一个0
到RAND_MAX
(定义于头文件 <cstdlib>
)之间的整数,而且每个整数出现的概率是相等的。
程序第一次调用rand()
之前需要对rand()
初始化:std::srand(std::time(0))
;
如果客户的服务时间processtime
在1-3分钟之间均匀分布,则有:
void Customer::set(long when)
{
arrive = when;
processtime = std::rand() % 3 + 1;
}
模拟某一分钟是否有顾客到达,假设平均每 min_per_cust
来一个顾客,则有以下bool类型表达式:
std::rand()*min_per_cust/RAND_MAX<1
ATM模拟的具体实现如下:
#include <iostream>
#include <cstdlib>
#include <ctime>
#include "queue.h" //包含类的头文件
using namespace std;
const int MIN_PER_HR = 60;
bool newcustomer(double x)
{
//假设平均每x分钟来一个顾客,下面表达式用于模拟每分钟有没有顾客到达
return (rand() * x / RAND_MAX < 1);
}
int main()
{
srand(time(0));
cout << "Case Study: Bank of Heather Automatic Teller\n";
cout << "Enter maximum size of queue:";
int qs;
cin >> qs;
Queue line(qs);
cout << "Enter the number of simulation hours:";
int hours;
cin >> hours;
long cyclelimit = MIN_PER_HR * hours; //模拟的分钟数
cout << "Enter the average number of customers per hour:";
double perhour; //每小时到达的顾客平均数
cin >> perhour;
double min_per_cust; //每min_per_cust到达一个顾客
min_per_cust = MIN_PER_HR / perhour;
Item temp; //新顾客data
long turnaways = 0; //离开的顾客数
long customers = 0; //加入排队的人数
long served = 0; //加入排队并且得到服务的人数
long sum_line = 0; //整个过程下,每分钟求和一次队列长度
int wait_time = 0; //当前正在服务的人还需要服务多少分钟结束
long line_wait = 0; //得到服务的顾客的等待时间之和(等待时间:从到达直到服务结束)
//模拟
for (int cycle = 0; cycle < cyclelimit; cycle++)
{
if (newcustomer(min_per_cust)) //模拟每分钟是否有顾客到达
{
if (line.isfull()) //如果队列已满
turnaways++; //离开的顾客数+1
else //如果队列还没有满
{
customers++; //加入排队的人数+1
temp.set(cycle); //cycle即当前顾客的到达时间,set同时生成随机的处理时间processtime
line.enqueue(temp); //当前顾客入队
}
}
if (wait_time <= 0 && !line.isempty()) //当前顾客服务完并且队列还有顾客
{
line.dequeue(temp); //当前顾客出队
wait_time = temp.ptime(); //返回当前顾客的处理时间processtime,作为下一个被服务顾客的等待时间
line_wait += cycle - temp.when(); //统计当前结束服务的顾客从到达直到服务结束的时间,并与前面已经服务过的顾客等待时间求和
served++; //已服务过的人数+1
}
if (wait_time > 0)
wait_time--; //模拟每分钟的服务过程
sum_line += line.queuecount(); //每分钟求和一次队列长度
}
if (customers > 0)
{
cout << "customers accepted:" << customers << endl;
cout << "customers served:" << served << endl;
cout << "turnaways:" << turnaways << endl;
cout << "average queue size:";
cout.precision(2);
cout.setf(ios_base::fixed, ios_base::floatfield);
cout << (double)sum_line / cyclelimit << endl; //每分钟的平均队列长度
cout << "average wait time:" << (double)line_wait / served << " minutes\n"; //队列内顾客的平均等待时间
}
else
cout << "No customers!" << endl;
cout << "Done!" << endl;
return 0;
}
我们输入信息后得到模拟的统计结果: