算法:銀行取款排隊模擬

某銀行有4個柜臺,假設某天有若干為客戶來辦理業(yè)務,每個客戶到達銀行的時間和取款需要的時間分布分別用兩個數(shù)組arrive_time(已經(jīng)按到達時間排序)和process_time來描述。
請寫程序計算所有客戶的平均等待時間,假設每個客戶取取款之前先拿到號排對,然后在任意一個柜臺有空閑的時候,號碼數(shù)最小的客戶上去辦理,假設所有的客戶拿到號碼之后不會失去耐心走掉。
測試用例:

NSArray<NSNumber *> *arrive_time = @[@1.0, @2.0, @3.0, @4.0, @4.0, @8.0];
NSArray<NSNumber *> *process_time = @[@50.0, @20.0, @11.0, @25.0, @30.0, @40.0];
// 計算過程:24.0 / 6   所有用戶需要等待的總時間/用戶總數(shù)
輸出結(jié)果:4.0

分析:首先不要被銀行取款這個場景迷惑,這個算法跟實際生活中場景有很大的不同,首先,arrive_time是已經(jīng)確定的,另外所有人占用僅有的4個柜臺時間也是已經(jīng)確定好的,不會有增加和減少的行為。

那么實際這個算法就是:按照arrive_time的序號依次開始,當一個顧客到達后,就需要判斷是否有空閑的柜臺,這里就需要一個數(shù)組來存放已經(jīng)使用的柜臺,數(shù)組的數(shù)組就用來存放當前剩余多久能夠完成正在進行的客戶取款。

NSMutableArray<NSNumber *> *servers = [NSMutableArray array];

然后還需要一個變量存儲所有顧客需要等待的累加的時間:

CGFloat allWaitTime = 0;

那么算法的最基本結(jié)構(gòu)就出來了:

// 顧客到達時間數(shù)組
NSArray<NSNumber *> *arrive_time = @[@1.0, @2.0, @3.0, @4.0, @4.0, @8.0];
// 對應到達時間每一個顧客需要占用柜臺的時間數(shù)組
NSArray<NSNumber *> *process_time = @[@50.0, @20.0, @11.0, @25.0, @30.0, @40.0];
// 被占用的柜臺數(shù)組
NSMutableArray<NSNumber *> *servers = [NSMutableArray array];
// 客戶需要等待的總時間
CGFloat allWaitTime = 0;

// 遍歷每一個顧客,計算需要等待的總時間
for (int i = 0; i < arrive_time.count; i++) {

}

每當一個顧客進來我們就需要先判斷是否有正在服務的柜臺,如果servers的數(shù)量小于4,那么就可以直接進行服務,不需要等到,而這個柜臺需要被占用的時間剛好就是這個顧客對應的process_time中的時間,所以直接在servers添加一個元素,值為process_time對隊應index的值,用于記錄一個柜臺被占用,并且需要剩余多少時間才會空閑。

for (int i = 0; i < arrive_time.count; i++) {
    // 當前存在空閑的柜臺,顧客直接進行取款
    if (servers.count < 4) {
        // 這個柜臺被占用,并且需要這個顧客需要的取款時間后才會空閑
        [servers addObject:process_time[i]];
        NSLog(@"第 %d 個 顧客不需要等待直接服務", i + 1);
    } else {  // 沒有空閑的柜臺,計算需要等待多久
         
    }
}

如果沒有空閑的柜臺我們,我們就需要計算需要等待時間最短柜臺還需要多少時間空閑。既然需要計算時間,我們首先需要一個時間線,來記算時間的流逝,不然的話是無法計算剩余等待時間等這些的。那么如何計算呢,這里其實已經(jīng)有了,就是每一個顧客的到達時間,第一個顧客到達時間為 1,第二個為 2,以第一個顧客到達時間為起始時間,那么第二個顧客達到是,時間增加了 1 個單位,當?shù)?3 個顧客到達時,時間又過去了 1 個單位。
每當一個顧客來的時候,如果存在正在占用的柜臺,我們就需要將所有被占用柜臺的剩余服務時間進行處理,減去這個顧客到達時和上一個顧客達到的時間差,這樣所有的服務中柜臺的剩余等待時間就會成功的更新,并且如果剩余等待時間小于等于這名顧客和上一名顧客到達時間的差值,就證明這個柜臺已經(jīng)完成服務,還需要將它從servers數(shù)組中移除:

