阅读更多

2顶
0踩

编程语言

转载新闻 C++ 实现银行排队服务模拟

2016-07-25 14:23 by 副主编 mengyidan1988 评论(0) 有8180人浏览
c++
来源:Wayne Shi
教程简介:使用 C++对银行排队服务进行模拟,以事件驱动为核心思想,手动实现模板链式队列、随机数产生器等内容,进而学习概率编程等知识。作为可选进阶,这个模型同时还能稍加修改的应用到 CPU 资源争夺模型中。

一、概述

实验所需的前置知识
  • C++ 基本语法知识

实验所巩固并运用的知识
  • OOP 编程思想
  • std::rand() 函数原理
  • 概率编程
  • 排队理论
  • 链式队列数据结构及其模板实现
  • 事件驱动的设计
  • 蒙特卡洛方法
  • C++ 动态内存管理和设计理念
  • CPU 资源争夺模型
  • 时间片轮转调度


要解决的问题

蒙特卡洛方法这个名字听起来很高大上,但它的本质其实是使用计算机的方法对问题进行模拟和复现。本次实验将使用蒙特卡洛方法来模拟银行排队这个问题:
引用

端午节当天,某个银行从早上八点开始服务并只服务到中午十二点就停止营业。假设当天银行只提供了 w 个服务窗口进行服务,问:
平均每分钟有多少个顾客抵达银行?
平均每个顾客占用服务窗口的时间是多少?

我们先来分析一下这个业务的逻辑:

首先我们要分析银行提供服务的逻辑。在银行服务中,所有顾客都是通过取号排队的方式等待服务的,这和火车站买票有所不同,在火车站买票时,顾客必须在某一个窗口所排的队列下进行排队,且无法变更自己所属的窗口,否则只能从队尾重新排队。换句话说,对于银行提供的服务来说,所有用户都是位于同一个队列上的,当某个服务窗口可用时,才会从排队队列的队首取出一个新的用户来办理银行业务。即代码实现过程中,服务窗口可以创建 w 个,但只需要实现一个顾客队列即可。

其次,对于顾客而言,有两个属性是能够被抽象出来的:

1.到达银行的时间;
2.需要服务的时间。
并且,这两个属性是随机的。到此,我们整个的排队模型就变成了:

下面我们来详细对这个问题的实现逻辑进行分析,让我们的程序能够给出类似下面的结果:






第一部分、银行排队系统的设计与建模

一、计算机中的随机

std::rand() 函数的原理

C++ 中的 std::rand() 函数产生的随机数并不是真正意义上的随机数,它并不服从数学上的均匀分布。为了使我们的模拟系统变得更加真实,我们需要知道 std::rand() 函数的原理。

std::rand() 生成的是一个随机的二进制序列(在硬件底层更好实现),这个序列的每一位0或者1的概率都是相等的。而对于 std::rand()%n 这个运算,会在 [0, n-1] 之间生成随机数,所以,如果 n-1 的二进制表示的值不都是由 1 组成,那么这里面的数是不会从均匀分布了(因为某些位可能不能为 1)。

所以,当且仅当 [0, n-1] 中的随机数可以用这个序列的子序列表示时,才能满足均匀分布。换句话说,仅当 n-1 的二进制数全为1 时,0,1出现的概率才是均等的。

我们先来实现随机这个类:
//
//  Random.hpp
//  QueueSystem
//

#ifndef Random_hpp
#define Random_hpp

#include <cstdlib>
#include <cmath>

class Random {
public:
    // [0, 1) 之间的服从均匀分布的随机值
    static double uniform(double max = 1) {
        return ((double)std::rand() / (RAND_MAX))*max;
    }
};
#endif /* Random_hpp */

这样的话,当我们调用 Random::uniform() 时,便能获得真正的服从均匀分布的随机数了。当指定参数后,便能够生成 [0, max) 之间的随机值了。

二、主函数逻辑设计

对于一个银行而言,对外界来说只需要提供两个参数:

1.总共的服务时间
2.服务窗口的数量
所以我们希望实现这样的代码:
// main.cpp
// QueueSystem
//

#include "QueueSystem.hpp"

#include <iostream>
#include <cstdlib>

int main() {

    std::srand((unsigned)std::time(0)); // 使用当前时间作为随机数种子

    int total_service_time = 240;       // 按分钟计算
    int window_num         = 4;
    int simulate_num       = 100000;    // 模拟次数

    QueueSystem system(total_service_time, window_num);
    system.simulate(simulate_num);

    std::cout << "The average time of customer stay in bank: "
              << system.getAvgStayTime() << std::endl;
    std::cout << "The number of customer arrive bank per minute: "
              << system.getAvgCustomers() << std::endl;

    return 0;
}

