小項目分析之C++ 實現模擬銀行排隊


 
一、問題定義與分析
問題定義
•要解決的問題——銀行一天之內的:

   1.總客戶數

   2.客戶總逗留時間

   3.客戶平均逗留時間

問題分析

•新來的人找個短的隊伍,站在隊尾開始排隊
•排在隊頭的人可以辦理業務
•排隊等待辦業務的客戶是在分散的、隨機的時間點到來的
•特點:離散事件、要排隊
•掌握每個客戶到達銀行和離開銀行這兩個時刻
•統計出客戶總數
•稱客戶到達銀行和客戶離開銀行這兩個時刻發生的事情為“事件”
•整個模擬按事件發生的先后順序進行處理
•事件驅動模擬
•事件的主要信息是事件類型和事件發生的時刻
•兩類事件:客戶到達事件和客戶離開事件
•事件應存儲在有序表里
•有序表按照事件發生的時刻順序排序
•隊列中的客戶的主要信息是客戶到達的時刻和客戶辦理業務所需要的時間
•隊列數量和銀行的窗口數量相同
•每個隊列的隊頭客戶就是正在辦理業務的客戶
•每個隊頭客戶都存在一個將要發生的客戶離開事件
 
二、類與算法設計
類設計
•有序表選用有序鏈表,主要操作是插入和刪除
•隊列,客戶排隊
 
•類圖:用來表示類以及類和類之間的關系的邏輯視圖
•利用類圖來記錄類的結構,這些類構成了程序的架構

銀行類:

•要有一個表示隊列數量的屬性
•要有一個打烊時間屬性
•要有一個總客戶數屬性
•要有一個客戶總逗留時間屬性
•聚合一個或多個隊列和一個有序鏈表

銀行類的方法:

•開門營業
•處理客戶到達事件
•處理客戶離開事件
•幫助客戶選擇一個最短的隊列
•確保按照事件發生的時間順序處理事件
 
銀行類類圖:
 

隊列:

•使用STL中的queue
•queue的節點:

有序鏈表:

•使用STL中的list
•list的節點:
 
算法設計

void Bank::Simulation()算法:

1.開門營業,OpenForDay()

2.如果有事件發生,那么:

  (1) 對於到達事件,處理到達事件,CustomerArrived(Event *event)

  (2) 對於離開事件,處理離開事件,CustomerDeparture(Event *event)

3.重復第2步

4.輸出統計結果

void Bank::OpenForDay()算法:

   1.初始化_queue_number為某個正整數

   2.初始化_close_time為以秒為單位的時間,比如8*3600,表示8個小時

   3.初始化_total_time為0

   4.初始化_customer_number為0

   5.設定第一個客戶到達事件,客戶到達時刻為0

   6.隊列和有序鏈表的初始化(這是由C++STL類自己完成的)

void CustomerArrived(Event *event)算法:

  1.產生隨機數:客戶辦理業務需要的時間,假設一個客戶最多需要30分鍾

  2.產生隨機數:下一個客戶到達的時間間隔,假設最多10分鍾來一個客戶

  3.下一個客戶到達時刻是當前事件發生時刻和時間間隔的和

  4.如果到達時刻銀行沒有下班,產生一個新的客戶到達事件插入事件有序鏈表

  5.給鏈表按事件的發生時刻排序(因為STL中沒有有序鏈表)

  6.找一個最短的隊列

  7.在最短的隊列中插入新到的客戶

  8.如果隊列中有且只有一個客戶,生成該客戶的一個離開事件插入到事件表

 這種情況下,離開事件發生時刻=到達時刻+辦理業務需要的時間

  9.統計客戶數量

void CustomerDeparture(Event *event)算法:

  1.計算該客戶在銀行中的逗留時間,並且累加總逗留時間

      客戶在銀行中的逗留時間=客戶離開事件發生時刻-客戶到達時刻

  2.從隊列中刪除該客戶

  3.如果隊列不空則設定一個新的隊頭客戶離開事件

    隊頭離開事件發生時刻=上個離開事件發生時刻(隊頭開始辦業務的時刻)+隊頭辦業務需要時間

 
