日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

烙饼问题与搜索树

發布時間:2025/3/21 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 烙饼问题与搜索树 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

轉自:http://blog.csdn.net/kabini/article/details/2276723

早在一年前,當時我的一個很牛的胖師兄受邀參加Google中國的面試,一開始問他考什么問題他就用簽了保密協議打發我們。但當最后他得知無緣Google的時候,終于打開話匣子,跟我們這些小字輩滔滔不絕地傳授了一些“面經”。我記得其中就有一道題就是這個一摞烙餅問題,還有一道概率題在我面試MSRA的時候也被問到,可恨我當時沒在意,后來面試吃了虧。不過如此的巧合說明微軟和Google面試題庫相同?抑或是兩個互為競爭對手的公司選擇的標準驚人的一致?只可惜Google今年沒來哈爾濱招聘,我沒法證實答案到底是A還是B。但如果是后者,我相信明年參加校園招聘的朋友還是有必要把這些經典的問題搞清楚。只可惜我當時對微軟的面試不甚了解導致準備不足,而《編程之美》又出得晚了半年,否則說不定....。算了,還是關注問題本身吧。

? ?? 言歸正傳,一摞烙餅問題其實是一個很有意思的問題,它的描述是讓一摞隨機順序的烙餅通過單手翻轉的方式進行排序,以達到這摞烙餅由小到大順序放置在盤子上的目的,其特點是每次翻轉都會導致第一個烙餅到所要反轉的那個烙餅之間的順序變為逆序。我們的目的是求出次數最少的翻轉方案以及翻轉次數,很顯然這是一個最優化問題,我們本能想到的是動態規劃、貪心以及分支限界三種方法。
??
??? 書中給出的遞歸算法或稱之為分支限界法(即遍歷+剪枝=分支限界)秉承了遞歸算法傳統的簡單、明了,但效率偏低的特點。這個問題的實質,我們在每一次反轉之前其實是需要做出一種選擇,這種選擇必須能夠導致全局最優解。遞歸算法就是遞歸的構建所有解(實際是一顆搜索樹),并在遍歷過程中不斷刷新LowerBound和UpperBound,以及當前的最優解(剪枝),并最終找到一個最整體優解。在這種策略下,提高算法的效率只能寄希望于剪枝方法的改進。但是這種方法顯然不是多項式時間的,有沒有多項式時間的算法呢?

??? 書中P22頁提到動態規劃,但最后卻給出了解決最優化問題普遍適用但效率可能是最差的遞歸方法。這不禁讓人疑惑:這也不美啊!?如果我們能證明該問題滿足動態規劃或貪心算法的使用條件,解決問題的時間復雜度將會降到多項式時間甚至N^2。但書中提到動態規劃卻最終沒有使用,又沒有講明原因,我覺得是一種疏失(應該不算錯誤)。那我們就來想一下為什么沒有動態規劃或貪心算法的原因。

? ? 我們知道動態規劃方法是一種自底向上的獲取問題最優解的方法,它采用子問題的最優解來構造全局最優解。利用動態規劃求解的問題需要滿足兩個條件:即(1)最優子結構 (2)子結構具有重疊性。條件(1)使我們可以利用子問題的最優解來構造全局最優解,而條件(2)是我們在計算過程中可以利用子結構的重疊性來減少運算次數。此外,《算法導論》上還以有向圖的無權最短路徑和無權最長路徑為例提出條件(3)子問題必須獨立。
??
? 首先我們假定烙餅問題存在優化子結構。假如我們有N個烙餅,把他們以其半徑由小到大進行編號。優化子結構告訴我們對于i個烙餅,我們只需要先排列前(i-1)個,然后再將第i個歸位;或先排列第2到i個,最后將第一個歸位;又或是找到一個位置k[i<=k<j]像矩陣乘法加括號那樣,使得我們先排列前k個,再排列后j-k個,最后再將二者合并,以找到一個最佳翻轉策略等等...
??
??? 根據動態規劃算法的計算過程,我們需要一個N*N矩陣M,其中M[i][j]表示將編號i至編號j的烙餅排序所需要的翻轉次數。但我們真的能從M[0][0..j-1]和M[1][j+1],或與M[i][j]同行同列的值來計算M[i][j]嗎?如果能,我們就能獲得多項式時間的算法。??


