• 沒有找到結果。

for Sprout 2014 by Yuan

N/A
N/A
Protected

Academic year: 2022

Share "for Sprout 2014 by Yuan"

Copied!
71
0
0

全文

(1)

Algorithm Design Methods

Divide & Conquer

for Sprout 2014 by Yuan

(2)

從一個例子開始…

從一個例子開始…

• 占三格的 L 形方塊

• 是否可以不重疊的放入 8x8 ,且缺了一格的棋盤中呢?

我們來試試看…

很「貪心」的盡量 把每個方格塞滿

糟糕了…好像行不通

怎麼辦?難道要回 去窮舉每一種可能 嗎 ?

(3)

別緊張 !!

別緊張 !!

• 遇到看似不可解的問題怎麼辦?

• 大問題不會解,小問題總會解了吧?

• 不妨從較小規模的合理問題開始思考 !

• 圖一是否可解呢?看起來易如反掌

• 放大一倍試試看!

• 圖二是否可解呢?看起來沒那麼難……

• 要怎麼從小問題的解法獲得一些線索呢 ?

圖一

圖二

(4)

切割! 切割!

• 這樣一來,就很像解四次圖一的 問題了!

• 可是有個小小的插曲:其中的三 格並沒有缺格

• 我們正好可以放上一塊方塊完美 的解決這個問題!

• 產生四個規模較小的子問題 ~

(5)

遞迴過程 遞迴過程

X 3

X4 X4

問題夠小,我們會解了

邊長 =2 為終止條件

(6)

什麼是「分治」?

什麼是「分治」?

• 切割問題,然後征服問題!

• 把問題切割成相似的子問題

• 利用相似的方法解決它

• 剛剛的例子,由「分治法」構造出一組正確的方案

• 遞迴是方便實做分治想法的好工具

• 遞迴的本質是用堆疊保存每一層函數之區域變數的狀態

(7)

且慢,貪心不好嗎?

且慢,貪心不好嗎?

• 貪心很棒啊,總是拿目前最有利的解

• 剛剛的問題,不知道該怎麼貪心

• 也有些問題,太貪心,得不償失

• HW5, Problem 1

• 總是以面額較高之貨幣付款,能讓使用的貨幣數量最小化嗎?

• 如果硬幣的面額是 {1,2,4,8}, 想要湊出面額 15, 怎麼做?

• 如果硬幣的面額是 {1,500,501}, 想要湊出面額 1000, 怎麼做

(8)

那,窮舉總行了吧 那,窮舉總行了吧

• 太過曠日費時,天荒地老

• 盲目的窮舉並沒有好好的利用問題的性質

• 剛剛的問題,如果對於每一個方格,窮舉四個方向

• 總複雜度需要 O(4^N) !!

• 其中 , N 是擺放的 L 型數量

(9)

• 直接解決大問題很難…

• 要是問題滿足以下條件:

• 規模小的時候 , 輕鬆簡單 , 易如反掌

• 規模大的時候 , 既不能貪心 , 窮舉又不切實際……

幸好,我們可以把大問題切割成小問題

• 而且,小問題的答案有助於我們探尋大問題的答案!

• 就可以考慮使用分治的概念解題

(10)

分工合作的想法 分工合作的想法

• 把大問題變成一些相似的小問題

• 該怎麼變 , 術語叫”切割問題”

• 不一定會有多個子問題,可能一個就足夠

• 有些不可能對答案造成影響的子問題,可以直接忽略

• 把小問題算出答案 ( 怎麼算的不重要 , 算得出來就好 )

• 就是”遞迴求解”囉

• 只要問題的切割有遇到終止條件的一天 , 一定算得出來 !

• 把小問題的答案變成大問題的答案

• 術語叫”合併問題”

• 善加利用我們已經得到的特性

(11)

複習一下二分搜尋法 複習一下二分搜尋法

• 在 S 中搜尋某數

• 不可能對答案造成影響的子問題,可以直接忽略

