趣味算法題——電梯調度問題

這是《編程之美》中的一道題,剛開始題目比較簡單,但是逐步推進之後的問題也有些難度,這樣由簡單到難的一步步深入的思想比較值得學習。

最初的問題

假如電梯在高峯期間只允許在某一層停留,所有的乘客在一樓上電梯,到達某層後,所有乘客從電梯下來,到達自己要去的樓層,在一樓的時候所有乘客選出自己要去的樓層,電梯根據所有乘客選擇的樓層信息得出要停留的樓層。
那麼,電梯停留在那一層,能夠保證這次乘坐電梯的所有乘客爬樓梯的層數之和最少。

問題的分析和解法

該問題本質上是一個優化問題,首先爲則個問題找到一個合適的抽象模型。從問題中可以看出,有兩個因素會影響到最後的結構:乘客的數目即需要停留的樓層,因此,我們可以從統計到達各層的乘客數目開始分析。
假設樓層總共有N層,電梯停留在X層,要去第i層的乘客總數目是Tot[i],這樣,所爬樓梯的總數就可以表示出老。
因此,我們就是要找到一個整數X使得這個總數的值最小。

解法

首先思考簡單解法,可以從第1層開始枚舉X一直到第N層,然後再計算出如果電梯在第X層的話,所有乘客總共需要爬多少層樓。這是最爲直接的一個解法。可以看出,這個算法需要兩重循環來完成計算

int n=0;//這個表示電梯所在的最高樓層
        int nPerson[] = new int[n+1];//這個數組表示要去每一層的乘客數
        //上面的兩個變量應該是給出的,這裏只是示意代碼沒有初始化。
        int nFloor,nMinFloor,nTargetFloor;//分別表示電梯停在第n層時候的結果,目前爲止的最小結果,最小結果所停的樓層。
        nTargetFloor = -1;//考慮到初始化最小結果和最小結果所砸的樓層,所以先設定爲一個非法值
        //循環遍歷每個樓層的結果,算出本層的結果,如果比之前結果小,就替換之前結果
        for(int i = 1; i <= n; i++){
            nFloor = 0;
            //計算本層以下的樓層的人的總消耗
            for(int j =1; j < i; j++){
                nFloor += nPerson[j]*(i-j);
            }
            //計算本層以上的樓層的人的總消耗
            for(int j = i+1; j <= n; j++){
                nFloor += nPerson[j]*(j - i);
            }
            //如果比之前結果小,或者還沒初始化,就用本層結果替換之前結果。
            if(nTargetFloor == -1 || nTargetFloor > nFloor){
                nMinFloor = nFloor;
                nTargetFloor = i ;
            }
        }

這個基本解法的時間複雜度爲O(N²)

解法的優化

我們希望儘可能地減少算法的時間複雜度。那麼,是否有可能在低於O(N²)
的規則下求出這個問題的解呢?
我們可以有如下的思路:
假設電梯停在第i層,顯然我們可以計算出所有乘客總共要爬樓梯的層數Y。如果有N1個乘客目的樓層在第i層樓一下,有N2個乘客在第i層樓,還有N3個乘客在第i層以上。這個時候,如果電梯改停在第i-1層,所有目的地在第i-1層一下的蹭課可以少爬一層,總共可以少爬N1層,所有目的地在i層及以上的乘客都需要多爬1層,總共需要多爬N2+N3層。因此,乘客總共需要爬的層數爲Y-N1+(N2+N3)=Y-(N1-N2-N3)層。
反之,如果電梯在i+1層停那麼乘客總共需要爬的層數爲Y+(N1+N2-N3)層。由此可見,當N1>N2+N3時,電梯在第i-1層停更好,乘客走的樓層數減少N1-N2-N3層;當N1+N2

int n=0;//這個表示電梯所在的最高樓層
        int nPerson[] = new int[n+1];//這個數組表示要去每一層的乘客數
        //上面的兩個變量應該是給出的,這裏只是示意代碼沒有初始化。
        int nMinFloor = 0,nTargetFloor = 1;//分別表示目前爲止的最小結果,最小結果所停的樓層。
        int N1 = 0 , N2 = nPerson[1] , N3 = 0;//分析中所說的N1,N2,N3創建和初始化
        //求出第二層時候的N3值並初始化
        for(int k = 2; k <= n;k++){
            N3 += nPerson[k];
            nMinFloor += nPerson[k] *(k-1);
        }
        //從第二層開始按照上述方法求出最優值
        for(int i = 2; i <= n ;i++){
            if( (N1 + N2) < N3){//如果i+1層更好,就替換最小結果,
                nTargetFloor = i ;
                nMinFloor += (N1 +N2 - N3);
                N1 += N2;
                N2 = nPerson[i];
                N3 -= nPerson[i];
            }else{//如果上面的樓層不會更好,就不用再查看了。
                break;
            }
        }

問題擴展

往上爬樓梯,總比往下走是要累的,假設往上爬一個樓層,需要消耗k個單位的能力,而往下走需要消耗一個單位的能量,那麼如果題目條件改爲讓所有人消耗的能量最少,這個問題怎麼解決呢?

分析

其實這個問題並沒有變難太多,只是在前面的思考的前提下放入一個新的考慮:上下樓消耗能量。這個需要在計算的時候上樓就要乘以一個參數K(因爲能量是下樓的k倍嘛),其他的考慮都還一樣。

int n=0,k = 1;//這個表示電梯所在的最高樓層和上樓梯需要消耗的能量
        int nPerson[] = new int[n+1];//這個數組表示要去每一層的乘客數
        //上面的變量應該是給出的,這裏只是示意代碼沒有初始化。
        int nMinFloor = 0,nTargetFloor = 1;//分別表示目前爲止的最小結果,最小結果所停的樓層。
        int N1 = 0 , N2 = nPerson[1] , N3 = 0;//分析中所說的N1,N2,N3創建和初始化
        //求出第二層時候的N3值並初始化
        for(int m = 2; m <= n;m++){
            N3 += nPerson[m];
            nMinFloor += nPerson[m] *(m-1);
        }
        //從第二層開始按照上述方法求出最優值
        for(int i = 2; i <= n ;i++){
            if( k*(N1 + N2) < N3){//如果i+1層更好,就替換最小結果,
                nTargetFloor = i ;
                nMinFloor += (k*(N1 +N2) - N3);
            }
            N1 += N2;
            N2 = nPerson[i];
            N3 -= nPerson[i];
        }

補充說明

其實目前爲止的邏輯過於不切實際,因爲實際中在高層電梯中不會出現只停一層的情況,而且如果有類似的政策的話,比較低的樓層的人會直接選擇爬樓去而不去按電梯(即使是統計好人數也會在計算總體的消耗上有很大的不同)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章