??? 我們來看書中給出的例子:(頂端)3,2,1,6,5,4,9,8,7,0(底端),我們最終的目標是計算M[0][9]。
這里我們以計算M[0][4]為例,計算的矩陣我已經在下面給出:??
? 0 1? 2? 3? 4? 5? 6? 7? 8? 9
? ------------------------
0|0 1 (1){1}[?]
1|? 0? 1 (1){1}??
2|???? 0? 1 (1)?
3|??????? 0? 0
4|?????????? 0
? ------------------?
??
? 實際上如果我們要向將0-4號烙餅(注意:烙餅編號也等同于其半徑)排為正序(中間有其他烙餅也沒關系),按照程序給出的結果, 我們需要進行3次翻轉,分別為[2,5,9](即分別翻轉隊列中第二(從零開始)、五、九個烙餅,這里的數字不是烙餅的編號):??
? [1]? [2]? [3]? 6?? 5? [4]? 9? 8? 7? [0]
? [4]?? 5??? 6? [3] [2] [1]? 9? 8? 7? [0]
? [0]?? 7??? 8?? 9? [1] [2] [3] 6? 5? [4]
??
????? 我們知道,該矩陣中每一個數的背后都隱含著一個烙餅的排列,例如M[0][4]就應該對應0,7,8,9,1,2,3,6,5,4
? 所以,每一個M[i][j]的選取都蘊含著其子排列的順序的變化。
????? 在計算M[i][j]的時候,我們需要計算i-j號餅的全部劃分(不包括全部為1的劃分)所能構成的翻轉結構,并取其翻轉 次數最少的哪一個最為M[i][j]的最終值。例如,我們在計算M[0][4]的時候,需要查看:
??
?? /**先將0和1-4號分別排序,最后將二者合并為有序所需要的翻轉次數*/
?? M[0][0],M[1][4]?
???
?? /** 同上 */
?? M[0][1],M[2][4]
???
?? /** 同上 */
?? M[0][2],M[3][4]
???
?? /** 同上 */
?? M[0][3],M[4][4]
???
?? /* 先將0、1、2、3-4號分別排序,最后將4者合并為有序所需要的翻轉次數.
??? * 注意這里又包含將4個分組再次進行劃分的問題!?
??? */
?? M[0][0],M[1][1],M[2][2],M[3][4]
?? .....//中間略
?? M[0][3],M[4][4]
??
?? 如果再加上運算過程中我們可以淘汰超過最大反轉次數的方案(剪枝?),我們完成全部的運算,所經歷的運算過程的時間復雜度已經不是多項式時間的,而是和先前所說的遞歸方法已沒什么兩樣。

???? 造成這種現象的原因是:某個子問題的最優解不一定是整體的最優解,所以我們在處理整個問題的時候,需要遍歷所有可能的子問題,并計算它到整體問題所消耗的代價,才能最終作出有利于整體問題的選擇。

?? 所以我們一開始的假設,即烙餅問題有優化子結構的假設是錯誤的。因此我們不能用動態規劃,同理也不能用貪心算法。


前面已經寫了一些關于烙餅問題的簡單分析,但因為那天太累有些意猶未盡,今天再充實一些內容那這個問題研究透。我想,通過這篇文章,我們就可以把這一類問題搞懂。再遇到優化問題,如果我們想不到別的辦法,就可以采用搜索樹算法來解決,至少我們不至于拿不出解決方案。前面我們已經知道,關于一摞烙餅的排序問題我們可以采用遞歸的方式來完成。其間我們要做的是盡量調整UpperBoundLowerBound,已減少運算次數。對于這種方法,在算法課中我們應該稱之為:Tree Searching Strategy。即整個解空間為一棵搜索樹,我們按照一定的策略遍歷解空間,并尋找最優解。一旦找到比當前最優解更好的解,就用它替換當前最優解,并用它來進行“剪枝”操作來加速求解過程。

書中給出的解法就是采用深度優先的方式來遍歷這棵搜索樹,例如要排序[4,2,1,3],最大反轉次數不應該超過(4-1)*2=6次,所以搜索樹的深度也不應大于6,搜索樹如下圖所示:

這里只列到第三層,其中被畫斜線的方塊由于和上層的某一節點的狀態重復而無需再擴展下去(即便擴展也不可能比有相同狀態的上層節點的代價少)。我們可以看到在右子樹中的一個分支,只需要用3次反轉即可完成,我們的目標是如何更為快速有效的找到這一分支。直觀上我們可以看到:基本的搜索方法要先從左子樹開始,所以要找到本例最佳的方案的代價是很高的(利用書中的算法需要查找292次)。

既然要遍歷搜索樹,就有廣度優先和深度優先之分,可以分別用棧和隊列來實現(當然也可以用遞歸的方法)。那么如何能更有效地解決問題呢?我們主要考慮一下幾種方法:

(1)???????爬山法

該方法是在深度優先的搜索過程中使用貪心方法確定搜索方向,它實際上是一種深度優先搜索策略。爬山法采用啟發式側讀來排序節點的擴展順序,其關鍵點就在于測度函數f(n)的定義。我們來看一下如何為上例定制代價函數f(n),以快速找到右子樹中最好的那個分支(很像貪心算法,呵呵)。

我們看到在[1,2,4,3]中,[1,2,3]已經相對有序,而[4]位與他們之間,要想另整體有序,需要4次反轉;而[3,1,2,4]中,由于[4]已經就位,剩下的數變成了長度為3的子隊列,而子隊列中[1,2]有序,令其全體有序只需要2次反轉。

所以我們的代價函數應該如下定義:

1?從當前狀態的最后一個餅開始搜索,如果該餅在其應該在的位置(中間斷開不算),則跳過;

2?自后向前的搜索過程中,如果碰到兩個數不相鄰的情況,就+1

這樣我們就可以在本例中迅速找到最優分枝。因為在樹的第一層

f(2,4,1,3)=3f(1,2,4,3)=2f(3,1,2,4)=1,所以我們選擇[3,1,2,4]那一枝,而在[3,1,2,4]的下一層:

f(1,3,2,4)=2f(2,1,3,4)=1f(4,2,1,3)=2,所以我們又找到了最佳的路徑。

上面方法看似不錯,但是數字比較多的時候呢?我們來看書中給出的10個數的例子:

[3,2,1,6,5,4,9,8,7,0]程序給出的最佳翻轉序列為{?4,8,6,8,4,9}(從0開始算起)

那么,對于搜索樹的第一層,按照上面的算法我計算的結果如下:

f(2,3,1,6,5,4,9,8,7,0)=4

???????f(1,2,3,6,5,4,9,8,7,0)=3

???????f(6,1,2,3,5,4,9,8,7,0)=4

???????f(5,6,1,2,3,4,9,8,7,0)=3

???????f(4,5,6,1,2,3,9,8,7,0)=3

???????f(9,4,5,6,1,2,3,8,7,0)=4

???????f(8,9,4,5,6,1,2,3,7,0)=4

f(7,8,9,4,5,6,1,2,3,0)=3

f(0,7,8,9,4,5,6,1,2,3)=3

我們看到有4個分支的結果和最佳結果相同,也就是說,我們目前的代價函數還不夠“一擊致命”,但是這已經比書中的結果要好一些,起碼我們能更快地找到最佳方案,這使得我們在此后的剪枝過程更加高效。

爬山法的偽代碼如下:

1?構造由根組成的單元素棧S

2 IF Top(s)是目標節點?THEN?停止;

3 Pop(s);

4 S的子節點按照啟發式測度,由小到大的順序壓入S

5 IF?棧空?Then?失敗

Else?返回2

?

??????????????如果有時間我會把爬山法解決的烙餅問題貼在后面。

(2)???????Best-First搜索策略

最佳優先搜索策略結合了深度優先和廣度優先二者的優點,它采取的策略是根據評價函數,在目前產生的所有節點中選擇具有最小代價值的節點進行擴展。該策略具有全局優化的觀念,而爬山法則只具有局部優化的能力。具體用小根堆來實現搜索樹就可以了,這里不再贅述。

(3)???????A*算法

如果我們把下棋比喻成解決問題,則爬山法和Best-First算法就是兩個只能“看”未來一步棋的玩家。而A*算法則至少能夠“看”到未來的兩步棋。

我們知道,搜索樹的每一個節點的代價f*(n)=g(n)+h*(n)。其中,g(n)為從根節點到節點n的代價,這個值我們是可求的;h*(n)則是從n節點到目標節點的代價,這個值我們是無法實際算出的,只能進行估計。我們可以用下一層節點代價的最小者來替代h*(n),這也就是“看”了兩步棋。可以證明,如果A*算法找到了一個解,那它一定是優化解。A*算法的描述如下:

1.??使用BestFirst搜索樹

2.??按照上面所述對下層點n進行計算獲得f*(n)的估計值f(n),并取其最小者進行擴展。

3.??若找到目標節點,則算法停止,返回優化解

總結:歸根到底,烙餅問題之所以難于在多項式時間內解決的關鍵就在于我們無法為搜索樹中的每一條邊設定一個合理的權值。在這里,每條邊的權值都是1,因為從上一個狀態節點到下一個狀態節點之需要一次翻轉。所以我們不能簡單地把每個節點的代價定義為翻轉次數,而應該根據其距離最終解的接近程度來給出一個數值,而這恰恰就是該問題的難點。但是無論上面哪一種方法,都需要我們確定搜索樹各個邊的代價是多少,然后才能進行要么廣度優先、要么深度優先、要么A*算法的估計代價。所以,在給出一個合理的代價之前,我們所有的努力都只能是幫忙“加速”,而無法真正在多項式時間內解決問題。

《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀

總結

以上是生活随笔為你收集整理的烙饼问题与搜索树的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。