for (int i = 0; i < arrive_time.count; i++) {
    // 保存剩余時間最小的柜臺的時間
    CGFloat minLast = MAXFLOAT;
    // 保存剩余時間最小的柜臺的index
    NSInteger minIndex = 0;
    // 當存在服務中柜臺是,需要對柜臺剩余時間進行刷新
    if (servers.count > 0) {
        // 保存已經(jīng)服務完成的柜臺,用于后面將他們從servers中移除
        NSMutableArray *removeArray = [NSMutableArray array];
        // 遍歷所有服務中的柜臺,即servers中的每一個元素
        for (int j = 0; j < servers.count; j++) {
            // 上一個顧客到達的時間
            NSInteger preArriveTime = i > 0 ? arrive_time[i - 1].floatValue : 0;
            // 當前顧客到達的時間就是他距離上一個顧客到達時間的差值
            // 這個差值就是時間線的標準
            CGFloat pastTime = arrive_time[i].floatValue - preArriveTime;
            // 當前柜臺剩余的時間就是上一個顧客來的時候,剩余的服務時間減去當前顧客距離上一個到達時間的差值
            CGFloat lastTime = servers[j].floatValue - pastTime;
            // 更新當前柜臺的剩余等待時間
            servers[j] = [NSNumber numberWithFloat:lastTime];
            // 計算剩余等待時間最小的柜臺
            if (lastTime < minLast) {
                minLast = lastTime;
                minIndex = j;
            }
            // 如果剩余時間小于0,它在之后會被從servers中移除
            if (lastTime < 0) [removeArray addObject:servers[j]];
        }
        // 將已經(jīng)空閑的柜臺從servers中移除
        [servers removeObjectsInArray:removeArray];
    }
    // 當前存在空閑的柜臺,顧客直接進行取款
    if (servers.count < 4) {
        // 這個柜臺被占用,并且需要這個顧客需要的取款時間后才會空閑
        [servers addObject:process_time[i]];
        NSLog(@"第 %d 個 顧客不需要等待直接服務", i + 1);
    } else {  // 沒有空閑的柜臺,計算需要等待多久
         
    }
}

更新完所有服務中柜臺的剩余時間后,再去計算如果柜臺都在等待中,那么顧客需要等待的時間:

NSLog(@"第 %d 個 顧客需要等待 %f s", i + 1, servers[minIndex].floatValue);
allWaitTime += servers[minIndex].floatValue;
servers[minIndex] = [NSNumber numberWithFloat:(servers[minIndex].floatValue + process_time[i].floatValue)];

完整算法代碼:

void checkTime(NSArray<NSNumber *> *arrive_time, NSArray<NSNumber *> *process_time) {
    // 被占用的柜臺數(shù)組
    NSMutableArray<NSNumber *> *servers = [NSMutableArray array];
    // 客戶需要等待的總時間
    CGFloat allWaitTime = 0;
    
    // 遍歷每一個顧客,計算需要等待的總時間
    for (int i = 0; i < arrive_time.count; i++) {
        // 保存剩余時間最小的柜臺的時間
        CGFloat minLast = MAXFLOAT;
        // 保存剩余時間最小的柜臺的index
        NSInteger minIndex = 0;
        // 當存在服務中柜臺是,需要對柜臺剩余時間進行刷新
        if (servers.count > 0) {
            // 保存已經(jīng)服務完成的柜臺,用于后面將他們從servers中移除
            NSMutableArray *removeArray = [NSMutableArray array];
            // 遍歷所有服務中的柜臺,即servers中的每一個元素
            for (int j = 0; j < servers.count; j++) {
                // 上一個顧客到達的時間
                NSInteger preArriveTime = i > 0 ? arrive_time[i - 1].floatValue : 0;
                // 當前顧客到達的時間就是他距離上一個顧客到達時間的差值
                // 這個差值就是時間線的標準
                CGFloat pastTime = arrive_time[i].floatValue - preArriveTime;
                // 當前柜臺剩余的時間就是上一個顧客來的時候,剩余的服務時間減去當前顧客距離上一個到達時間的差值
                CGFloat lastTime = servers[j].floatValue - pastTime;
                // 更新當前柜臺的剩余等待時間
                servers[j] = [NSNumber numberWithFloat:lastTime];
                // 計算剩余等待時間最小的柜臺
                if (lastTime < minLast) {
                    minLast = lastTime;
                    minIndex = j;
                }
                // 如果剩余時間小于0,它在之后會被從servers中移除
                if (lastTime < 0) [removeArray addObject:servers[j]];
            }
            // 將已經(jīng)空閑的柜臺從servers中移除
            [servers removeObjectsInArray:removeArray];
        }
        // 當前存在空閑的柜臺,顧客直接進行取款
        if (servers.count < 4) {
            // 這個柜臺被占用,并且需要這個顧客需要的取款時間后才會空閑
            [servers addObject:process_time[i]];
//            NSLog(@"第 %d 個 顧客不需要等待直接服務", i + 1);
        } else { // 沒有空閑的柜臺,計算需要等待多久
//            NSLog(@"第 %d 個 顧客需要等待 %f s", i + 1, servers[minIndex].floatValue);
            allWaitTime += servers[minIndex].floatValue;
            servers[minIndex] = [NSNumber numberWithFloat:(servers[minIndex].floatValue + process_time[i].floatValue)];
        }
    }
    CGFloat res = allWaitTime / arrive_time.count;
    NSLog(@"計算完成 \n總等待時間 %.1f \n顧客數(shù)%zd \n平均等待時間: %.1f\n", allWaitTime, arrive_time.count, res);
}

打印測試輸出:

NSArray<NSNumber *> *arrive_time = @[@1.0, @2.0, @3.0, @4.0, @4.0, @8.0];
NSArray<NSNumber *> *process_time = @[@50.0, @20.0, @11.0, @25.0, @30.0, @40.0];
checkTime(arrive_time, process_time);

總等待時間 24.0 
顧客數(shù)6 
平均等待時間: 4.0
耗時: 0.284076 ms

復雜度分析:
最外層需要遍歷所有顧客為n,內(nèi)部更新servers需要最多4次,移除servers最多4次,假定動態(tài)數(shù)組增加和刪除都是O(1),那么總共需要n * (4 + 4),復雜度為O(n)。

測試1000條數(shù)據(jù)需要的運行時間:

NSMutableArray<NSNumber *> *arrive_time = [NSMutableArray array];
for (int i = 0; i < 10000; i++) {
    [arrive_time addObject:[NSNumber numberWithFloat:i]];
}

NSMutableArray<NSNumber *> *process_time = [NSMutableArray array];
for (int i = 0; i < 10000; i++) {
    [process_time addObject:[NSNumber numberWithFloat:i % 10 + 10]];
}


CFAbsoluteTime startTime =CFAbsoluteTimeGetCurrent();
checkTime(arrive_time, process_time);
CFAbsoluteTime linkTime = (CFAbsoluteTimeGetCurrent() - startTime);
NSLog(@"\n耗時: %f ms", linkTime *1000.0);

總等待時間 131176253.0 
顧客數(shù)10000 
平均等待時間: 13117.6
耗時: 6.420970 ms

總結(jié)

這個題主要是考察思維的,能不能從看似沒有頭緒的問題中,找到如何選擇時間的參照系這個關鍵,至于之后的時間的計算就是很簡單的了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。