zoukankan      html  css  js  c++  java
  • 《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
    ";
            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
    ";    //平均每个人的等待时间
            }
            else
                cout<<"No customers!
    ";
            cout<<"Done!
    ";
    
            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);
    }
  • 相关阅读:
    .NetTiers不支持UDT的解决方式
    CreateRemoteThread的问题
    使用.NetTiers的事务
    how do i using c# to obtain call stack on crash?
    使用C#为进程创建DUMP文件
    GTD软件
    c#调用c++的dll
    使用PowerDesigner生成数据库
    笨鸟学iOS开发(2)ApplicationSettings
    让IIS支持中文名
  • 原文地址:https://www.cnblogs.com/banmei-brandy/p/11454323.html
Copyright © 2011-2022 走看看