算法+數據結構(第02篇)玩掃雷就是優化算法

算法+數據結構(第02篇)玩掃雷就是優化算法

引言

上篇文章介紹了算法的本質和基本概念《算法+數據結構(第01篇)走下神壇吧!算法》,這次我們用實際的問題來做算法實戰。

假設如下場景:

公司新年晚會進行奪寶遊戲,獎品是最新款的智能手機、VR遊戲機、便攜電腦三件套。

遊戲規則如下:

當主持人宣佈遊戲開始的時候,每位員工的手機上會同時收到兩組數字(數組中的每個數字都是正整數且兩兩不等)和一個目標正整數。

員工需要在兩組數字中分別取兩個數字相加,使得相加的結果與目標正整數最接近。哪位員工先做出結果,那麼獎品就歸誰。

為了使贏率最高,請問應該採用什麼樣的策略或者方法?

顯然,這是在對一個特定問題找方法。那麼根據上篇文章所講到的,這就是在求算法。

那麼如何算法求解呢?

答案就在上篇文章提到的“樸素而廣泛的方法論”中。這個方法論其實就是算法求解的套路。

套路第一步:重新定義問題,結構化描述


原問題是生活場景,要轉換成結構化問題描述。結構化描述分為如下兩步:數據與規則抽取、數據結構選擇與轉化。

數據與規則抽取

數據的來源: 數據一般在原問題描述中以名詞、量詞形式出現

數據的摘取:並不是所有的名詞和量詞都是有效數據。很明顯,只有和問題求解相關的名詞和量詞才有意義。“問題求解”是動作,與動詞相關。

那麼是不是所有的動詞都有效呢?也不是。只有和規則相關的動詞才是有效的。

規則的發掘:規則就是抵達結果的條件。

根據上面的定義, 不難看出

數據是:兩組數字(數組中的每個數字都是正整數且兩兩不等)、一個目標整數

規則是:從兩組數字中分別取兩個數字相加,相加的結果必須與目標正整數最接近


算法+數據結構(第02篇)玩掃雷就是優化算法


數據結構選擇與轉化


上篇文章已經講到了:算法的依託是數據結構。如果把算法看做設計域的話,那麼數據結構就是連接問題域到設計域的橋樑。那麼如何選取合適的數據結構呢?

答案是:對上一步摘取的數據進行類型聯想、關聯。


算法+數據結構(第02篇)玩掃雷就是優化算法


上一步中,我們已經摘取了數據——兩組數和一個正整數。很明顯,這裡涉及到兩個類型:數組和整數。

而這兩個屬於基礎數據結構類型,至此數據結構選擇問題解決了。接下來就是要對摘取的數據,基於選擇的數據結構進行轉化——“重整化”:

兩組數字(數組中的每個數字都是正整數且兩兩不等)=> int A[]; int B[];

目標正整數 => int c;

聰明的你,一定會問一個問題:數據結構的選擇僅僅就在這一步決定嗎?

答案是否定的。數據結構的選擇會貫穿整個算法設計,是一個不斷迭代的過程。後面部分會詳細闡述。

套路第二步:問題歸類


算法問題的基本類型:搜索、排序、規劃、計算。

回到當前問題,根據問題描述,顯然屬於搜索類型。

套路第三步:經驗匹配


現在我們來翻看已有的搜索算法,看看有沒有能與當前問題匹配的。

理論上有3種情況:

第1種情況,100%匹配,此時“直接拿來主義”;

第2種情況,部分匹配,此時可在已有算法基礎上進行調整、組合或者改良;

第3種情況,完全不匹配,此時需要我們根據已有知識(甚至是跨學科知識,比方說數學、生物等),創新性地開發新算法。

針對搜索問題,我們有一個萬能算法——“暴力搜索”,即遍歷每一種可能性,直到找到答案。

但是這個算法要窮盡所有可能性,所以帶來的時間和空間開銷通常都是巨大的,用上篇文章的術語來講,就是計算複雜度賊高。

為了給大家一個量化感覺,先用“暴力搜索”算法來解答這個題。

暴力搜索算法