三、C++程序編寫
 附注:
Lambda表達式簡介
•只有當可以應用一個()調用操作符符到一個對象時,這個對象才是可調用對象
•函數:可調用對象
•函數指針:可調用對象
•重載了()調用操作符的類:可調用對象
•Lambda表達式:可調用對象
•一個Lambda表達式表示了一個可調用的代碼單元
•可以認為是一個無名的、內聯的函數
•可以定義在函數內部
•有一個返回類型、一個參數列表和一個函數體

[capture list](parameter list)->return type {function body}

[capture list](parameter list)->return type {function body}

•capture list:Lambda表達式所在的函數的局部變量
•把局部變量寫在capture list里可以在Lambda表達式內部使用它們
•return type:返回類型(尾置返回指定方式)
•parameter list:參數列表
•function body:函數體

舉例:

_event_list.sort(

      [](const Event &e1, const Event &e2) -> bool

      {return e1._occur_time < e2._occur_time;});

定義頭文件

•頭文件中通常包含類的定義、常量的定義和常量表達式的定義
•習慣:頭文件的名字通常和其中定義的類的名字相同
•頭文件可以用#include包含其它頭文件
•#include是預處理器提供的功能
•為了讓頭文件可以被安全地包含多次,就需要使用預處理器
•預處理器不是C++語言的一部分
•可以用#define定義一個預處理變量
•預處理器變量有兩種狀態:己定義和未定義
•#ifdef:如果預處理變量己定義,返回true
•#ifndef:如果預處理量未定義,返回true
•只有測試為true時,才繼續預處理直到#endif
•頭文件保護符,以bank.h為例 :

  #ifndef __BANK_H__

  #define __BANK_H__

  // #include<其它頭文件>

  // const、constexpr and class 定義

  // extern 多文件共享變量聲明

  #endif

 

<------以下為實現代碼------>

 
bank.cc
#include "stdafx.h"
#include "bank.h"
#include <iostream>
#include <clocale>
#include <chrono>
#include <cstdlib>

/*
#include <algorithm>
std::sort(_work_queue, _work_queue + _queue_number, 
      [](const std::queue<QueueNode> &q1, 
        const std::queue<QueueNode> &q2) -> bool
      {return q1.size() < q2.size();});
*/

Bank::Bank(int window, int close_time)
:_queue_number(window), _close_time(close_time),
  _total_time(0), _customer_number(0)
{
  _work_queue = new std::queue<QueueNode>[window];

  srand(std::chrono::system_clock
      ::to_time_t(std::chrono::system_clock::now()));
}

Bank::~Bank()
{
  delete[] _work_queue;
}

void Bank::OpenForDay()
{
  // 第一個客戶到達
  _event_list.push_back({0, 0});
}

// 客戶到達事件
// 客戶到達時,有三件事需要做:
// 1:為此時刻到達的客戶隨機產生一個辦理事務需要時間
// 2:隨機產生下一客戶到達的時間間隔
// 3:把到達的客戶放入一個最短的工作隊列
void Bank::CustomerArrived(Event *event)
{
  ++_customer_number;

  int duration_time, inter_time;

  // 此時刻到達客戶辦理事務需要時間
  duration_time = rand() % 1800 + 1; //一個客戶最多要30分鍾
  // 下一個客戶在從event->_occur_time+inter_time時刻到來
  inter_time = rand() % 600 + 1; // 最多10分鍾來一個客戶

  // 下一個客戶到達時間
  int t = event->_occur_time + inter_time;

  // 銀行還沒有關門
  if(t < _close_time) {
    _event_list.push_back({t, 0});
    // 按到達時間排序事件表,早前晚后
    SortEventList();
  }
  // 選一個最短隊列排隊
  int i;
  i = FindShortestQueue();
  
  _work_queue[i].push({event->_occur_time, duration_time});

  if(_work_queue[i].size() == 1) {
  // 這個i隊列第一個客戶,生成他的離開事件
    _event_list.push_back(
        {event->_occur_time + duration_time, i + 1});
    SortEventList();
  }
}

