资源描述
电话客户服务模拟
一、 问题描述
一个模拟时钟提供接听电话服务的时间(以分钟记),然后这个时钟将循环的自增一(分钟)直到到达指定时间为止。在时钟的每个“时刻”,就会执行一次检查来看看当前电话的服务是否已经完成了,如果是,这个电话从电话队列中删除,模拟服务将从队列中取出下一个电话(如果有的话)继续开始。同时还需要执行一个检查来判断是否有一个新的电话到达。如果是将其到达时间记录下来,并为其产生一个随机服务时间,这个服务时间也被记录下来,然后这个电话被放入电话队列中,当客户服务人员空闲时,按照先来先服务的方式处理这个队列。当时钟到达指定时间时,不会再接听新的电话,但是服务将继续,直到电话队列中所有电话都得到处理为止。当完成上述功能以后,将记录最佳方案,记录客服人员信息,通过桶存储方式来记录和查询信息。
二、 基本要求
(1)程序需要的初始数据包括:客户服务人员的人数,时间限制,电话的到达速率,平均服务时间,客服人员编号,客服人员姓名。
(2)程序产生的结果包括:处理的电话数,每个电话的平均等待时间,服务总时间,限制时间内接听电话数。
三、工具 / 准备工作
在开始做课程设计项目前,应回顾或复习相关内容。
需要一台计算机,期中安装有Visual C++ 6.0、Visual C++ 2005、Visual C++ 2005 Express、Dev-C++或MinGW Developer Studio之一的集成开发环境。
四、 分析与实现
由于要计算客户的等待时间,并且每个客户都有接受服务所需的时间,为实现这些功能,对客户加上当前接受服务的时间,具体客户结构类型和服务人员类型如下:
// 服务人员类型
struct TelephoneServerType
{
char severNumber[18]; // 服务人员编号
char severName[16]; // 服务人员姓名
bool isEmpty; // 是否为空
int objServerTotalTime; //服务总时间
int objTelephoneOfLimit; //限制时间内接听电话数
};
//客户类型
struct CustomerType
{
unsigned int arrivalTime; //客户到达时刻
unsigned int duration; //客户接受服务所需的时间
unsigned int curServiceTime; //当前接受服务的时间
};
//服务人员类型
struct ServerType
{
int serverTotalTime; //服务总时间
int telephoneOfLimit; //限制时间内接听电话数
};
为了模拟计时,在电话客户服务模拟类中增加表示当前时间的变量curTime,此处时间单位为分钟,为更好地模拟,使用泊松随机数,为模拟客户随机打进电话,需要知道客户到达率(平均每分钟打进电话人数),为模拟客户接受服务的时间,需要知道平均服务时间,为存储服务人员信息,需要创造一个文本,将基桶写入文件中,具体客户服务模拟类声明如下:
template <int m, int b>
TelephoneServer<m, b>::TelephoneServer()
// 操作结果: 初始化服务人员信息
{
//初始化数据成员
curTime=0; //当前时间初值为0
totalWaitingTime=0; //总等待时间初值为0
numOfCalls=0; //处理的电话数初值为0
//获得模拟参数
cout<<"输入客户人员的人数(请输入数字):";
cin>>numOfCustomerServiceStaffs; //输入客户人员的人数
cout<<"输入时间限制(请输入数字):";
cin>>limitTime; //不再接受新电话的时间
int callsPerHour; //每小时电话数
cout<<"输入每小时电话数(请输入数字):";
cin>>callsPerHour; //输入每小时电话数
arrivalRate=callsPerHour/60.0; //转换为每分钟电话数
cout<<"输入平均服务时间(请输入数字):";
cin>>averageServiceTime; //输入平均服务时间
//分配动态存储空间
callsWaitingQueue=new LinkQueue<CustomerType>[numOfCustomerServiceStaffs]; //为客服电话等待队列数组分配存储空间
customerServed=new CustomerType[numOfCustomerServiceStaffs]; //为客服人员正在服务的客户分配存储空间
servers=new ServerType[numOfCustomerServiceStaffs]; //为服务人员分配存储空间
//初始化客服人员正在服务的客户
for(int i=0; i<numOfCustomerServiceStaffs; i++)
{ //初始化每个客服人员正在服务的客户
customerServed[i].curServiceTime=customerServed[i].duration=0; //表示还没有人接受服务
servers[i].serverTotalTime=servers[i].telephoneOfLimit=0; //表示还没有工作
}
//设置随机数种子
SetRandSeed(); //以当前时间值为随机数种子
ifstream iFile("telph.dat"); // 建立输入文件
if (iFile.fail())
{ // 打开文件失败,表示不存在文件
ofstream oFile("telph.dat"); // 建立输出文件
if (oFile.fail()) throw("打开文件失败!"); // 抛出异常
oFile.close(); // 关闭文件
}
else
{ // 存在文件
iFile.close(); // 关闭文件
}
hashFile.open("telph.dat", ios::in|ios::out|ios::binary); // 以读写方式打开文件
if (hashFile.fail()) throw("打开文件失败!"); // 抛出异常
hashFile.seekg(0, ios::end); // 定位到文件尾
int bucketNum = hashFile.tellg() / sizeof(BucketType); // 桶数
if (bucketNum < b)
{ // 桶数不于基桶数,说明文件不完整或已被破坏, 应初始化基桶
BucketType bucket;
int pos; // 临时变量
for (pos = 0; pos < m; pos++)
{ // 初始化基桶
bucket.records[pos].isEmpty = true; // 空记录
bucket.next = -1; // 无溢出
}
hashFile.clear(); // 清除标志
hashFile.seekg(0, ios::beg); // 定位到文件头
for (pos = 0; pos < b; pos++)
{ // 写基桶到文件中
hashFile.write((char *)&bucket, sizeof(BucketType));// 写入基桶
}
}
}
类TelephoneServer的Service()辅助函数为客户服务人员服务当前的电话,如果当前客户接受服务的时间还未到达客户服务所需的时间,则继续在为客户提供服务,否则如有客户在等待服务,则从等待队列中取出新客户进行服务,并更新总客户等待时间,具体实现如下:
template <int m, int b>
void TelephoneServer<m, b>::Service()
//操作结果:服务当前电话(如有电话)
{
for(int i=0;i<numOfCustomerServiceStaffs;i++)
{ //处理每个客服工作人员提供的服务
if(customerServed[i].curServiceTime<customerServed[i].duration)
{ //未到达客户接受服务所需的时间,正在为客户提供服务
customerServed[i].curServiceTime++; //增加客户接受服务的时间
}
else
{ //已到达客户接受服务所需的时间,为下一客户提供服务
if(!callsWaitingQueue[i].Empty())
{ //有客户在等待
callsWaitingQueue[i].OutQueue(customerServed[i]); //从等待队列中取出新客户进行服务
totalWaitingTime+=curTime-customerServed[i].arrivalTime; //更新总客户等待时间
}
}
}
}
类TelephoneServer的CheckForNewCall()辅助函数用于生成当前时间打进电话的人数,对每个打进电话的客户,将其插入最短的客服电话等待队列中,具体实现如下:
template <int m, int b>
void TelephoneServer<m, b>::CheckForNewCall()
//操作结果:检查是否有新电话,如果有,则将电话添加到电话队列
{
int calls=GetPoissionRand(arrivalRate); //当前打进电话的人数
for(int i=1;i<=calls;i++)
{ //第i个电话
CustomerType customer; //客户
customer.arrivalTime=curTime; //客户到达时间
customer.duration=GetPoissionRand(averageServiceTime); //客户接受服务所需的时间
customer.curServiceTime=0; //当前接受服务的时间
int pos=MinLengthCallWaitingQueue(); //最短客服电话等待队列的位置
callsWaitingQueue[pos].InQueue(customer); //客户插入等待队列
numOfCalls++; //处理的电话数
}
}
类TelephoneServer的Display()辅助函数用于在模拟的最后显示处理的总电话数和每个电话的平均等待时间,具体实现如下:
template <int m, int b>
void TelephoneServer<m, b>::Display()
//操作结果:显示模拟结果
{
cout<<"处理的总电话数:"<<numOfCalls<<endl;
cout<<"平均等待时间:"<<GetTheAverageServiceTimet()<<endl<<endl;
}
类TelephoneServer的Run()方法实现模拟电话客户服务,当为到达时间限制时,首先检查是否有新电话,如果有,则将电话添加到电话队列,然后客户服务人员再对当前客户进行服务,最后增加时间;当已到达时间限制时,不再检查是否有新电话,但客户服务人员还要对当前客户进行服务,并增加时间,具体实现如下:
template <int m, int b>
void TelephoneServer<m, b>::Run()
//操作结果:模拟电话客户服务
{
while(curTime<limitTime)
{ //未到达时间限制,可检查新电话
CheckForNewCall(); //检查是否有新电话,如果有,则将电话添加到电话队列
Service(); //进行服务
curTime++; //增加时间
}
while(MinLengthCallWaitingQueue()>0)
{ //在客服电话等待队列中还有客户在等待服务
Service(); //进行服务
curTime++; //增加时间
}
Display();
}
类TelephoneServer中Hash(const char severNumber[18])辅助函数用于生成散列函数值,具体实现如下:
template <int m, int b>
long TelephoneServer<m, b>::Hash(const char severNumber[18])
// 操作结果: 返回散列函数值
{
long h = 0; // 散列函数值
for (int pos = 0; pos < (int)strlen(severNumber); pos++)
{ // 依次处理各数字字符
h = (h * 10 + severNumber[pos] - '0') % b;
}
return h; // 返回散列函数值
}
类TelephoneServer中LocateHelp(const BucketType &bucket, char severNumber[18])辅助函数用于记录服务人员编号在桶中的位置,具体实现如下:
template <int m, int b>
int TelephoneServer<m, b>::LocateHelp(const BucketType &bucket, char severNumber[18])
// 操作结果: 返回服务人员编号severNumber在桶bucket中的位置
{
for (int pos = 0; pos < m; pos++)
{ // 依次比较桶中各服务人员信息存储记录
if (!bucket.records[pos].isEmpty &&
strcmp(bucket.records[pos].severNumber, severNumber) == 0) return pos; // 定位成功
}
return -1; // 定位失败
}
类TelephoneServer中Locate(BucketType &bucket, long &offset,int &pos, char severNumber[18])辅助函数用于定位服务人员编号所在的桶,以及在桶中的位置和桶在文件中的位置,具体实现如下:
template <int m, int b>
void TelephoneServer<m, b>::Locate(BucketType &bucket, long &offset,
int &pos, char severNumber[18])
// 操作结果: 定位服务人员编号severNumber所在的桶bucket, 在桶中的位置pos, 桶在文件
// 中的位置位置offset
{
long h = Hash(severNumber); // 散列函数值
offset = sizeof(BucketType) * h; // 桶在文件中的位置
hashFile.clear(); // 清除标志
hashFile.seekg(offset, ios::beg); // 文件定位
hashFile.read((char *)&bucket, sizeof(BucketType)); // 读取基桶
pos = LocateHelp(bucket, severNumber); // 定位服务人员信息存储记录在桶中的位置
if (pos == -1) offset = bucket.next; // 溢出桶的位置
while (pos == -1 && offset != -1)
{ // 继续在溢出桶中查找
hashFile.clear(); // 清除标志
hashFile.seekg(offset, ios::beg); // 文件定位
hashFile.read((char *)&bucket, sizeof(BucketType)); // 读到基桶
pos = LocateHelp(bucket, severNumber); // 定位服务人员信息存储记录在桶中的位置
if (pos == -1) offset = bucket.next; // 后继溢出桶的位置
}
}
类TelephoneServer中LocateEmptyRecordHelp(const BucketType &bucket)辅助函数用于获得空记录的位置,具体实现如下:
template <int m, int b>
int TelephoneServer<m, b>::LocateEmptyRecordHelp(const BucketType &bucket)
// 操作结果: 返回空记录位位置
{
for (int pos = 0; pos < m; pos++)
{ // 依次比较桶中各服务人员信息存储记录
if (bucket.records[pos].isEmpty) return pos; // 定位成功
}
return -1; // 定位失败
}
类TelephoneServer中LocateEmptyRecord(BucketType &bucket, long &offset, int &pos, char severNumber[18])辅助函数用于服务人员编号所在具有空记录的桶,以及桶中空记录的位置,和桶在文件中的位置,具体实现如下:
template <int m, int b>
void TelephoneServer<m, b>::LocateEmptyRecord(BucketType &bucket, long &offset,
int &pos, char severNumber[18])
// 操作结果: 定位服务人员编号severNumber所在的具有空记录的桶bucket, 桶中的空记录位置pos, 桶
// 在文件中的位置位置offset
{
long h = Hash(severNumber); // 散列函数值
offset = sizeof(BucketType) * h; // 桶在文件中的位置
hashFile.clear(); // 清除标志
hashFile.seekg(offset, ios::beg); // 文件定位
hashFile.read((char *)&bucket, sizeof(BucketType)); // 读到基桶
pos = LocateEmptyRecordHelp(bucket); // 定位桶中空记录的位置
if (pos == -1) offset = bucket.next; // 溢出桶的位置
while (pos == -1 && offset != -1)
{ // 继续在溢出桶中查找
hashFile.clear(); // 清除标志
hashFile.seekg(offset, ios::beg); // 文件定位
hashFile.read((char *)&bucket, sizeof(BucketType)); // 读到基桶
pos = LocateEmptyRecordHelp(bucket); // 定位桶中空记录的位置
if (pos == -1) offset = bucket.next; // 后继溢出桶的位置
}
}
类TelephoneServer中Input()辅助函数用于输入记录并且将数据写入通过桶写入到文件中,具体实现如下:
template <int m, int b>
void TelephoneServer<m, b>::Input()
// 操作结果: 输入记录
{
TelephoneServerType telph; // 服务人员信息存储记录
telph.isEmpty = false; // 标记
cout << "输入服务人员编号:";
cin >> telph.severNumber;
cout << "输入服务人员姓名:";
cin >> telph.severName;
telph.objServerTotalTime=GetTheServerTotalTime();
telph.objTelephoneOfLimit=GetTheTelephoneOfLimit();
BucketType bucket; // 桶
long offset; // 桶在文件中的相应位置
int pos; // 服务人员信息存储记录在桶中的位置
Locate(bucket, offset, pos, telph.severNumber); // 定位服务人员信息存储记录的位置
if (pos != -1)
{ // 定位成功
cout << "编号已在散列文件中!" << endl;
}
else
{ // 定位失败
LocateEmptyRecord(bucket, offset, pos, telph.severNumber);// 定位空记录位置
if (pos != -1)
{ // 找到空记录
bucket.records[pos] = telph; // 将服务人员信息存储记录赋值给bucket.records[pos]
hashFile.clear(); // 清除标志
hashFile.seekg(offset, ios::beg); // 定位文件
hashFile.write((char *)&bucket, sizeof(BucketType));// 写桶
}
else
{
hashFile.clear(); // 清除标志
hashFile.seekg(0, ios::end); // 定位到文件尾
bucket.next = hashFile.tellg(); // 后继溢出桶位置
hashFile.clear(); // 清除标志
hashFile.seekg(offset, ios::beg); // 定位文件
hashFile.write((char *)&bucket, sizeof(BucketType));// 写桶
offset = bucket.next; // 新溢出桶在文件中的位置
for (pos = 1; pos < m; pos++)
{ // 设置空记录
bucket.records[pos].isEmpty = true;
}
pos = 0; // 服务人员信息存储记录的位置
bucket.records[pos] = telph; // 将服务人员信息存储记录赋值给bucket.records[pos]
hashFile.clear(); // 清除标志
hashFile.seekg(offset, ios::beg); // 定位文件
hashFile.write((char *)&bucket, sizeof(BucketType));// 写桶
}
}
}
类TelephoneServer中Serach()辅助函数用于查找客服人员的信息,通过输入客服人员编号获得其在文件中的位置并显示在屏幕上,具体实现如下:
template <int m, int b>
void TelephoneServer<m, b>::Serach()
// 操作结果: 查找记录
{
char severNumber[18]; // 服务人员编号
cout << "输入服务人员编号:";
cin >> severNumber;
BucketType bucket; // 桶
long offset; // 桶在文件中的相应位置
int pos; // 服务人员信息存储记录在桶中的位置
Locate(bucket, offset, pos, severNumber);// 定位服务人员信息存储记录的= -1)
if (pos == -1)
{ // 定位失败
cout << "查找失败!" << endl;
}
else
{
hashFile.clear(); // 清除标志
hashFile.seekg(offset, ios::beg); // 定位文件
hashFile.read((char *)&bucket, sizeof(BucketType)); // 读桶
cout << "服务人员编号:" << bucket.records[pos].severNumber << endl;
cout << "服务人员姓名:" << bucket.records[pos].severName << endl;
cout << "服务总时间:" << bucket.records[pos].objServerTotalTime << endl;
cout << "限制时间内接听电话数:" << bucket.records[pos].objTelephoneOfLimit << endl;
}
}
类TelephoneServer中ServerRun()辅助函数用于处理客服人员信息,通过获得相应的信息,从而链接文本中的桶,具体实现如下:
template <int m, int b>
void TelephoneServer<m, b>::ServerRun()
// 操作结果: 处理服务人员信息
{
int select; // 临时变量
do
{
cout << "1.输入信息 2.查找信息 3.退出" << endl;
cout << "输入选择:";
cin >> select; // 输入选择
while (GetChar() != '\n'); // 跳过当前行的其它字符
switch (select)
{
case 1:
Input(); // 输入记录
break;
case 2:
Serach(); // 查找记录
break;
}
} while (select != 3);
}
五、 测试与结论
测试时,假设时间限制为600(分钟),每小时电话数为60,平均服务时间为1(分钟),对不同客服人员人数进行模拟如下:
综合可知,当有3个客服人员是比较恰当的,故开始记录客服人员信息。
六、 课程设计总结
主要是对于信息记录的扩展。首先是计算最佳服务方式需要的客服人员人数;当接近平均等待时间为1(分钟)时,此时开始记录客服人员信息,信息内容有客服人员编号和姓名。该功能是通过桶存储方式写入到文本中,然后以客服人员编号为比较信息在存储文本里查找和写入。另外还有就是计算客服人员的客服人员服务总时间和在限制时间内接听电话数,并且将该数据加入到服务人员类型里面,然后通过函数传递的方式传递,然后写入桶中,在查找信息的时候随其他信息输出。在完成该功能的过程中,我是在桶一文件中声明了两个类,但是在传递数据的时候有错误,就是在其中一个类中是正确的数据,但是我是把该数据加入到加入到客户类型里,然后通过该结构变量调用,但是传递到另一个类中的数据就是乱码,我也不知道怎么回事。通过自己手动编写这个项目,体会到自己的要学的还很多,另外就是熟悉了以前所不熟悉的一些知识,比如结构的使用,还有链表的使用等等,我相信多动手会让我收获更多。
展开阅读全文