1. 程式人生 > >佇列應用之銀行排隊 離散事件模擬

佇列應用之銀行排隊 離散事件模擬

原始碼的github地址,可以下載到本地執行

package demo;

import impl.LinkedQueue;
import org.omg.CORBA.DynAnyPackage.Invalid;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 佇列的應用之離散事件模擬
 * 假設某銀行有4個視窗,每個視窗某一時刻只能接待一個客戶,因此客戶需要在每個視窗前順次排隊
 * 對於剛進入銀行的客戶 如果某個視窗的業務員正空閒,則可上前辦理業務
 * 若4個視窗均有客戶,他便會排在人最少的視窗隊尾
 * 要求:計算一天中 客戶在銀行逗留的平均時間
 * 平均時間等於=(每個客戶離開銀行的時間-每個客戶進入銀行的時間)之和 / 客戶的人數
 * 這裡,將客戶進入銀行 和離開銀行 這兩個時刻發生的事情稱為事件
 * 這一程式也稱為 事件驅動
 * <p>
 * 演算法:
 * 事件型別:這裡只有五種事件型別, 0-客戶到達 1-A視窗客戶離開 2-B視窗客戶離開 3-B視窗客戶離開 4-B視窗客戶離開
 * 到達的客戶事件,包括事件型別,到達時間
 * 離開的客戶事件,包括事件型別,離開時間
 * <p>
 * 處理客戶到達事件,新來的客戶,只會插入到四個視窗佇列中,最短的那個。同時 若銀行未關門 則生成一個新的客戶到達事件,該事件發生在前一個客戶到達事件之後
 * 如果插入的佇列為空 則再產生一個客戶離開事件
 * 佇列元素中包含,到達時間,辦事所需時間
 * <p>
 * <p>
 * 處理客戶離開事件,根據事件型別 從相應的視窗佇列頭,將事件出隊,統計逗留時間,如果佇列不為空,則再產生一個客戶離開事件
 * <p>
 * 所以 這裡使用兩個資料結構,事件佇列,視窗佇列
 */
public class Simulation {

    private static Long TotalTime; //總逗留時間
    private static int CustomerNum;//客戶總數
    private static Long CloseTime = 18 * 3600L;//銀行關門時間 下午6點時刻的秒數

    static class Event { //事件類 元素
        private int eventType;//事件型別
        private Long time;//事件發生的時間

    }

    static class QEvent { //排隊的佇列元素
        private Long ArrivalTime; //到達時間
        private int Duration;//辦事所需的時間
    }

    static class QueueWindow {
        private int windowNum;//視窗號
        private LinkedQueue<QEvent> WindowQueue;//該視窗的排隊佇列
    }


    private static LinkedQueue<Event> eventQueues = new LinkedQueue<Event>();//事件佇列
    private static LinkedQueue<QEvent>[] queues = new LinkedQueue[3]; //4個視窗的客戶佇列

    public static void OpenForDay() {
        //初始化客戶人數和逗留時間為0
        TotalTime = 0L;
        CustomerNum = 1;
        //生成第一個客戶到達事件
        Event event = new Event();
        long rad= (long)( Math.random() * 30 * 60L);
        event.time = 8 * 3600L +rad; //8點開門  30分鐘內隨機來一名客戶
        System.out.println("生成第一個客戶到達事件,該客戶為8點"+rad/60+"分到來");
        event.eventType = 0;
        eventQueues.InitQueue(); //初始化事件佇列
        eventQueues.EnQueue(event); //發生的事件進入事件佇列內
        //初始化視窗佇列
        for (int i = 0; i < queues.length; i++) {
            LinkedQueue<QEvent> qEventLinkedQueue = new LinkedQueue<QEvent>();
            qEventLinkedQueue.InitQueue();
            queues[i] = qEventLinkedQueue;
        }

    }


    public static char getEventType() {
        if (eventQueues.GetHead().eventType == 0) {
            return 'A';
        } else {
            return 'D';
        }

    }


    /**
     * 找出四個正在排隊的佇列中 最短的佇列
     *
     * @return
     */
    private static QueueWindow getShortestQueue() {
        int max = 0;
        int maxTag = 0;
        for (int i = 0; i < queues.length; i++) {
            if (queues[i].QueueLength() >= max) {
                max = queues[i].QueueLength();
                maxTag = i;
            }
        }
        QueueWindow queueWindow = new QueueWindow();
        queueWindow.windowNum = maxTag;
        queueWindow.WindowQueue = queues[maxTag];
        return queueWindow;
    }