三、对象及逻辑设计

总结一下,现在我们需要实现的东西有:

1.服务窗口类(会被创建 w 个)
2.顾客队列类(只会被创建一个)
3.顾客结构(包含两个随机属性: 到达时间, 服务时间)
为了更好练习 C++,我们会弃用诸如 vector 这些快捷编码的标准库来进行『过度编码』,自行编写模板类。

根据前面的问题描述,我们可以初步确定这样一些类的设计需求:

1.QueueSystem 类: 负责整个队列系统的模拟;
2.ServiceWindow 类: 队列系统的服务窗口对象,每当一个银行创建时,服务窗口会被创建,为了让整个问题更加灵活,我们假设需要创建 window_num 个窗口;
3.Queue 类: 银行队列系统的顾客排队的队列;
4.Random 类: 在第二节中已经讨论过。
然而,在设计 ServiceWindow 之前,我们要考虑 ServiceWindow 类到底要放置什么成员,首先,对于一个服务窗口,会有一个顾客属性,用于存放顾客。另一方面,一个窗口只会有两种状态:要么正在服务(被占用),要么空闲。因此 ServiceWindow 中首先会有下面的枚举:
// ServiceWindow.hpp
// QueueSystem
//

enum WindowStatus {
    SERVICE,
    IDLE,
};

既然我们要在 ServiceWindow 中存放顾客,由于顾客本身并不需要提供什么方法,因此可以直接将顾客设计为一个结构体 Customer,同时,顾客也会成为等待队列中的一员。所以,Customer 也可以被称之为队列的一个 Node,此外,每个顾客说需要的服务时间是随机的,但是到达时间并不应该由顾客自身确定(我们在下一节再讨论为什么),所以Customer结构的默认构造应该被设计出来:
//  Node.h
//  QueueSystem
//

#ifndef Node_h
#define Node_h

#include "Random.hpp"

#define RANDOM_PARAMETER 100

struct Node {
    int arrive_time;
    int duration;
    struct Node *next;

    // 默认到达事件为0,需要服务的事件是随机的
    Node(int arrive_time = 0,
         int duration = Random::uniform(RANDOM_PARAMETER)):
        arrive_time(arrive_time),
        duration(duration),
        next(nullptr) {}
};

typedef struct Node Node;
typedef struct Node Customer;

#endif /* Node_h */

那么,结合前面的 WindowStatus枚举和 Customer结构,我们的 ServiceWindow 类可以这样设计,因为窗口本身涉及的操作还算是比较简单,比如设置窗口状态是否繁忙,获取当前服务顾客的到达时间来方便后续计算等等,因此我们直接将其设计成类内的 inline 函数:
//  ServiceWindow.hpp
//  QueueSystem
//

#ifndef ServiceWindow_hpp
#define ServiceWindow_hpp

#include "Node.hpp"

enum WindowStatus {
    SERVICE,
    IDLE,
};

class ServiceWindow {
public:
    inline ServiceWindow() {
        window_status = IDLE;
    };
    inline bool isIdle() const {
        if (window_status == IDLE) {
            return true;
        } else {
            return false;
        }
    }
    inline void serveCustomer(Customer &customer) {
        this->customer = customer;
    }
    inline void setBusy() {
        window_status = SERVICE;
    }
    inline void setIdle() {
        window_status = IDLE;
    }
    inline int getCustomerArriveTime() const {
        return customer.arrive_time;
    }
    inline int getCustomerDuration() const {
        return customer.duration;
    }
private:
    Customer customer;
    WindowStatus window_status;
};

#endif /* ServiceWindow_hpp */

三、事件驱动的设计

有了上面的这些设计,似乎我们只要编写好用户排队队列,就已经足够描述整个排队的系统了,然而,在上面的设计中,还有一个很大的问题,那就是:整个系统还处于静止状态。当顾客位于等待队列时,窗口什么时候服务下一个顾客,如何处理这里面的逻辑,到目前为止,我们都没有思考过。

为了让整个系统『运行』起来,我们还要考虑整个系统的运行时间线。这里我们给出一种事件驱动的设计。

在前面的分析中,我们知道整个系统中,无非出现两种事件:

1.有顾客到达
2.有顾客离开
其中,第二种顾客离开的事件,同时还包含了窗口服务等待队列中的下一个顾客这个事件。所以,我们如果能够维护一个事件列表,那么就能够驱动整个队列系统的运行了。因为,当事件发生时,我们通知这个队列系统更新他自身的状态即可。