void Bank::CustomerDeparture(Event *event)
{
   int i = event->_type - 1;

   QueueNode customer;

   // 客戶事務處理完畢,離開
   customer = _work_queue[i].front();
   _work_queue[i].pop();

   _total_time 
     += event->_occur_time - customer._arrival_time;

   // 第i個隊列的一個離開事件
   if(!_work_queue[i].empty()) {
     customer = _work_queue[i].front();
    _event_list.push_back(
        {customer._duration_time + event->_occur_time, i + 1});
    SortEventList();
   }
}

int Bank::FindShortestQueue()
{
  int result = 0;
  for(int i = 0; i < _queue_number; ++i) {
    if(_work_queue[result].size() > _work_queue[i].size())
      result = i;
  }
  return result;
}

void Bank::SortEventList()
{
  // 方法一,Lambda表達式:
  _event_list.sort(
      [](const Event &e1, const Event &e2) -> bool
      {return e1._occur_time < e2._occur_time;});

  // 方法二:
  // 你知道怎么寫一個函數來比較兩個event嗎?
  // 其實就是把Lambda表達式寫成一個函數,把
  // 這個函數作為sort的參數就可以了。

  // 方法三,使用 struct Event::operator< :
  _event_list.sort();

  // 注意:上面的方法一和方法二可以注釋掉任何一個,
  // 寫兩個,只是為了演示。
}

void Bank::Simulation()
{
  OpenForDay();
  Event event;
  while(!_event_list.empty()) {
    event = _event_list.front();
    _event_list.pop_front();
    if(event._type == 0) // 到達事件
      CustomerArrived(&event);
    else
      CustomerDeparture(&event);
  }
  // 計算並輸出平均逗留時間
  std::wcout << L"客戶數:" << _customer_number << std::endl
    << L"總逗留時間(小時):" << (double)_total_time / 3600.0 
    << std::endl
    << L"平均逗留時間(分鍾):" 
    << (double)_total_time / (double)(_customer_number * 60)
    << std::endl;
}

int wmain(int argc, wchar_t *argv[], wchar_t *env[])
{
  _wsetlocale(LC_ALL, L"");

  Bank bank;
  bank.Simulation();
  return 0;
}

stdafx.cpp

// stdafx.cpp : source file that includes just the standard includes
// bank.pch will be the pre-compiled header
// stdafx.obj will contain the pre-compiled type information

#include "stdafx.h"

// TODO: reference any additional headers you need in STDAFX.H
// and not in this file

頭文件:

bank.h

#ifndef __BANK_H__
#define __BANK_H__

#include <queue>
#include <list>

struct Event
{
	int _occur_time; // 事件發生的時刻
	int _type; // 事件類型,0表示到達事件,1到
	                 // 4 表示四個窗口的離開事件
  bool operator<(const Event &rhs)
  {
    return _occur_time < rhs._occur_time;
  }
};

struct QueueNode
{
	int _arrival_time; // 客戶到達時間
	int _duration_time;// 客戶需要的服務員時間
};

class Bank
{
	public:
		explicit Bank(int window_number = 4, 
				      int close_time = 8 * 3600);
		~Bank();
		void Simulation();
	private:
		int _queue_number;                 // 隊列個數
		int _close_time;                   // 關門時間
		int _total_time;                   // 累計客戶逗留時間
		int _customer_number;              // 客戶總數

		std::list<Event>      _event_list; // 事件鏈表
		std::queue<QueueNode> *_work_queue;// 工作隊列

		void OpenForDay();
		void CustomerArrived(Event *event);
		void CustomerDeparture(Event *event);
    int FindShortestQueue();
    void SortEventList();
};

#endif

stdafx.h

// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
//

#pragma once

#include "targetver.h"

#include <stdio.h>
#include <tchar.h>



// TODO: reference additional headers your program requires here

targetver.h

#pragma once

// Including SDKDDKVer.h defines the highest available Windows platform.

// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and
// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h.

#include <SDKDDKVer.h>

運行結果:

  

  

  

  

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM