2019華爲軟件精英挑戰賽分享

目錄

一.寫在前面的話

二、語言選擇

三、判題器實現

四、回溯解死鎖

四、發車時間設計

五、尋路設計

六、寫在最後的話


一.寫在前面的話

       第一次參加軟件精英挑戰賽,複賽最後打了第六名,隊名是行車不規範親人兩行淚,我們上臺時候,主持人都說這隊名真應景啊…就差兩名就可以進決賽了,但是不得不說前面的大佬真的是太強了,把我們甩的賊老遠。不過也沒什麼遺憾,複賽練習賽時候也差不多是5-8名左右,所以正式比賽就想着要保住前十,別拿第五(感謝第五老鐵抗雷…),最好第六,也算是如願以償了。

代碼和數據在git上面:https://github.com/zhangbaodan/2019HUAWEI

      主要分享下解死鎖思路以及尋路那邊的一些特徵提取。如果您覺得有幫助的話,記得git上面點個小星星哇~

      再放張隊名圖片(美工小姐姐真的是太用心了)

                                                           

二、語言選擇

       和往年一樣,今年也開放了三種語言c++,java,python。最開始接觸這個題時候,感覺用不到機器學習的東西(我們太菜,想了半天沒想到,大佬估計用得到)。所以就把python給pass掉了,畢竟這玩意運行起來太慢了,後面也有很多隊吃了這個語言的虧。c++寫起來太複雜(主要是我c++寫的太菜,我的c++ = c + stl),所以把c++也pass掉了。而且感覺這個比賽car,road,cross剛好三個類,java寫起來會比較順手,而且很多封裝的類寫起來也比較方便,運行時間上面也和c++差距不是很大,所以最後選擇了java(西北複賽32個隊裏面,只有三個java,四個pyhon,看來c++大佬是真的多)。

三、判題器實現

       週五放的比賽,晚上時候經過討論,決定先把判題器擼出來,做出這個決定着實不容易,光規則就有19頁,能不能擼出來真的不好說,但還是決定賭一把(真的感謝當初這個決定)。那麼,對於計算機來說,每次我只能更新一輛車,哪以什麼樣的方式把所有車都更新完呢?那時候,官方還沒有更新任何有關判題器邏輯的東西,所以都只能自己想。我們決定按ID順序遍歷所有的車輛,對於某一個車輛,如果有車輛影響到它的更新,那麼就一直深度遍歷更新下去(官方採用按cross和road方式遍歷,事實證明他們的思路更清晰,實現起來也更容易),這是我們一開始寫的邏輯圖:                                                                                                                                

       最開始打算按照車輛去遍歷,如果遇到影響當前車輛更新的車輛,那麼就一直DFS更新下去,看上去漏洞百出,和官方給出的邏輯完全不一樣,但也就還是按着這玩意擼了。差不多擼了兩三天,一個看上去能用,實際完全用不了的判題器就橫空出世了。然後路徑規劃那塊搞了搞,算是能用了。過了一週以後,官方可以線上提交代碼了,第一天晚上提交時候各種死鎖。把發車時間打的很散以後終於跑通了,兩張圖跑了80000左右…

       實現這玩意着實不容易,尤其最開始官方啥都沒說的時候,每天就是一邊擼,一邊看官方的論壇回覆,看看有沒有自己沒有注意到的問題,而且是一邊看官方回覆,一邊懷疑官方回覆是不是錯的,因爲在有點地方,我們按照他的邏輯改,反而更不準了,最後索性都不看論壇了(事實證明,永遠不要懷疑官方...)。

       很遺憾的是,官方在初賽後期幾乎完全公佈了判題器邏輯時候,由於那時候我們判題器邏輯已經定型了(不太準),看了他們的邏輯,感覺差不多,就沒有完全按照他們的邏輯去改。這一下子偷懶,直接導致我們一個星期死在判題器上面。後面實在找不到BUG了,就痛定思痛,把直接寫的邏輯全刪,完全按照官方的邏輯去寫(連命名都和官方一模一樣),結果很神奇的就一致了。

         然而,我們一直偷了一個懶。官方之前一直強調cross的ID不保證連續,直到初賽比賽的早上,我才火急火燎的改成不連續讀取,但是判題器按照路口ID升序遍歷那塊忘記改了,導致判題器不準。再加上我們回溯方案很耗時間,初賽練習賽時候沒有表現出來,正式比賽時候,數據量一上去,我們立馬就超時了。只能換回一個星期以前的思路(沒有回溯),胡亂調參,還好苟進複賽了(24/32)。

        複賽時候,由於增加了優先級車輛以及預置車輛,所以判題器需要大改。官方索性放出了完整的邏輯導圖(這邏輯圖是真的清晰,什麼時候才能寫出這麼優雅的邏輯)。有了初賽踩坑經歷,這次我們決定一模一樣復現他的邏輯(名字都一樣)。差不多在複賽前一個星期,線上線下完全一致了(總時間後幾位不一致,這是因爲四捨五入的關係,所以沒太在意),提交上去一頁都是提交成功(綠色)的感覺是真的爽。

        總之,對於我們隊來說,復現別人的東西也是一個不小的難題。一定要堅信官方是對的(就像代碼給你報bug,絕對不能懷疑編輯器,一定要有對不起,都是我的錯,我一定改的態度一樣),堅持弄下去,一定能搞出來!

