《C++primerplus》第12章“队列模拟”程序

 这个程序刚开始学有很多难点,个人认为主要有以下三项:

1.链表的概念

2.如何表示顾客随机到达的过程

3.程序执行时两类之间的关系,即执行逻辑

关于第一点,书上的图解释得比较清楚了,把“空指针”示意为接地很形象。为了理解链表的概念,需要自己把指针的指向变动慢慢推演一遍。大体来说,就是要理清头部的指针、尾部的指针和中间新增的结点的指针,三者是怎么联系在一起的;每当新增一个结点,各个指针应该如何变化;怎么删除节点等。

在程序中,三者是这么个流程。

首先,一个Queue类对象初始化时,其内部的头部和尾部指针都是空指针。(想象成指向大地)

所谓“节点”是个结构体,内部有两个变量,一个是Customer类对象,名字叫“Item”,另一个是个指针,叫“next”。上面的front和rear,以及这个next,都是指向这种结构体的指针。

然后,当一个新节点出现时,可以这么表示:

成员函数内部使用new关键字分配了一个指向这种结构体的指针,名字叫“add”,那么与此同时一个新的Node结构体也出现了。其内部的Item可以用函数的参数去初始化,next指针设为空。

接着,将add指针存的地址赋给front。

 那么front就会指向这个新增的节点,如果它是第一个的话,rear也要指向它,于是演变为如下状态:

 

 同样,再增加一个新节点时,应该更改指针指向,使其演变为如下状态:

add是每次new出来的指针,是用于入队的成员函数enqueue()内部的临时变量,所以会不断变化。

再次新增节点时的状况都是类似的,每一个Node结构体就代表了排队的顾客。

有顾客要出队时,定义一个临时Node结构体指针temp,把front存的地址给它,item也用front指向的队首节点初始化。

那么temp就会指向队首节点。然后使front指向下一位节点,原本的队首节点就移出来了。

接着删除temp指针,就模拟了出队的情况。

当队列空无一人时,front和rear会再次置为空指针。

关于第二点,需要学习rand()函数的使用。定义一个时间种子之后,rand()可以生成 [0,RAND_MAX)之间的随机数,然后后面加一定运算就可以自定义范围,这一点我在程序的注释中作了详细解释。

关于第三点,我画了一张模拟过程的流程图:

 

下面是程序所有代码的详细注释。

Queue类和Customer类的声明:

//Queue.h -- Declaration of class Queue and Customer
#ifndef _QUEUE_H_
#define _QUEUE_H_

#include <cstdlib>    //for rand() and srand()

class Customer
{
private:
    long arrive;
    int processtime;

public:
    Customer(){arrive = processtime = 0;}
    void set(long when);
    long when() const {return arrive;}
    int ptime() const {return processtime;}
};

typedef Customer Item;    //为了方便,为Customer类一个别名Item

class Queue
{
private:
    enum{Q_SIZE = 10};
    struct Node
    {
        Item item;
        struct Node * next;
    };
    Node * front;
    Node * rear;
    int items;
    const int qsize;
    Queue(const Queue & q):qsize(0){}    //防止类外面用复制构造函数做初始化操作
    Queue & operator = (const Queue & q){return *this;}    //因为都是私有函数不可直接调用,所以编译时就会报错

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);
};

#endif // _QUEUE_H_

对应方法的实现:

//Queue.cpp -- Methods of class Queue and Customer
#include "Queue.h"

Queue::Queue(int qs) : qsize(qs)    //创建对象时就用qs初始化qsize
{
    front = rear = NULL;    //队首队尾的指针都设为空
    items = 0;
}

bool Queue::isempty() const
{
    return items == 0;
}

bool Queue::isfull() const
{
    return items == qsize;
}

int Queue::queuecount()const
{
    return items;
}

