算法設計與分析之分治法解決漢諾塔問題


前言:該篇博客是我《算法設計與分析》課程的期末筆記,將會出一個系列,後續章節請見主頁。
其他:如果可以的話,請給我關注收藏點贊三連,嘻嘻謝謝,如果不可以的話請告訴我“下次一定”。
參考資料:[1]王曉東.算法設計與分析(第三版)[M].北京:清華大學出版社,2014
————————————————————————

2 遞歸與分治策略

2.1 算法介紹

分治法(Divide and conquer):將一個規模較大問題分解爲規模較小的子問題,先求解這些子問題,然後將各子問題的解合併得到原問題的解的算法思路。

遞歸(Recursion):直接或間接地調用自身的算法。

  • 優點:
    • 是一種自然的思考方式
    • 思路清晰
    • 易於實現
  • 缺點:
    • 具體執行步驟難以理解
    • 壞的遞歸大幅提高算法複雜度

分治與遞歸的聯繫:分治法的子問題通常與原問題結構和求解方法相同,可以通過遞歸的方法求解。

2.2 漢諾塔(Hanoi tower)

2.2.1 問題描述

有3根柱子及n個不同大小的圓盤,最初,所有盤子由上到下、從小到大地套在第一根柱子上。移動圓盤時受到以下限制:
(1) 每次只能移動一個盤子;
(2) 盤子只能從柱子頂端滑出移到下一根柱子;
(3) 盤子只能疊在比它大的盤子上。
請問:將所有盤子從第一根柱子移到最後一根柱子,如何移動才能移動次數最少?需要移動多少次?

2.2.2 問題解決

令三個柱子分別爲A、B、C,最初所有的盤子都在A上,從宏觀上看,要想把A的盤子全部移動到C,則可以:

  • ① 將n-1個盤子從A移到B
  • ② 將最下面的1個盤子從A移到C
  • ③ 將n-1個盤子從B移動到C

你可能會問,問題要求(1)中明明限制了一次只能移動一個盤子!
這個時候就要用到分治和遞歸的思想,上述三個步驟中,僅有步驟②是直接執行,其中步驟①和步驟③都要通過遞歸實現。
我們先說步驟①,如何把n-1個盤子從A移到B?這可以看成一個新的子問題,而這個子問題跟原問題是一樣的,所以我們可以利用遞歸,再次重複上述的三個步驟就行了,但是,要如何設置參數呢?首先,這個子問題中只有n-1個盤子,所以我們只需要把n改爲n-1,其次,要想把n-1個盤子從A移到B,就得先把n-2個盤子從A移到C,依次類推往下…
請你注意:在不斷遞歸的過程中,A、B、C的“移動角色"在不斷改變,原問題是把n個盤子從A移到C,子問題爲把n-1個盤子從A移到B,子子問題是把n-2個盤子從A移到C…
步驟③的原理同步驟②。

建議這段講解,配合代碼一起閱讀,將有助於理解。

2.2.3 時間複雜度

時間複雜度:O(2n)
分析:
在這裏插入圖片描述
我們首先以5個圓盤爲例,來分析時間複雜度。
這張圖的含義:灰色部分爲直接執行的步驟②,黃色部分爲遞歸執行的步驟①和③。圓圈內的數字代表圓盤的個數,圓圈旁的數字代表所需執行的步驟數,左側的式子代表對應一層的執行步驟數。
這張圖的分析:我們從左側的式子中可以看出,n=5時,就把每一層的執行步驟數加起來,就是總的執行步驟數,爲3+7x20+7x21+7x22+6x22
推廣至n時,則爲3+(20+21+…+2n-3)+6x22=2.5x2n-4,故O(2n)

2.2.3 代碼實現

public class HanoiSimple{
	public static void move(Tower A,Tower B,Tower C){
		move(A.size(),A,B,C);
	}
	public static void move(int n,Tower A,Tower B,Tower C){
		if(n==1){
			C.add(A.remove());
		}
		else{
		    /*關鍵代碼:實現遞歸與分治*/
			move(n-1,A,C,B);//將n-1個盤子從A移到B(繼續遞歸)
			move(1,A,B,C);//將最下面的那個盤子從A移到C(真正執行)
			move(n-1,B,A,C);//將n-1個盤子從B移動到C(繼續遞歸)
		}
	}
}

Tip:這裏的代碼單獨跑是跑不開的,僅供學習參考,還需要測試代碼及Tower類的定義。

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