综上所述,我们可以先设计事件表中的事件结构:
//  Event.hpp
//  QueueSystem
//

#ifndef Event_hpp
#define Event_hpp

#include "Random.hpp"
#define RANDOM_PARAMETER 100

struct Event {
    int occur_time;

    // 使用 -1 表示到达事件, >=0 表示离开事件, 同时数值表示所离开的服务窗口
    int event_type;

    Event* next;

    // 默认为到达事件,发生事件随机
    Event(int occur_time = Random::uniform(RANDOM_PARAMETER),
          int event_type = -1):
        occur_time(occur_time),
        event_type(event_type),
        next(nullptr) {}
};

#endif /* Event_hpp */

这里我们使用了一个小小的 trick,那就是用整数来表示事件的类型,而不是简单的使用枚举。

这是因为,对于 ServiceWindow 来说,我们可以使用数组来管理多个 ServiceWindow,那么对应的事件类型如果涉及为整数,事件类型就可以同时作为 ServiceWindow 的索引下标了,当 event_type 大于等于 0 时,数值还表示离开的服务窗口。

又因为事件列表、顾客队列,本质上可以归类为同一个结构,那就是队列:只不过他们的入队方式有所差异,对于事件列表而言,入队方式必须按发生事件的时间顺序入队,而对于顾客,则是直接添加到队尾。考虑到了这一点,我们便能很容易的利用模板来设计队列的基本需求了:
//  Queue.hpp
//  QueueSystem
//

#ifndef Queue_hpp
#define Queue_hpp

#include <iostream>
#include <cstdlib>

#include "Event.hpp"

// 带头结点的队列
template <typename T>
class Queue
{
public:
    Queue();
    ~Queue();
    void clearQueue();             // 清空队列
    T* enqueue(T &node);
    T* dequeue();
    T* orderEnqueue(Event &event); // 只适用于事件入队
    int  length();
private:
    T *front;  // 头结点
    T *rear;   // 队尾
};
#endif /* Queue_hpp */

四、QueueSystem

经过前面的讨论,我们已经完成了对所有基本结构的设计,根据这些设计,我们能够初步确定我们要实现的队列系统的基本结构。

首先,根据对主函数的设计,初始化整个队列系统我们需要两个参数:
  • 银行的总服务时间(分钟) int total_service_time
  • 银行开放的服务窗口数 int window_num

其次,我们需要 QueueSystem 发开放至少三个接口:
  • 模拟 simulate()
  • 获得顾客平均逗留时间 getAvgStayTime()
  • 获得平均每分钟顾客数 getAvgCustomers()

第三,内部需要实现的内容包括:
  • 系统运行前的初始化 init()
  • 让系统运行的 run()
  • 系统结束一次运行的清理工作 end()

第四,整个系统需要管理的核心成员有:
  • 可供服务的窗口 ServiceWindow* windows
  • 顾客等待队列 Queue<Customer> customer_list
  • 事件列表 Queue<Event> event_list
  • 当前的系统事件 Event* current_event

第五,处理事件的方法:
  • 处理顾客到达事件 void customerArrived()
  • 处理顾客离开事件 void customerDeparture()

最后,我们所希望的平均顾客逗留时间和平均每分钟的顾客数涉及的四个变量:
  • 顾客的总逗留时间 int total_customer_stay_time
  • 一次运行中系统服务的中顾客数量 int total_customer_num
  • 每分钟平均顾客数 double avg_customers
  • 顾客平均逗留时间 double avg_stay_time

事实上,可以预见的是,在处理顾客服务逻辑的时候,我们还需要一个方法getIdleServiceWindow 来获取当前服务窗口的状态,从而增加代码的复用度。

所以,整个 QueueSystem 类的代码设计为:
//
//  QueueSystem.hpp
//  QueueSystem
//

#ifndef QueueSystem_hpp
#define QueueSystem_hpp

#include "Event.hpp"
#include "Queue.hpp"
#include "ServiceWindow.hpp"

class QueueSystem {

public:
    // 初始化队列系统
    QueueSystem(int total_service_time, int window_num);

    // 销毁
    ~QueueSystem();

    // 启动模拟
    void simulate(int simulate_num);

    inline double getAvgStayTime() {
        return avg_stay_time;
    }
    inline double getAvgCustomers() {
        return avg_customers;
    }

private:
    // 让队列系统运行一次
    double run();

    // 初始化各种参数
    void init();

    // 清空各种参数
    void end();

    // 获得空闲窗口索引
    int getIdleServiceWindow();

    // 处理顾客到达事件
    void customerArrived();

    // 处理顾客离开事件
    void customerDeparture();

    // 服务窗口的总数
    int window_num;

    // 总的营业时间
    int total_service_time;

    // 顾客的逗留总时间
    int customer_stay_time;

    // 总顾客数
    int total_customer_num;

    // 核心成员
    ServiceWindow*  windows;
    Queue<Customer> customer_list;
    Queue<Event>       event_list;
    Event*          current_event;

    // 给外部调用的结果
    double avg_customers;
    double avg_stay_time;

};

#endif /* QueueSystem_hpp */

本节回顾

在这一节中,我们设计了整个银行排队系统的基本逻辑,并借鉴了事件驱动的思想设计了驱动队列系统的事件类。本节中我们一共创建了:
  • Event.hpp
  • Node.hpp
  • Queue.hpp
  • Random.hpp
  • ServiceWindow.hpp
  • QueueSystem.hpp
  • main.cpp

现在我们的代码还不能够直接运行,本节我们先关注理清我们的业务逻辑。在下一节中,我们将实现这些代码的详细逻辑,这包括:
  • Queue.hpp 中模板链式队列的具体实现
  • QueueSystem.cpp 中的详细服务逻辑
  • Random.hpp 中更复杂的随机概率分布

在这些实现中,我们将进一步巩固下面的知识的运用:
  • C++ 类模板
  • 链式队列的数据结构
  • 概率编程
  • C++ 中内存管理和设计理念
  • 大小: 41.5 KB
  • 大小: 142.3 KB