bool Queue::enqueue(const Item & item)
{
    if(isfull())    //判断队列是否已满
        return false;
    Node * add = new Node;    //新增一个节点(指针)
    add->item = item;    //节点的初始化
    add->next = NULL;    //节点的下位指针设为空,为后面新增节点准备
    items++;    //队列人数+1
    if(front == NULL)    //判断队列是否为空
        front = add;    //是,就把add指针存的地址赋给front指针,front和add一样指向新增的节点
    else
        rear ->next = add;    //否,就把add指针存的地址,赋给rear指向的节点里的下位指针,即原本队列最后一个节点里的指针指向了新增的节点
    rear = add;    //rear和add一样指向新增的节点
    return true;
}

bool Queue::dequeue(Item & item)
{
    if(front == NULL)    //判断队列是否为空
        return false;
    item = front ->item;
    items--;
    Node * temp = front;    //临时指针,用来存储原本front存储的地址(也就是即将出队的节点的地址)
    front = front->next;    //原本的front指针指向即将出队的节点的下一个节点
    delete temp;    //删除临时指针,原本分配给该节点的内存不再被使用,即该节点被删除
    if(items == 0)
        rear = NULL;    //如果该节点删除后队列就空了,那么rear谁也不指向
    return true;
}

Queue::~Queue()
{
    Node * temp;
    while(front != NULL)
    {
        temp = front;
        front = front->next;
        delete temp;
    }
}

void Customer::set(long when)
{
    processtime = std::rand()%3 + 1;    //服务时间为[1,3)中一个随机值(分钟)
    arrive = when;    //记录其到达的时间
}

主程序。加了个大循环来不断测试。

//Bank.cpp -- Using Class

#include "Queue.h"
#include <iostream>
#include <ctime>    //for time()

const int MIN_PER_HR = 60;

bool newcustomer(double x);

int main()
{
    using std::cin;
    using std::cout;
    using std::endl;
    using std::ios_base;

    std::srand(std::time(0));    //生成随机数时间种子

    int flag = 1;    //用于保持循环
    while(flag)
    {
        cout<<"Case Study: Bank of Heather Automatic Teller\n";
        cout<<"Enter maximum size of queue: ";
        int qs;
        cin>>qs;    //指定排队的最大人数,不指定默认为10
        Queue line(qs);    //初始化Queue类对象line

        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_HR / perhour;    //换算平均下来每多少分钟到达一位顾客

        Item temp;    //一个临时顾客对象,用于代表每个循坏周期服务的顾客
        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);    //为这位顾客生成随机的服务时间(1-3分钟),并记录其到达的时间
                    line.enqueue(temp);    //顾客入队,更新内部所有指针
                }
            }

        /* wait_time是每位顾客服务时间的计数器,可以这么想象:*/
        /* 每有一位顾客到达了队首,就开始掐表倒计时(1-3分钟随机)*/
        /* 时间一到0,表示服务完毕,下一个人补上,重新倒计时,如此重复 */
            if(wait_time <=0 && !line.isempty())    //上一位服务完毕且队伍里还有人
            {
                line.dequeue(temp);    //下一位出队,开始服务
                wait_time = temp.ptime();    //置计数器为该位顾客的服务时间
                line_wait += cycle - temp.when();    //用现在的时间减去该顾客的到达时间,所有结果累加(即总等待时间)
                served ++;    //已服务的人数+1
            }
            if(wait_time>0)
                wait_time--;    //上一位服务未完毕,保持当前状态,时间-1
            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!\n";
        cout<<"Done!\n";

        cout<<"Enter 1 to simulate again,0 to quit: ";
        cin>>flag;    //输入0以终止循环
    }

    return 0;
}

/* 判断顾客是否到达的函数 */
/* RAND_MAX是能够生成的最大随机数,rand()会生成[0,RAND_MAX)之间的随机数 */
/* 因此rand()/RAND_MAX会生成[0,1)之间的随机数,再乘以x就是[0,x)之间的随机数 */
/* 加上小于1的判断,生成的数会有1/x的概率小于1,而小于1就表示这一分钟内有顾客到了 */
bool newcustomer(double x)
{
    return (std::rand() * x/RAND_MAX < 1);
}

猜你喜欢

转载自www.cnblogs.com/banmei-brandy/p/11454323.html