對於數組A中的每一個元素進行遍歷:

設當前元素為A[i],則:

遍歷數組b中的每一個元素B[j]:

(i)計算A[i]+B[j]的值,將所求的值記為t;

(ii) 計算t-c的絕對值|t-c|,記為k;

(iii) 如果當前的k比歷史的k小(k的初值可以設成一個極大值)。

那麼: 將 {A[i], B[j]}取代之前的候選結果,作為新的候選結果,待所有的遍歷結束,最終的候選結果就是所要求的解。

上面的算法有兩重循環,所以暴力搜索時間複雜度為O(La x Lb)。

其中La表示數組a中元素的個數,Lb表示數組b中元素的個數。

隨著La和Lb的增大,複雜度以兩者乘積速度上升。那麼如何對暴力算法進行優化呢?

關於複雜度的計算,我會在下篇文章中詳細介紹。


套路第四步:算法優化三步走


步驟1:

找到算法性能瓶頸源頭,稍微分析一下,就明白:上述暴力搜索算法的開銷在於窮盡了所有元素。

步驟2:

對源頭進行改造,那麼是否可以避免窮盡所有元素而得到結果呢?換言之,是否可以只比較部分元素、其他元素就自然被排除了呢?

要得到這樣的效果,顯然我們需要一種性質——這種性質必須是容易獲得的:要麼可以直接從當前數據中獲取,要麼可以通過已有方法(算法)獲取。

最容易想到的就是有序性,這種性質可以通過排序算法獲取。我們可以用快速排序算法對A數組和B組數進行排序,將排序後的元素按照下圖放置:

(為了方便表示,我們假設A數組是10個元素,B數組是12個元素)


算法+數據結構(第02篇)玩掃雷就是優化算法


上圖中的每個方格就是用來存放相加結果的。很顯然,暴力搜索就是對上圖中的每個方格都做了計算。

現在我們要做的,就是利用有序性,避開儘可能多的方格。

我們從右上角方格[A10, B1]開始遍歷:

記s[A10, B1] = A10 + B1,則:

(i) 如果s[A10, B1] == 目標正整數c,那麼元素對{A10, B1}即為所求解

(ii) 如果s[A10, B1] < 目標正整數c, 那麼所有與[A10,B1]在同一排的方格都不用計算了

原因如下:因為A1<=A2<=...<=A9<=A10,所以s[A1, B1] <= s[A2, B1] <= ... <= s[A10, B1],從而這些s距離c都比s[10, B1]遠,都不是所求解。

(iii) 類似地,如果s[A10, B1] > 目標正整數c,那麼所有與A[10, B1]在同一列的方格都不用計算了,顯然,按照對角線方向來遍歷,每遍歷一個方格,就可以避開一排或者一列的方格,感覺就像在玩掃雷遊戲:)


算法+數據結構(第02篇)玩掃雷就是優化算法


算法+數據結構(第02篇)玩掃雷就是優化算法


算法+數據結構(第02篇)玩掃雷就是優化算法


算法+數據結構(第02篇)玩掃雷就是優化算法


算法+數據結構(第02篇)玩掃雷就是優化算法


步驟3:驗證

現在我們來驗證一下優化後的算法的複雜度,整個算法分成兩部分:

第1部分是快速排序。快速排序算法的時間複雜度是O(nlogn),所以這部分的時間複雜度是O(MAX(LalogLa, LblogLb))

第2部分是掃雷遍歷。這部分最壞的情況就是走完整個對角線。此時共遍歷La+Lb個方格,時間複雜度是O(La+Lb)

兩者相加得到最壞情況下的整體時間複雜度為:O(MAX(LalogLa, LblogLb)+La+Lb)

好啦,就寫到這裡了,後續連載會持續更新...

END

博主還有很優秀的技術交流群,很多技術大拿,CTO,活躍度百分八十以上。問題解答百分之90以上。加博主好友後回覆【加群】,然後回答技術問題,答對者才能進入,其他博主和廣告商勿擾進群介紹,當然也會有一些學習資源,群裡直接回復資源介紹即可。


分享到:


相關文章: