量杯問題相關優化方式

 

208 37 賴昭勳

 

機器人專題課 程式作業

簡介

量杯問題

有\(n\)個杯子,每個杯子有容量\(c_i\),現在有三種基本操作:

  1. 將一杯子內的水裝滿
  2. 將一杯子內的水倒至另一杯(中間不能有水消失)
  3. 將一杯子內的水倒光

 

現在欲在最少的基本操作數內,使其中一個杯子內的水容量是\(x\),輸出倒水的方法。

實作方式

量杯問題的圖論觀點

將每一種狀態(各水杯的容量) 視為一個節點,節點 \((u, v)\)間若可以在一個操作內達成,則連一條有向邊\(u \rightarrow v\)。

 

問題則可以變成,所有含有容量\(x\)的狀態為「終點」,一開始所有杯子都是空的狀態為起點,找出一條起點到任一終點的最短路徑。

廣度優先搜尋(BFS)

簡單來說流程(pseudocode)就是:

 

目前的距離 \(dis = 0\)

找過的點 \(vis = [起點]\)

while (沒找到終點)

    for \(v\) in vis:

        將\(v\)所有連到且沒找過的點\(u\)加入 vis

        如果找到終點就停止執行

    dis += 1

 

 

為什麼是對的?

顯然,\(x\)次執行後所有離起點\(x\)的點都會被看到。

可以用數學歸納法證明。

因此最短路徑如果是\(y\),他就必然在\(y\)步時被找到。

如何還原答案?

對每個節點紀錄 \(p[i]\),代表轉移到該節點的節點(類似樹狀圖的父親概念)。

最後從終點一路沿著\(p[i]\)往起點回朔就能得到解答的路徑(反轉)。

程式碼

搜尋重複狀態時的優化:

Hash 哈希/雜湊

為什麼要避免重複放進同一個狀態?

假設有 \(k\)個東西指向同一個節點,他就會被放入\(k\)次,而他所連到的所有東西也會被多放\(k\)倍,重複疊加產生指數複雜度。

1

1

2

3

3

8

16

8

原本的作法

	for(i=1;i<=qe;i++)
	{
		flag = 0;
		// for(j=1;j<=n;j++) if(c[j]!=que[i][j]) flag = 1;
		for(j=1;j<=n;j++) 
			if(c[j]!=que[i][j]) 
			{   // 有不同的量杯水量,不是相同的組合(break後可繼續比對下一個queue值)
				flag = 1;
				break;
			}
		if(flag==0) return 0;  // 完全相同的量杯水量組合(不必再放到queue中了)
	}
	return 1; // queue中無此c[1..5]組合紀錄(必須push到queue中)

每次檢查所有以前的狀態,查詢複雜度\(O(狀態數) = O(n)\)

From: 彭天健老師

高中資訊課程

想辦法優化!

想法一:

開一個「所有可能狀態」的布林陣列,並用某種方式將量杯的狀態用一個整數紀錄,就可以馬上查這個狀態有沒有用過。

 

插入/查詢時間複雜度: \(O(1)/O(1)\)

空間複雜度:???

缺點

如果每個杯子的容量分別是\(c_1, c_2, ..., c_n\),

則基本上有\(\Pi_{i=1}^n c_i\)那麼多種狀態,更何況我們無法事先知道每個杯子的水量,這樣會浪費很多記憶體空間。

解決方式: 雜湊的概念

雜湊可以被想像成一個函數 \(f(x) \rightarrow y\)。其中\(x\)的定義域大小沒有限制,但\(y\)的值域大小固定且有限。簡單來說就是把原本的資料經過某種方式壓縮在一個固定的數值範圍間。

ex. Jiwfoehid -> Jiw

EWIIED -> EWI

JiwHEFID -> Jiw

雜湊的問題

因為輸入的種類數很多,輸出種類數卻很少,一定會存在兩個以上不同的輸入得到一樣的雜湊值。

 

解決方法:

插入數值時:放在雜湊值後面第一個是空的位置。

查詢數值是否存在時:持續查詢直到遇到雜湊值後面第一個空位。

時間複雜度?

空間可以自訂(不能低於可能出現的狀態數)

時間: 如果雜湊函數是好的話,期望在查詢和插入都是\(O(1)\)

好的雜湊函數

  • 雜湊值完全取決於輸入
  • 雜湊函數用到所有輸入的資料
  • 雜湊函數「平均地」將輸入資料分攤在所有可能數值
  • 相近的輸入值通常會出現差很多的雜湊值

我使用了"Rolling Hash" 也就是將各杯子的水量視為一個多項式的係數f(x),並求f(k) mod m 的結果。

程式實作與執行

謝謝大家的聆聽

量杯問題與優化

By justinlai2003

量杯問題與優化

  • 899