---
# System prepended metadata

title: 分治法
tags: [Algorithm]

---

分治法
===
###### tags: `Algorithm`

>分治法:分而治之
分: 切分問題
治: 解決、合併問題

很多時候大的問題很難解決，但是當規模較小的時候我們能直接知道答案。
那我們何不嘗先解決小的問題，在解決大的呢?
:::info
分治步驟:
我有一個問題 -> 如果小到我們知道答案直接回答 否則繼續問更小的問題 -> 想辦法用兩個小問題的答案湊出原本問題的答案。
:::
或許這樣講有點抽象，我們用範例來讓大家體會分治的強大。

## 例一. 區間最小值
>給你 n 個數字, 請輸出其中的最小值。

用單純的分治解最小值看起來或許很蠢，不過我認為他是夠簡單且能體會分治的問題。
試著套用上面剛剛學到的步驟，嘗試以分治解決。
1. 問題: `int query(int l, int r)`
我們希望這個函式能回傳`[l,r)`區間的最小值。

2. 什麼時候可以直接回答?
當 `r-l==1` 時, 只有一個元素, 最小值就是他自己

3. 當不能直接回答，問更小的問題
我們把詢問 `[l,r)` 切成兩半
變成 `[l,m)` 和 `[m,r)`，其中 `m = (l+r)/2`

4. 當我們有了小問題的答案，該如何回答原問題?
整個區間的最小值 = min( 左半最小, 右半最小)
因此 `return min( query(l,m), query(r,m) );`

:::warning
呼叫遞迴時有個要點，你要先假設你的下一層會好好地把事情做完。
不要擔心下一層怎麼運作，你只要確保:
1. 如果下一層是對的我能算對這一層
2. 遞迴直到某一層能直接停下來(直接取得答案)
 
類似數學歸納法的感覺
:::

### Code
寫成程式碼會長成:
```cpp
int arr[100];

int query (int l, int r)
{
    if (r-l == 1) return arr[l];
    else
    {
        int m = (l+r)/2;
        return min( query(l,m), query(m,r) );
    }
}
```
## 例二. 合併排序

>給你 n 個數字, 請你由小到大排序。

這個問題看起來有點難，至少我們不能很直覺的解決他。
我們再度嘗試以分治解決。

1. 問題: msort(l,r) 我們希望把 `[l,r)` 排序

2. 什麼時候可以直接回答?
當 `r-l == 1` ，也就是只有一個元素時，我們不用排序。

3. 當不能直接回答，問更小的問題
我們把它切成兩半
設 `m = (l+r)/2` ，然後 `msort(l,m); msort(m,r);`

4. 用兩個小問題的答案湊出原本問題的答案
這部分稍微難一點
當我們有
`左:(1,3,6,7)` 和 `右:(2,4,5,8)` 兩個序列
要如何推出 `答:(1,2,3,5,7,8,10)`?

我們從左右兩邊各自拿最小的出來比較 
1比較小先排入答案
因此有:
```
左:(3,6,7) 右(2,4,5,8) 答:(1, ...)
```
接下來繼續，這次最小的是右邊頭的 2, 那也把它丟進答案:
```
左:(3,6,7) 右(4,5,8) 答:(1,2, ...)
```
接下來可以試著自己推演。

示意圖:
![](https://i.imgur.com/rW3T4E6.png)

### Code
```cpp
int arr[N], n, tmp[N];

inline void merge (int l, int r) // 合併答案
{
    // tmp 拿來存合併時的答案
    // i 為左半邊頭, j 為右半邊頭, k 為放進答案的位置
	int m = (l+r)>>1;
	for (int i=l,j=m,k=l; k<r; k++)
	{
        // 右半邊空了 或 左半最小的比較小, 並且左半還有東西, 那就拿左邊。反之。
		if (i<m && (j>=r || arr[i]<arr[j])) tmp[k] = arr[i++];
		else tmp[k] = arr[j++];
	}
	for (int i=l; i<r; i++) arr[i] = tmp[i];
}
inline void merge_sort (int l, int r) // 遞迴
{
	if (r-l<=1) return;
	int m = (l+r)>>1;
	merge_sort(l,m);
	merge_sort(m,r);
    // 現在, 陣列的左半和右半都是分別排序好的。
	merge(l,r);
}
```