• 於是拋棄不用求解的子問題

• 分割問題為兩個規模接近的子問題,子問題的規模是原問題的一半

• 只有分,不太需要治

• 因為最後都只有一個可能的分支

• 子問題的解直接就會是答案

a M1 b M c M2 d

S

a M1 b M c M2 d

S

(12)

適用分治的時機 適用分治的時機

• 天生就適合分治的問題

• 問題本身就長得很遞迴 ( 原問題可以切割成許多規模較小的問題 )

• 資料結構由遞迴定義而得

• 用分治的想法可以讓程式很清楚!我是 Heap!

我也是 Heap!

我是二元搜尋樹 ! 我的子孫也是 !

(13)

遞迴定義 遞迴定義

f()

=

=

(14)

適用分治的時機 適用分治的時機

• 看起來貌似沒有分治結構的問題,如剛剛的 L 型方塊

• 但分治可以幫助我們求解

• 再舉一個例子

• 輸入 n ,請構造出一組 1~n 的排列,滿足任意選擇其中三個數,

按照原本的順序排列,均不會形成等差數列。

• n=8

• 3 4 2 1 7 8 5 6

(15)

• 想要直接構造長度為 n 的解答,似乎很困難?

• 不妨考慮分治:如果我們在已有長度為 n/2 的解答 X 的情況下,

是否有辦法構造出一組長度為 n 的解答 X’ 呢?

• 既然解答 X 合法,表示從 X 中任意取三個數,必定不會形成等差 數列

• 如果對於 X 中的每個元素加減一個數,是否還保持此性質?

• 如果對於 X 中的每個元素乘上一個數,是否還保持此性質?

(16)

• N=3 的解: 3 1 2

• N=6 的解:要如何構造,使得 1~6 都會用到呢?

• 使用 N=3 的解做一些變化,但避免變化前與變化後形成等差數列

• [3 1 2] 每個數值都加上 3 ,得到 [6 4 5]

• [3 1 2] 與 [6 4 5] 組合,這樣可以用到每個數

• [3 1 2 6 4 5]

• 似乎並未有效的防止等差數列產生

• 換個想法,利用乘法得到較大的數

(17)

於是乎 於是乎

• [3 1 2] 每個數值都乘以 2 ,得到 [6 2 4]

• 剩下 1, 3, 5 不妨把 [6 2 4] 各減 1 ,得到 [5 1 3] 這樣可以用到每個數

• 該怎麼決定他們的位置呢?

• 如果把奇數都擺在偶數的前面,無論是 [ 奇奇偶 ] 或 [ 奇偶偶 ] 都不可能 形成等差數列!當然,因為遞迴的性質, [ 奇奇奇 ] 與 [ 偶偶偶 ] 也不可能 我們成功找到一種由 N=3 的解 構造出 N=6 的解 的方法了!

• 有了這個思路後,剩下的想法就容易許多,不妨自己試試看,如果 n 是奇數的話,也可以這樣做嗎?

(18)

那那… 那那…

• 以上都是如果不把問題規模縮小,我們難以構造的問題

• 對於原本我們就會解答的問題

• 分治法有沒有任何幫助呢?

• 給定 a,n,M (int 範圍內 ) ,請求出 (a^n)%M

• 還記得嗎 ~~ (a*b)%M = ( a%M * b%M ) % M

• 因此,提前取餘數並不會造成結果不同,

• 於是我們在此不用擔心溢位問題

(19)

• Ans = (a^n)%M

• 用迴圈求解

• for(ans=1,i=0;i<n;i++)

• ans=ans*a%M;

• 時間複雜度為 O(n)!

• 可是 n 在 int 範圍內,這樣不夠快

• 其實,我們隱含著把求解「 a^n 」分割成求解「 a^(n-1) 」與「 a^1 」的 兩個子問題,再利用相乘合併,求得「 a^n 」的答案