2
0
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • 中文乱码处理汇总.docx

    中文乱码处理汇总.docx

  • jboss JAVA乱码_tomcat,jboss,weblogic乱码问题以及Java中各种乱码问题大汇总

    InputStreamReader读取utf-8文件中文乱码处理中文编码问题、编码原理服务器编码问题1. tomcat服务器乱码处理设置server.xml,找到这一段connectionTimeout="20000"redirectPort="8443"/&gt;加上URIEnco...

  • java乱码处理汇总

    乱码的处理!程序员永远无法避免的话题,解决乱码总汇

  • 解析关于java,php以及html的所有文件编码与乱码的处理方法汇总

    本篇文章是对关于java,php以及html的所有文件编码与乱码的处理方法进行了详细的总结与介绍,需要的朋友参考下

  • java乱码处理大全_JAVA WEB开发中处理乱码汇总

    一、表单提交的乱码处理表单提交分为GET和POST两种提交方式。两种方式的乱码解决又不一样,用POST提交只需要在接受的时候加上request.setCharacterEncoding("utf-8");而GET方法处理应该将接收过来的值打碎成ISO-8859...

  • springboot 添加拦截器之后中文乱码_Spring Boot 中文乱码问题解决方案汇总

    使用 Spring Boot 开发,对外开发接口供调用,传入参数中有中文,出现中文乱码,查了好多资料,总结解决方法如下:第一步,约定传参编码格式不管是使用httpclient,还是okhttp,都要设置传参的编码,为了统一,这里...

  • java exception 乱码_java 中的乱码问题汇总及解决方案

    java中的乱码问题最近做项目经常会遇到Java中的乱码问题,于是就抽时间整理下出现乱码问题的情况和如何处理,这里做了一个整理,分析编码与解码编码就是将字符转为字节,解码就是就是将字节转换为字符。字节流与字符...

  • get请求中文传参乱码问题解决汇总终极版

    get请求中文传参乱码问题解决汇总终极版 一、如果使用tomcat作为服务器,那么修改tomcat配置文件conf/server.xml中,在 &amp;lt;Connector port=”8082” protocol=”HTTP/1.1” &amp;gt; 中加入 URIEncoding=”utf...

  • java web 乱码_java web中中文乱码问题汇总

    1,jsp页面中文乱码解决办法:文件头部定义指定页面显示编码page language="java" import="java.util.*" contentType="text/html;charset=GB2312" %&gt;2,URL传递参数中文乱码解决方法:修改Tomcat服务器的配置文件...

  • 我的乱码问题处理汇总

    下面是我解决乱码时候的一点小经验。欢迎指正 一、避免乱码的一些注意点: 1.尽量使用统一的编码,如果你是重头开发一个系统,特别是Java开发的,推荐从页面到数据库再到配置文件都使用UTF-8进行编码,安全第一。 2....

  • 乱码问题处理汇总

    下面是我解决乱码时候的一点小经验。欢迎指正   一、避免乱码的一些注意点: 1.尽量使用统一的编码,如果你是重头开发一个系统,特别是Java开发的,推荐从页面到数据库再到配置文件都使用UTF-8

  • Node.js数据抓取乱码问题汇总

    一般简单使用后,还是乱码 形如:пїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ。所有这里主要说的是 Windows-1251(cp1251)编码与utf-...

  • VScode中文输出乱码处理方法(汇总)

    VScode 中文乱码python版本 在使用vscode的过程中,发现中文输出乱码;...目前已知问题,软件会乱码,安装和卸载中文软件,例如有道词典查词也会乱码, 二、临时处理方法 chcp命令 终端输入:chcp 65001

  • Web开发中文乱码问题汇总

    1.POST方式POST请求数据是通过HTTP的BODY传递数据到服务器,浏览器会根据ContentType的编码格式对参数进行编码,服务端也是通过ContentType的编码格式进行解码,所以一般不会有乱码问题,而且我们可以通过request....

  • 人力资源经理绩效考核表.xls

    人力资源经理绩效考核表

  • 智慧环卫管理平台建设方案Word(211页).docx

    一、智慧环卫管理平台的建设背景与目标 智慧环卫管理平台的建设源于对环卫管理全面升级的需求。当前,城管局已拥有139辆配备车载GPS系统、摄像头和油耗传感器的环卫车辆,但环卫人员尚未配备智能移动终端,公厕也缺乏信息化系统和智能终端设备。为了提升环卫作业效率、实现精细化管理并节省开支,智慧环卫管理平台应运而生。该平台旨在通过信息化技术和软硬件设备,如车载智能终端和环卫手机App,实时了解环卫人员、车辆的工作状态、信息和历史记录,使环卫作业管理透明化、精细化。同时,平台还期望通过数据模型搭建和数据研读,实现更合理的环卫动态资源配置,为环卫工作的科学、健康、持续发展提供决策支持。 二、智慧环卫管理平台的建设内容与功能 智慧环卫管理平台的建设内容包括运行机制体制建设、业务流程设计、智慧公厕系统建设、网络建设、主机和储存平台需求、平台运维管理体系、硬件标准规范体系以及考核评价体系等多个方面。其中,智慧公厕系统建设尤为关键,它能实时监控公厕运行状态,保障公厕的清洁和正常运行。平台建设还充分利用了现有的电子政务网络资源,并考虑了有线和无线网络的需求。在功能上,平台通过普查、整合等手段全面收集环卫车辆、企业、人员、设施、设备等数据,建立智慧环卫基础数据库。利用智能传感、卫星定位等技术实现环卫作业的在线监管和远程监控,实现对道路、公共场所等的作业状况和卫生状况的全面监管。此外,平台还建立了环卫作业网格化管理责任机制,实现从作业过程到结果的全面监管,科学评价区域、部门、单位和人员的作业效果。 三、智慧环卫管理平台的效益与风险规避 智慧环卫管理平台的建设将带来显著的环境、经济和管理效益。环境方面,它将有力推进环境卫生监管服务工作,改善环境卫生状况,为人民群众创造更加清洁、卫生的工作和生活环境。经济方面,通过智慧化监管,大大降低了传统管理手段的成本,提高了监管的准确性和效率。管理方面,平台能够追踪溯源市民反映的问题,如公厕异味、渣土车辆抛洒等,并找到相应的责任单位进行处置,防止类似事件再次发生。同时,平台还拥有强大的预警机制功能,能够在很多环卫问题尚未出现前进行处置。然而,平台建设也面临一定的风险,如部门协调、配合问题,建设单位选择风险以及不可预测的自然灾害等。为了规避这些风险,需要加强领导、统一思想,选择优秀的系统集成商承接项目建设,并做好计算机和应用系统的培训工作。同时,也要注意标准制定工作和相关法律法规的制定工作,以保证系统建设完成后能够真正为环卫管理工作带来便利。

  • apache-parent-10-14.el7.x64-86.rpm.tar.gz

    1、文件内容:apache-parent-10-14.el7.rpm以及相关依赖 2、文件形式:tar.gz压缩包 3、安装指令: #Step1、解压 tar -zxvf /mnt/data/output/apache-parent-10-14.el7.tar.gz #Step2、进入解压后的目录,执行安装 sudo rpm -ivh *.rpm 4、安装指导:私信博主,全程指导安装

Global site tag (gtag.js) - Google Analytics