電梯優化奇技淫巧
多線並行,多策略競爭
此處 特意感謝 lsj 霸霸提出的這一神奇思路,我只是把這個想法變為現實。
很慚愧,只做了一點一點微小的工作(端起茶杯)
一、提出背景
已知的各種算法都有各自的局限性
-
ALS 在處理大多數情況的時候效果明顯差於 Look ,但是ALS,在面對
1-FROM-1-TO-3
2-FROM--2-TO-3
3-FROM-10-TO-15
這種情況的時候,反而會因為它 先來先選 的沙雕策略比 Look 折返路程更少。
-
貪心算法在面對精心構造的數據的時候可能會被來回 放風箏 這是 墜痛苦的 。
防止互測被卡TMax
指導書最開始並沒有禁止互測通過針對對方算法構造一個極具針對性的數據點。
個人看法是,針對一個多數情況下較優的算法,通過一個很極端的例子,去卡爆對方性能,獲取hack分數
是有點缺德的。同時這個所謂的Bug還難以修復。
開發這個多策略的 初心 是防止自己的程序出現在極端情況下慢於純ALS的情況。
前三次的經驗積累
第二次和第三次作業當中,我的一些優化算法,可能在某些情況下出現負優化,於是我需要根據判斷
優化前后的字符串長度取舍本次的優化。 於是lsj julao提出能不能在此處根據時間取舍優化,並初步提出了
多電梯並行的操作。
二、實現
大體思路
多策略可以理解為一種在 算法選擇 層面的貪心,整體思路如下
- 開多個電梯線程,這些電梯線程不同於第三次作業的合作關系,他們不僅算法不同,且他們之間的運行是完全獨立的。
- 主線程獲取新的請求,把這個請求給每個電梯線程各分配一次。
- 各個線程把自己的輸出 重定向 到一個緩沖區中,這里我用的是官方輸出接口自帶的一個指定PrintStream的重載,把輸出重定向到一個ByteArrayStream 當中。
- 設置設置一個輸出類,這個輸出類有一個靜態鎖,用於保證全局輸出的線程安全。最先執行完所有請求的線程獲得這個靜態鎖,把自己緩沖區內的數據全部輸出出來,並通過一些標記之類的東西阻止其他線程也進行輸出。 之后直接exit(0)用最暴力的方式結束程序。
部分源碼
主線程
public class ElevatorSystem {
public static void main(String[] args) {
GlobalPermission permission = new GlobalPermission();
Rail rail0 = new Rail(16,3);
Rail rail1 = new Rail(16,3);
Rail rail2 = new Rail(16,3);
Dispatcher dispatcher0 = new Dispatcher(rail0,permission);
Dispatcher dispatcher1 = new Dispatcher(rail1,permission);
Dispatcher dispatcher2 = new Dispatcher(rail2,permission);
dispatcher0.addSillyElevator();
dispatcher1.addLookElevator();
dispatcher2.addAlsElevator();
ArrayList<Elevator> elevators0 = dispatcher0.getElevators();
ArrayList<Elevator> elevators1 = dispatcher1.getElevators();
ArrayList<Elevator> elevators2 = dispatcher2.getElevators();
TimableOutput.initStartTimestamp();
permission.systemStart();
Thread elevator0 = new Thread(elevators0.get(0));
Thread elevator1 = new Thread(elevators1.get(0));
Thread elevator2 = new Thread(elevators2.get(0));
elevator0.start();
elevator1.start();
elevator2.start();
ElevatorInput elevatorInput = new ElevatorInput(System.in);
while (true) {
PersonRequest request = elevatorInput.nextPersonRequest();
if (request == null) {
permission.systemQuit();
try {
elevatorInput.close();
} catch (Exception e) {
e.printStackTrace();
}
break;
}
try {
// 每拿到一個新的請求,把這個請求給每個電梯各分配一次
dispatcher0.getBuffer().put(request);
dispatcher1.getBuffer().put(request);
dispatcher2.getBuffer().put(request);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
輸出輔助類
我把緩沖區以及最后的帶鎖輸出方法封裝到了一個OutputHelper類中,每個電梯都自帶一個OutputHelper對象
public class OutputHelper {
private static boolean hasOuput = false;
private ByteArrayOutputStream storeStream = new ByteArrayOutputStream();
private PrintStream stream = new PrintStream(storeStream);
public static synchronized void output(OutputHelper helper) {
if (!hasOuput) {
helper.finalOutput();
hasOuput = true;
}
}
public void println(String string) {
TimableOutput.println(string,stream);
}
public void finalOutput() {
Scanner scanner = new Scanner(storeStream.toString());
while (scanner.hasNextLine()) {
System.out.println(scanner.nextLine());
}
}
}
電梯運行線程
public void run() {
dispatcher.updateRequests();
while (this.hasMoreWork()) {
dispatcher.updateRequests();
ElevatorOrder order = getOrderAndUpdateBuffer();
dispatcher.updateRequests();
executeOrder(order);
}
// 任務執行完畢,開始輸出
OutputHelper.output(helper); // 輸出!
System.err.println(scheduler); // 輸出“優勝者”的信息,便於統計算法性能
System.exit(0); // 暴力關程序
}
強測運行效果
根據本人利用err流輸出的統計信息,在強測數據中,本人的高分數據點(95+)的最優策略包含了本人使用的三種算法,可見這種多策略對性能的提升是很大的。
當然,在這里我必須得膜那些用單策略但是性能分高於我的dalao,三個算法比不過對方一個算法,哭哭。
方法兩個優勢
- 較為簡單地大幅度提高性能
- 可以規避掉自己某一算法中出現瘋狂懟門、電梯摸魚的Bug。 因為這種情況,電梯一定無法完成任務,那么他們一定無法輸出,那么正確執行的電梯就會掩蓋這一錯誤。
三、更多的拓展(事后孔明)
動態地改變所采用的算法
此方法出自shh,但是他摸了,沒去實現,哭哭。
這里大概講一下shh的思路。
我們之前所說的多策略都是每個線程給定一個算法,然后取最優。
如果遇到一下這種情況,這個方法的效果可能會大打折扣
- 請求具有明顯的階段性
- 不同階段的請求的特征明顯適合不同的算法
那么這樣子看來,我們的多策略還是不夠
於是,shh提出了根據每固定執行步數給各個算法打個分,然后調整算法的操作。
(反正我是不會實現的,哭哭)
加入評測機檢驗自己的輸出
前文我們提到,多策略在其中一個線程因為bug而陷入長時間無法運行的狀態時,一般不會輸出錯誤。
因為那個bug線程一直摸魚,無法輸出,就會使得正確執行的線程可以輸出。
那么,這就帶來另一個問題 如果一個線程因為Bug,執行錯誤地線程最先結束,那么輸出將會是錯誤的。
這里,我們就可以把自己評測機的評測模塊加進來,對我們自己的輸出結果進行檢驗。
如果是正確的輸出,則正常輸出並結束程序,如果不是正確數據則等待下一個可以輸出的線程,直到等到第一個正確的輸出。
此外如果擔心自己電梯出現異常
的問題,可以try-catch一下,如果有異常就失去輸出的權力。
這樣,就可以大幅度降低自己程序出錯的概率~
用於統計各個算法的執行效果
在第三次作業中,我需要統計為三部電梯選擇合適的控制算法。
於是我通過根據三部電梯的任務特征生成針對性數據,通過運行多策略電梯統計各個策略的效果。
最后根據統計結果為三部電梯的調度策略做出調整。