• 兩個子問題的規模懸殊太大,並不是個理想的分割方法

(a^n)%M

(20)

換個想法 換個想法

• 在生活中,我們如何計算 2^16 呢 ?

• 2*2=4

• 4*4=16

• 16*16=256

• 256*256=65536

• 只要四次就足夠了

• 那 2^17 呢 ?

• 再多乘一次 2 就好 !

(21)

• Divide ( 把問題分隔成相似的小問題 )

• a^n =

• If n%2 == 0, a^n = [a^(n/2)]^2

• If n%2 == 1, a^n = [a^((n-1)/2)]^2*a

• a^(n/2) 確實是「性質相近」且「規模較小」的子問題

• Recursive ( 求出小問題的解答 )

• 終止條件: If n = 1, 則 a 就是解答了 !

• Conquer! (Merge 利用小問題的答案求解大問題 )

• 設 b = a^(n/2), 則 If n%2 == 0, a^n = b*b, else a^n=b*b*a, 都是常數 時間可以完成的工作!

(22)

分析一下時間 分析一下時間

• 直觀的看來,每次可以約略把 n 的大小變為一半或更小

• 直到 n 變成 1 為止

• 從大問題到子問題的規模: n -> n/2 -> n/4 -> …… -> 1

• 總共有 (int)log_2(n)+1 層,每一層的合併時間為常數

• 因此,我們可以在 O(log n) 時間內求出解答

• 盡量把問題分為兩個大小約略相等的子問題,讓子問題的最大規 模盡量快速的變小,才能有效率的降低複雜度

• 思考:如何快速的計算 (a + a^2 + a^3 + … + a^n) 呢 ?

(23)

常見的排序問題 常見的排序問題

• 插入排序法

• 泡沫排序法

• 選擇排序法

• 時間複雜度 = ?

(24)

合併排序法 Merge Sort 合併排序法 Merge Sort

• 現在想要排序一個陣列 長度為 n 的陣列

• 依樣畫葫蘆

• 分割 : 把陣列分成左右長度差不多的兩部分

• 遞迴 : 把左右兩部分都各自排序完成

• 合併 : 把左右部分排序後的結果合併起來,成為大問題的答案

• 想一想:給定兩個已經排序過的陣列 A,B, 有沒有可以快速得到 A 與 B 所 有元素一起排序過後的結果的方法呢?

(25)

由兩個分別排序過的陣列 合併出整體的結果

由兩個分別排序過的陣列 合併出整體的結果

• A = [1 5 6 8 9] B = [2 4 7 10]

• C[1] = 目前 A 跟 B 當中最小的元素

• 最小的在哪裡 ? 只可能在 A 的開頭或 B 的開頭

• C[1] = 1

• A = [1 5 6 8 9] B = [2 4 7 10]

• C[2] = 2

• A = [1 5 6 8 9] B = [2 4 7 10]

• C[3] = 4

• A = [1 5 6 8 9] B = [2 4 7 10] ……..

• C[4] = 5

假設兩部分都已經排序完成 , 該怎麼合併呢 !?

(26)

繼續 繼續

A = [1 5 6 8 9] B = [2 4 7 10] => C[5] = 6

A = [1 5 6 8 9] B = [2 4 7 10] => C[6] = 7

A = [1 5 6 8 9] B = [2 4 7 10] => C[7] = 8

A = [1 5 6 8 9] B = [2 4 7 10] => C[8] = 9

A = [1 5 6 8 9] B = [2 4 7 10] => C[9] = 10

A = [1 5 6 8 9] B = [2 4 7 10] => 完成 !

C = [1,2,4,5,6,7,8,9,10]

(27)

合併需要幾次運算 合併需要幾次運算

• 每次從 A 的開頭與 B 的開頭 , 挑選數值較小者

• 如果是空的就忽略他

• 決定 C 的每一項 , 只需要一次比較 => O(1)

• 每次合併所需時間:該部分的數列長度