    /**
     * 處理客戶到達事件
     * <p>
     * 處理客戶到達事件,新來的客戶,只會插入到四個視窗佇列中,最短的那個。
     * 同時 若銀行未關門 則生成一個新的客戶到達事件,該事件發生在前一個客戶到達事件之後
     * 如果插入的佇列為空 則再產生一個客戶離開事件
     * 佇列元素中包含,到達時間,辦事所需時間
     */
    public static void CustomerArrived() {
        //原先事件佇列裡面的事件出隊
        Event event = eventQueues.DeQueue();

        //生成排隊事件
        QEvent qEvent = new QEvent();
        qEvent.ArrivalTime = event.time;
        qEvent.Duration = 5 * 60 + (int) (Math.random() * 20 * 60); //辦事所需時間 是5~20分鐘以內

        //找出最短的視窗佇列
        QueueWindow queueWindow = getShortestQueue();
        //判斷這個佇列是否為空,是的話再產生一個客戶離開事件
        if (queueWindow.WindowQueue.isEmpty()) {
            Event leaveEvent = new Event();
            leaveEvent.time = qEvent.ArrivalTime + qEvent.Duration; //客戶的離開時間,等於其到達的時間+辦事的時間
            leaveEvent.eventType = queueWindow.windowNum;
            eventQueues.EnQueue(leaveEvent);
        }
        //視窗事件入隊
        queueWindow.WindowQueue.EnQueue(qEvent);

        //產生新的客戶到達事件
        Event newEvent = new Event();
        newEvent.eventType = 0;
        newEvent.time = event.time + 10 * 60L + (long) (Math.random() * 20 * 60L); //前一個客戶來到之後  10~30分鐘內隨機來一名客戶
        if (newEvent.time >= CloseTime) {
            return; //終止產生新的客戶到達事件
        }
        eventQueues.EnQueue(newEvent); //新的到達事件入隊
        CustomerNum++;//客戶數+1
    }

    /**
     * *處理客戶離開事件,根據事件型別 從相應的視窗佇列頭,將事件出隊,統計逗留時間
     * 如果佇列不為空,則再產生一個客戶離開事件
     *
     * @param
     */
    private static void CustomerDeparture() {
        Event event = eventQueues.DeQueue();
        QEvent qEvent = queues[event.eventType].DeQueue();//對應視窗佇列的排隊事件出隊
        TotalTime = TotalTime + (event.time - qEvent.ArrivalTime);//逗留時間  等於離開事件發生時間減去到達時間

        //如果該視窗不為空 則繼續產生下一個客戶離開事件
        if (!queues[event.eventType].isEmpty()) {
            Event leaveEvent = new Event();
            leaveEvent.eventType = event.eventType;
            leaveEvent.time = event.time + queues[event.eventType].GetHead().Duration;// 新的客戶離開時間 等於上一個客戶離開的時間+該客戶自己的辦事時間
            eventQueues.EnQueue(leaveEvent);
        }
    }


    public static void main(String[] args) {
        OpenForDay();//初始化 開始一天
        while (!eventQueues.isEmpty()) {   //事件列表不為空時候
            switch (getEventType()) { //事件型別
                case 'A':
                    CustomerArrived();
                    break; //處理客戶到達事件
                case 'D':
                    CustomerDeparture();
                    break;//處理客戶離開事件
                default:
                    System.out.println("事件型別錯誤");
                    ;
            }


        }


        System.out.println("今天總共:" + CustomerNum + "個客戶,總逗留時間為" + TotalTime + "秒" + ",等於" + TotalTime / 60 + "分");
        System.out.println("每個人平均逗留時間為:" + TotalTime / CustomerNum + "秒, 也就是" + (TotalTime / CustomerNum) / 60 + "分");
    }
}

輸出結果
生成第一個客戶到達事件,該客戶為8點20分到來
今天總共:29個客戶,總逗留時間為25744秒,等於429分
每個人平均逗留時間為:887秒, 也就是14分
原始碼的github地址,可以下載到本地執行