四、回溯解死鎖

       當發生死鎖的時候,我們可以獲得死鎖對應的車輛以及對應的路和路口。如果我們能夠事先對這些車輛以及路口做一定的管控的話,死鎖就不會發生了。       

        最簡單的方法是,當發生死鎖後,讓時光倒流,回到過去,然後做一定的調整,也就是回溯解鎖的辦法。首先,我們來說下如何回溯,不管採用何種回溯解鎖思路,前提條件是能回的去。比如在第100秒時候發生死鎖,如何回到90秒?一種很暴力的思想是重頭仿真到90秒,這種方案肯定用不了,太耗時了。我們在初賽時候,採用的方案是,對於每一秒,我們都記錄下該時刻對應的所有仿真信息。如果想回到90秒,只要讀取對應實現記錄的信息,做一個替換就好了。(這裏在工程上需要注意的是深拷貝和淺拷貝的問題,我們一定要做深拷貝,淺拷貝會導致地址共用,會出很多BUG)。

        然而...每一次記錄都需要話很大的時間(如果車輛數很多的話,代價更大),這也直接導致我們初賽代碼超時。複賽時候,我們採用每隔10秒記錄一次信息,這樣整個耗時相比於原來就小了10倍,而且這樣的操作對回溯並沒有產生很大的影響,發生死鎖後,我們只需要退回到最近的記錄的時間片就可以了。

       回溯後,一般採用的是回溯改路或者回溯不發車的思想,我們採用的是後者,比如在第100秒時候發生死鎖,記錄發生死鎖車輛以及發生死鎖路口(以及路口周圍的路口)。然後回到90秒(有一個類用於保存對應時間片的所有仿真信息,將90秒到100秒記爲forbidTime。期間有三個策略來儘量保證再次回到100秒時候,不會發生死鎖:

       1、100秒發生死鎖的車輛不允許在這期間發車(如果它原來實在這期間發車話);

       2、死鎖路口以及周圍路口不允許發車;

       3、全圖允許存在的車輛數按百分比減小,因爲我們有個很大的參數是控制當前地圖下最大允許存在的車輛數,把這個參數減小以後,也會降低死鎖可能性。

       當然,在這三種策略下也不能百分百保證第100秒時候不發生死鎖。我們的策略是如果不行,就再往會倒10秒,回到80秒,如果還不行就繼續往回倒...,當然,往回倒多少次取決於當前最大記錄了多少時間片信息。往回倒的時間片越多,在第100秒時候,整個車輛數就越小,發生死鎖可能性就越小。

四、發車時間設計

       有一個很重要的點是控制車輛的發車時間。我目前知道的兩種普遍的做法是:方案1.每隔幾秒發一波車;方案2.控制全圖車輛總數,如果當前地圖上車輛總數小於這個閾值,就立馬發一些車作爲補充。我們初賽採用的是方案1,複賽採用的是方案2。

       方案1:每隔幾秒鐘就發一波車,這種策略可以使得等到下一次發車時間到的時候,上一次發的車很大一部分就已經到達終點了。這樣的發車策略,在一定程度上預防了死鎖。這種策略下有如下若干問題:

       同一個發車批次下,應該發哪些車。有些做法是讓同一個發車批次下,車輛的始發地儘可能的分散。還有一種做法是,讓車輛的速度儘可能一致。在初賽時候,我們採用了後者,因爲同樣速度下,車與車之間的空隙會儘可能的小。這樣道路容納量會比較大。那麼,先發速度大的還是小的?我發現很多人的做法是先發速度大的,他們認爲速度大的會盡快到達終點,不會對後面速度小的車輛產生過大的影響。但是,不曉得爲啥,我們模型裏面先發速度小的結果會比較好。而且,我認爲先發大或者小結果應該是一樣的。

       每次應該發多少車?如果同一個發車批次下,車輛速度都儘可能一致的話,我認爲速度小的時候,可以多發一些車,速度大的時候,可以少發一些車。因爲速度越大,越容易死鎖。如果速度是16的話,假設其要跨越路口(當前在路的最前面),那麼下一條路的可以進入的車道中,如果從後往後數16個格子內存在一個等待車輛,那麼它就肯定是等待狀態了。而如果速度是4的話,只看4個格子。所以速度越大,越容易死鎖,地圖容納總數就越小。

       發車間隔是多少?發車間隔其實也控制這地圖車輛總數,因此,速度大的時候發車間隔大一點,速度小的時候,發車間隔小一點會比較好。

       方案2是我們複賽採用的方案,首先,按速度發車的思想沒有變。我們不去控制發車間隔以及發車量。而是直接去控制地圖上總車輛數,這樣兩個參數簡化爲一個參數,而且控制的效果更好。在不同速度下,發車的總量也是不一樣的(和方案1一樣)。速度越大,發車總量越小。

       在複賽中,對於優先級車輛,能發就發,不按照速度來發,我們會控制一個優先級車輛發車時候的總地圖容納量。對於普通車輛,會控制一個允許普通車輛加入的最早時間。在最早加入時間到了以後,按照速度有小到大依次發車,不同速度下設置不同的車輛總數。


五、尋路設計

       尋路無非有兩種尋路,一種是沒有仿真之前,就事先規劃好他的路,還有一種是,等到它真正要出發的時候,在去尋路。我們採用的是後者,比如在仿真到第100秒時候,有一輛車要出發,我們就把第100秒的地圖以及那輛車扔給尋路算法,返回一條路徑來。這樣會比事先規定路線要準確一些。

       尋路主要採用的是dijkstra算法。其核心是對於路的權重設定。我們在尋路這一塊做的比較爛,很大一部分精力都放在判題器和解鎖以及發車上面了,在我們很爛的尋路模型中一共有如下幾個參數:

        1.行駛完這條路所需要的時間

       這個參數應該是大家都能想到的一個參數,但在我們模型中,因爲是分速度調參的,在前面幾個速度中,這個參數的權重並不是很大,如果很大的話,車輛很容易立馬發生死鎖。當然,我們會在最後兩三個速度下,把這個權重調的大一點,因爲後面已經沒有多少車加入了,所以儘可能讓他早走完比較好。

       2.當前車道擁擠程度

        道路擁擠程度通過(道路總容納量 – 道路可加入量)/道路總容納量計算得到,這裏我們計算的不是道路已有車輛數,而是道路可加入量,這可能和其他隊伍的計算方式不太一樣。這個參數對應權重比較大,因爲我們希望車輛能夠均勻的分散開來。即使這樣做會繞比較遠的路,但我們認爲這樣也是可以接受的。

       3. 速度差異

        我們希望車和道路之間的速度儘可能接近,如果車速高於路速的話,對於車來說本身是個損失;如果路速高於車速的話,會導致該車後面的車有可能損失速度;如果車速等於路速的話,該路上車的速度都一直,速度損失將會降到最小。

       4. 已經選擇確尚未到達該路的普通車輛數

        這個對應權重設的比較小,也不曉得有沒有用~

       5. 已經選擇卻尚未到達該路的優先級車輛數

        這個權重就設的比較大了,因爲優先級車輛本身計算權重就比較大,我們儘可能讓非優先級車輛不去選擇優先級車輛已經選擇的路。這樣即使在最開始優先級車輛行駛中,我們加入一些非優先級車輛,也不會對優先級車輛運行的總時間產生過大的影響(如果設置的無窮大的話,優先級車輛總時間只可能降,不會升)。

        關於動態改路的問題,首先,做動態改路肯定是能夠降低總時間的,但是,我們尋路模型可能太爛了,改了反而不如不改…


六、寫在最後的話

        這次比賽稱爲華爲軟挑有史以來最變態的一次,代碼量要求真的是很大。通過這次比賽,也使得自己對代碼風格和代碼管理這一塊有了很大的提升。感覺我們失利的主要原因是在尋路那一塊做的很爛,尤其是在看了別人開源的代碼以後。總之,長路漫漫,來年再戰!

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