• A = [1 5 6 8 9] B = [2 4 7 10]

• C = [1,2,4,5,6,7,8,9,10]

• 在這個例子中長度是 9

(28)

遞迴樹 遞迴樹

• 雖然看不見,但實際上我們求解問題的過程可以畫成一 個樹狀結構,我們稱之為「遞迴樹」,樹上的每個節點 實際上代表每個子問題

• 不同於以往,貪心法是「直接走向最佳解所在的分支」

,並得以證明 ( 至少一組 ) 最佳解在該分支中;

• 現在行不通了,必須「綜合考量可能分支所得到的解」

,然後藉由這些小問題的解,找出原本大問題的解!

(29)

29

Merge sort ~ Merge sort ~

7 2 9 4  3 8 6 1

(30)

30

Merge sort ~ Merge sort ~

• 求左半邊

7 2  9 4

7 2 9 4  3 8 6 1

(31)

31

• 左半邊的左半邊

7 2  9 4

7  2

7 2 9 4  3 8 6 1

(32)

32

Merge sort ~ Merge sort ~

• 到”終止問題”囉 , 剩下一個 !

7 2  9 4

7  2

7  7

7 2 9 4  3 8 6 1

(33)

33

• 繼續做

7 2  9 4

7  2

7  7 2  2

7 2 9 4  3 8 6 1

(34)

34

• 合併 !

7 2  9 4

7  2  2 7

7  7 2  2

7 2 9 4  3 8 6 1

(35)

35

• 合併 !

7 2  9 4

7  2  2 7 9 4  4 9

7  7 2  2

7 2 9 4  3 8 6 1

9  9 4  4

(36)

36

• 繼續合併

7 2  9 4  2 4 7 9

7  2  2 7 9 4  4 9

7  7 2  2 9  9 4  4

7 2 9 4  3 8 6 1

(37)

37

• 右邊也做一做

7 2  9 4  2 4 7 9 3 8 6 1  1 3 6 8

7  2  2 7 9 4  4 9 3 8  3 8 6 1  1 6

7  7 2  2 9  9 4  4 3  3 8  8 6  6 1  1

7 2 9 4  3 8 6 1

(38)

38

• 完成囉 !

7 2  9 4  2 4 7 9 3 8 6 1  1 3 6 8

7  2  2 7 9 4  4 9 3 8  3 8 6 1  1 6

7  7 2  2 9  9 4  4 3  3 8  8 6  6 1  1

7 2 9 4  3 8 6 1  1 2 3 4 6 7 8 9

(39)

分析一下執行時間 分析一下執行時間

長度為 n/2 長度為 n/2

長度為 n/4 長度為 n/4 長度為 n/4 長度為 n/4

長度 1 長度 1 長度 1 長度 1 長度 1 長度 1 長度 1 長度 1

長度為 n

第一層總和 : 1 個節點 , 每個長度 O(n)

第二層總和 : 2 個節點 , 每個長度約 (n/2), 總和 O(n)

第三層總和 : 4 個節點 , 每個長度約 (n/4), 總和 O(n)

第四層總和 : 8 個節點 , 每個長度約 (n/8), 總和 O(n)

第 log n 層總和 : n 個節點 , 每個長度 1, 總和 O(n)

(40)

每一層的時間都是 O(n) ! 每一層的時間都是 O(n) !

• 終止條件為 n=1, 總共有幾層呢?

• 顯然由一些簡單的數學 , 會發現共有 log N 層 !

• 總時間 O(n log n) !!

• 空間呢 ? 我們只要利用原本的陣列操作就可以囉 !

• 需要一個輔助陣列暫存合併後的結果

• 不可以覆蓋到原本的兩個要合併的陣列

(41)

Merge-sort function Merge-sort function

• N 、 A[0~(N-1)]

• mergesort(0, N):  其中包含 Left, 不包含 Right

• Example: mergesort(3, 7) => 排序 A[3], A[4], A[5], A[6]

mergesort(Left, Right):

if(Left+1==Right) return;  長度為 1, 終止條件 int Mid = (Left + Right) / 2;  找出中間的位置

mergesort(Left, Mid);  遞迴求解 , 排序好 A[Left], …, A[Mid-1]

mergesort(Mid, Right);  遞迴求解 , 排序好 A[Mid], …, A[Right-1]

int L=Left, R=Mid, K=Left;

while(L<Mid || R<Right)  開始合併 左半邊開頭 = L, 右半邊 = R if( L<Mid &&

(R>=Right || A[L]<=A[R]) )

// 若左半邊還有東西 , 且 (1) 右半邊空了 (2) 左半邊的開頭較小 B[K++] = A[L++];  放進左半邊的開頭 , 而開頭位置 L 加 1

else

B[K++] = A[R++];  放進右半邊的開頭 , 而開頭位置 R 加 1 for(L=Left; L<Right; L++)  丟回去原本的陣列

A[L]=B[L];

(42)

另外的一種思路 另外的一種思路

• 快速排序法

• 對問題先好好的分割,讓合併時不那麼麻煩!

• 分割 :

• 從陣列中選一個值 x

• 亂排一下 , 把陣列分成三個區域 順序保持 L; M; R

• L: <x 的元素 ( 順序不重要 )

• M: =x 的元素

• R: >x 的元素 ( 順序不重要 )

• 遞迴 : 用快速排序法分別把 L 跟 R 排好

• 合併 : 把左右各自的結果合併起來

• 怎麼合併 ? 不用合併 , 遞迴完成自然而然整個都是遞增的

(43)

快速排序法 ~ 快速排序法 ~

• 選擇 x 值 = 6, 讓數列滿足

• 在此為了講解方便 , 三個部份我們依照原本的順序排列

• 這步有很多種不同的實作方式

• (L) 7 2 9 4 3 7 6 1 (R)

• 反覆進行以下操作:直到 L>=R 的位置為止

• 從 L 往右找第一個 >=6 的數 p

• 從 R 往左找第一個 <=6 的數 q

• 交換 p,q , L 往右移一格, R 往左移一格

• 7 (L) 2 9 4 3 7 6 (R) 1

• 1 2 9 (L) 4 3 7 (R) 6 7

• 1 2 6 4 3 7 9 7

• 在此為了講解方便 , 我們假設結果為 [2 4 3 1] 6 [7 9 7]

7 2 9 4 3 7 6 1  2 4 3 1 6 7 9 7

小於 6 等於 6 大於 6

(44)

快速排序法 ~ 快速排序法 ~

• 開始排序 , 左右部分已經分好囉

7 2 9 4 3 7 6 1  2 4 3 1 6 7 9 7

(45)

快速排序法 ~ 快速排序法 ~

• 遞迴排好左半邊

2 4 3 1  1 2 4 3

7 2 9 4 3 7 6 1  2 4 3 1 6 7 9 7

(46)

快速排序法 ~ 快速排序法 ~

• 遇到終止條件

2 4 3 1  1 2 4 3

1

7 2 9 4 3 7 6 1  2 4 3 1 6 7 9 7

(47)

快速排序法 ~ 快速排序法 ~

• 1 的順序確定了

7 2 9 4 3 7 6 1  2 4 3 1 6 7 9 7

2 4 3 1  1 2 4 3

1

(48)

快速排序法 ~ 快速排序法 ~

• 排右半邊

7 2 9 4 3 7 6 1  2 4 3 1 6 7 9 7

2 4 3 1  1 2 4 3

1 4 3  3 4

4

(49)

快速排序法 ~ 快速排序法 ~

• 3 與 4 的順序確定了 , 由於原數列滿足

• 所以兩個部分各自排好後 , 整體就滿足從小到大的關係

7 2 9 4 3 7 6 1  2 4 3 1 6 7 9 7

2 4 3 1  1 2 3 4

1 4 3  3 4

4

(50)

快速排序法 ~ 快速排序法 ~

• 左半邊都排序完成了

7 2 9 4 3 7 6 1  1 2 3 4 6 7 9 7

2 4 3 1  1 2 3 4

1 4 3  3 4

4

(51)

快速排序法 ~ 快速排序法 ~

• 開始做右半邊

7 9 7 1  1 3 8 6 7 2 9 4 3 7 6 1  1 2 3 4 6 7 9 7

2 4 3 1  1 2 3 4

1 4 3  3 4

4

(52)

快速排序法 ~ 快速排序法 ~

• 小於 7 的部分是空的 , 不需要遞迴求解

7 9 7  7 7 9 6 7 2 9 4 3 7 6 1  1 2 3 4 6 7 9 7

2 4 3 1  1 2 3 4

1 4 3  3 4

4

9

(53)

快速排序法 ~ 快速排序法 ~

• 更新回去 ( 雖然只有一個元素 )

7 9 7  7 7 9 6 7 2 9 4 3 7 6 1  1 2 3 4 6 7 9 7

2 4 3 1  1 2 3 4

1 4 3  3 4

4

9

(54)

快速排序法 ~ 快速排序法 ~

• 兩個部分都做好了 , 大功告成 @_@

7 9 7  7 7 9 6 7 2 9 4 3 7 6 1  1 2 3 4 6 7 7 9

2 4 3 1  1 2 3 4

1 4 3  3 4

4

9

(55)

分析時間 分析時間

• 每次需要比較一個元素與 x 的大小關係 , 決定他的位置在左邊還是右邊

• 所需時間與該部分元素數量呈線性關係

• 每一層總和都接近 O(n) 等級

(56)

長度為 n/2 長度為 n/2

長度為 n/4 長度為 n/4 長度為 n/4 長度為 n/4

長度 1 長度 1 長度 1 長度 1 長度 1 長度 1 長度 1 長度 1

長度為 n

第一層總和 : 1 個節點 , 每個長度 O(n)

第二層總和 : 2 個節點 , 每個長度約 (n/2), 總和 O(n)

第三層總和 : 4 個節點 , 每個長度約 (n/4), 總和 O(n)

第四層總和 : 8 個節點 , 每個長度約 (n/8), 總和 O(n)

第 log n 層總和 : n 個節點 , 每個長度 1, 總和 O(n)

好的情況

( 長相跟 Merge sort 一模一樣 ) 好的情況

( 長相跟 Merge sort 一模一樣 )

(57)

分析時間 分析時間

• 在一般的情形下 (x 介於中間 , 使得 L 與 R 的長度差不 多 ) 也只會有 log n 層 , 於是在這種情形下的時間複雜 度與 Merge sort 相等

• O(n) * O(log n) = O(n log n)

• 還記得演算法的執行時間中 , 除了時間複雜度 ( 執行時間 與問題規模的相對增長速率 ) 以外 , 執行時間的常數也會 稍微影響執行速度

快速排序法在好的情況略比 Merge Sort 快

(58)

糟糕的情況 糟糕的情況

• 長度總和 n

• 長度總和 n-1

• 長度總和 n-2

• 長度總和 n-3

• …………

• 長度總和 1

• 1 + 2 + 3 + … + n = n(n+1)/2 = O(n^2) !!

(59)

所以…… 所以……

• 總共可能高達 n 層 !!!

• 最差情況 : O(n) * O(n) = O(n^2) 遠大於 O(n log n)

• 幸運的是 , 這並不常發生

• 邪惡的出題者表示 : 依據莫非定律 , 你覺得 不會發生的情況有時特別容易發生 OAOAOAO

(60)

比較一下兩種排序的方法 比較一下兩種排序的方法

• 合併排序 :

• 隨意分割問題 ( 左右切一半 ), 然後好好的合併

• 因為分割的兩個問題大小接近 , 所以遞迴的深度不會超過 O(log n), 總時間 O(n log n).

• 快速排序 : ( 聽起來很迅速 )

• 好好的分割問題 ( 排列好大小關係 ), 然後自然就合併了

• 因為分割的兩個問題大小可能差異很大 , 所以遞迴的深度高達 O (n), 總時間最差 O(n^2), 平均來說 O(n log n).

• 我們可以隨機的選取中心點 , 這樣期望複雜度為 O(n log n)

(61)

經典問題 – 逆序數對數量計算 經典問題 – 逆序數對數量計算

• A = [2, 4, 1, 3]

• 對於一個陣列 A 中 , 任取兩個元素 , 順序不變 , 如果前者大於後者 , 則我們稱為”逆序數對”

• (2,4)

• (2,1) => 逆序數對

• (2,3)

• (4,1) => 逆序數對

• (4,3) => 逆序數對

• (1,3)

(62)

計算逆序數對的數量 計算逆序數對的數量

• 既然不知道答案 , 就每一種組合都試試看 !

• 還記得傳說中的枚舉法

• 枚舉所有數對…檢查一下大小就好 !

• 有 n(n-1)/2 = O(n^2) 種 !

• 成本太高囉

• 仔細想想 , 我們並不是真的需要找出所有逆序數對

(63)

分治 ! 分治 !

• 我們試著把陣列分成兩半

• 線條就是”逆序數對”

• 分成兩半後有兩種情形

• 1. 在同一部分 ( 上面的線條 )

• 2. 在不同部分 ( 下面的線條 ), 橫跨兩邊

7 2 9 4  3 8 6 1

(64)

分割問題 分割問題

• 1. 在同一部分 => 遞迴求解

• 2. 在不同部分 => 合併的時候處理

7 2 9 4  3 8 6 1

(65)

遞迴形式 遞迴形式

• 因為分割問題的方法與 Merge Sort 非常類似,因此就沿用一 樣的定義吧 !

(66)

寫成函數 寫成函數

N 、 A[0~(N-1)]

int counting(Left, Right):  其中包含 Left, 不含 Right

回傳逆序數對的數量

N=8, A[0~(N-1)] = [7,2,9,4,3,8,6,1]

counting(0,4) = 3

counting(5,8) = 4

Mid=(Left,Right)/2

counting(Left,Right)=

counting(Left, Mid) ( 都

在左邊

)+counting(Mid, Right) (

都在右邊

)

橫跨左右部分的逆序數對 ( 下面的線條 )

7 2 9 4  3 8 6 1

(67)

合併問題 合併問題

• counting(Left,Right)=

• counting(Left, Mid) ( 都在左邊)+counting(Mid, Right) ( 都在右邊 )

• 橫跨左右部分的逆序數對 ( 下面的線條 )

• 橫跨左右部分的逆序數對 : 怎麼求呢 ?

• 枚舉左邊的每一項 , 右邊的每一項

• 在第一層就 O(n/2) * O(n/2) = O(n^2) !

• 辛苦的分治白忙一場 , 問題的合併變成瓶頸

• 有沒有更好的合併方法呢

(68)

觀察性質 觀察性質

• 觀察 1: 因為這些數對橫跨兩邊 , 因此 :

• 即使將左右兩部分排序 , 這部分的數量依然相同 !

• 觀察 2: 經過排序以後瓊舉左邊的每一項 , 與它有關的逆序 數對是原本右邊滿足的部分 , 加上往右找比它小的那些數值

!

7 2 9 4  3 8 6 1

2 4 7 9  1 3 6 8

1 對 : (2,1) 2 對 : (4,1)/

(4,3) 3 4

共有 10 對 ! 3>1,

6>4,

(69)

時間呢 時間呢

• 回傳 : 左半部分與右半部分 ( 排序前 ) 的逆序數對數量 : 3+4

• 加上上面所有箭頭的長度和 : 10 = 3+4+10 = 17

• 每瓊舉左邊的一項 x, 就只要檢查上一次瓊舉的部分箭頭終點的下一項 y, 如 果 y<x, 那箭頭就可以延伸 !

• 設該層共有 n 個元素 , 左右各 n/2 個

• 左邊枚舉 n/2 次

• 箭頭也至多只會延伸 n/2 次

• O(n) !!

• 與合併排序法的時間分析一模一樣

2 4 7 9  1 3 6 8

(70)

寫寫程式吧 寫寫程式吧

• 以合併排序法為基礎

• 在合併前額外加上計算 “橫跨左右部分的逆序數對數量”

• 回傳三個部份的總和 , 就是在處理範圍中的逆序數對數量

• 順便也幫忙排序完成了 !

• 在此問題中,子問題的答案並不只有「該區間內逆序數對的數 量」,也隱含了「該區間內排序過的結果」

(71)

counting function counting function

• N 、 A[0~(N-1)]

• int counting(Left, Right):  其中包含 Left, 不含 Right

• 回傳逆序數對的數量

int counting (Left, Right):

if(Left+1==Right) return 0;  長度為 1, 終止條件 int Mid = (Left + Right) / 2;  找出中間的位置

int Cnt = counting(Left, Mid);  遞迴求解 , 順便排序 A[Left]~A[Mid-1]

Cnt += counting(Mid, Right);  遞迴求解 , 順便排序 A[Mid]~A[Right-1]

int L, R=Mid;  R: 箭頭的起點永遠在中間

for(L=Left; L<Mid; L++){  枚舉左邊的每一項

while(R<Right && A[R]<A[L])  如果箭頭可以延伸的話 R++;  就延伸吧 !

Cnt += R-Mid;  加上箭頭的長度 }

//Merge sort: 合併左右兩個部分 return Cnt;

參考文獻

相關文件

The resulting FAS-P IVOT approach demonstrates that repetitively running a sorting algorithm of query complexity O(N lg N ) on N items may lead to a practical way to solve min-FAST

Tungsten carbide scissors last almost two times longer than stainless steel, and needle holder tips can be made with a pyramid cross pattern that holds suture needles much

Textbook Chapter 4.3 – The substitution method for solving recurrences Textbook Chapter 4.4 – The recursion-tree method for solving recurrences Textbook Chapter 4.5 – The master

• Algorithmic design methods to solve problems efficiently (polynomial time).. • Divide

Textbook Chapter 33.4 – Finding the closest pair of points.. Closest Pair of

Textbook Chapter 4.3 – The substitution method for solving recurrences Textbook Chapter 4.4 – The recursion-tree method for solving recurrences Textbook Chapter 4.5 – The master

Algorithm Design Methods Greedy Algorithm.. by Chin

for Sprout 2014 by Chin

Algorithm Design Methods Greedy Algorithm.. by Chin

Algorithm Design Methods Divide &amp; Conquer..

for Sprout 2014 by Chin

Initially, in closed shock tube, water column moves at u = 1 from left to right, yielding air compression at right. &amp; air expansion

by 2019

Algorithm Design Methods Enumeration.. by Chin

if left_sum&gt;=right_sum and left_sum&gt;=cross_sum return (left_low,left_high,left_sum). else if right_sum&gt;=left_sum and right_sum&gt;=cross_sum

證明比較鬆的upper bound或lower bound來慢慢 接近tight

Strassen’s method is not as numerically stable as 基本法..

主定理 (Master Theorem) 主定理 (Master Theorem). reference:

Lecture &amp; modified by baluteshih Credit by yp155136. Credit

by 2020

Graph Algorithms Euler Circuit Hamilton Circuit.. for Sprout 2014 by Chin

Graph Algorithms Euler Circuit Hamilton Circuit.. for Sprout 2014 by Chin

proposed a greedy algorithm to utilize the Divide-and-Conquer technique to obtain near optimal scheduling while attempting to minimize the size of total communication messages