第四十一章~四十二章:荷蘭國旗問題、矩陣相乘之Strassen算法
前言
本文要講的兩個問題:荷蘭國旗和矩陣相乘之Strassen算法都跟分治法相關,故把這兩個問題放到了一起。所謂分治,便是分而治之的意思,好比打戰時面對敵人龐大的武裝部隊,採取避其主力,各個擊破的策略。
有何問題,歡迎隨時不吝指正,thanks。
第十一章、荷蘭國旗問題
題目描述
現有紅白藍三個不同顏色的小球,亂序排列在一起,請重新排列這些小球,使得紅白藍三色的同顏色的球在一起。這個問題之所以叫荷蘭國旗,是因爲我們可以將紅白藍三色小球想象成條狀物,有序排列後正好組成荷蘭國旗。如下圖所示:
思路分析
初看此題,我們貌似除了暴力解決並無好的辦法,但聯想到我們所熟知的快速排序算法呢?我們知道,快速排序時基於分治模式處理的,對一個典型子數組A[p...r]排序的分治過程爲三個步驟:
- 分解:A[p..r]被劃分爲倆個(可能空)的子數組A[p ..q-1]和A[q+1 ..r],使得A[p ..q-1] <= A[q] <= A[q+1 ..r]
- .解決:通過遞歸調用快速排序,對子數組A[p ..q-1]和A[q+1 ..r]排序。
- 合併。
也就是說,快速排序的主要思想便是依託於一個partition分治過程,每一趟排序的過程中,選取的主元都會把整個數組排列成一大一小的序列,繼而遞歸排序完整個數組。
如下僞代碼所示:
快速排序算法的關鍵是PARTITION過程,它對A[p..r]進行就地重排:
PARTITION(A, p, r)
1 x ← A[r]
2 i ← p - 1
3 for j ← p to r - 1
4 do if A[j] ≤ x
5 then i ← i + 1
6 exchange A[i] <-> A[j]
7 exchange A[i + 1] <-> A[r]
8 return i + 1
繼而遞歸完成整個排序過程:
QUICKSORT(A, p, r)
1 if p < r
2 then q ← PARTITION(A, p, r) //關鍵
3 QUICKSORT(A, p, q - 1)
4 QUICKSORT(A, q + 1, r)
舉個例子如下:i 指向數組頭部前一個位置,j 指向數組頭部元素,j 在前,i 在後,雙雙從左向右移動。
① j 指向元素2時,i 也指向元素2,2與2互換不變
i p/j
2 8 7 1 3 5 6 4(主元)
② 於是j 繼續後移,直到指向了1,1 <= 4,於是i++,i 指向8,故j 所指元素1 與 i 所指元素8 位置互換:
i j
2 1 7 8 3 5 6 4
③ j 繼續後移,指到了元素3,3 <= 4,於是同樣i++,i 指向7,故j 所指元素3 與 i 所指元素7 位置互換:
i j
2 1 3 8 7 5 6 4
④ j 一路後移,沒有再碰到比主元4小的元素:
i j
2 1 3 8 7 5 6 4
⑤ 最後,A[i + 1] <-> A[r],即8與4交換,所以,數組最終變成了如下形式:
2 1 3 4 7 5 6 8
ok,至此快速排序第一趟完成。就這樣,4把整個數組分成了倆部分,2 1 3,7 5 6 8,再遞歸對這倆部分分別進行排序。
全部過程可以參看此文:快速排序算法,或看下我以前在學校裏畫的圖:
而我們面對的問題是,重新排列使得所有球排列成三個不同顏色的球,是否可以設定三個指針,借鑑partition過程呢?
解法一、partition分治
通過前面的分析得知,這個問題,類似快排中partition過程。只是需要用到三個指針,一前begin,一中current,一後end,倆倆交換。
- current遍歷,整個數組序列,current指1不動,
- current指0,與begin交換,而後current++,begin++,
- current指2,與end交換,而後,current不動,end--。
爲什麼,第三步,current指2,與end交換之後,current不動了列,對的,正如algorithm__所說:current之所以與begin交換後,current++、begin++,是因爲此無後顧之憂。而current與end交換後,current不動,end--,是因有後顧之憂。
讀者可以試想,你最終的目的無非就是爲了讓0、1、2有序排列,試想,如果第三步,current與end交換之前,萬一end之前指的是0,而current交換之後,current此刻指的是0了,此時,current能動麼?不能動啊,指的是0,還得與begin交換列。
ok,說這麼多,你可能不甚明瞭,直接引用下gnuhpc的圖,就一目瞭然了:
參考代碼如下:
//引用自gnuhpc
while( current<=end )
{
if( array[current] ==0 )
{
swap(array[current],array[begin]);
current++;
begin++;
}
else if( array[current] == 1 )
{
current++;
}
else //When array[current] =2
{
swap(array[current],array[end]);
end--;
}
}
本章完。
第四十二章:矩陣相乘之Strassen算法
題目描述
請編程實現矩陣乘法,並考慮當矩陣規模較大時的優化方法。
思路分析
根據wikipedia上的介紹:兩個矩陣的乘法僅當第一個矩陣B的列數和另一個矩陣A的行數相等時才能定義。如A是m×n矩陣和B是n×p矩陣,它們的乘積AB是一個m×p矩陣,它的一個元素其中 1 ≤ i ≤ m, 1 ≤ j ≤ p。
值得一提的是,矩陣乘法滿足結合律和分配率,但並不滿足交換律,如下圖所示的這個例子,兩個矩陣交換相乘後,結果變了:
下面咱們來具體解決這個矩陣相乘的問題。
解法一、暴力解法
其實,通過前面的分析,我們已經很明顯的看出,兩個具有相同維數的矩陣相乘,其複雜度爲O(n^3),參考代碼如下:
//矩陣乘法,3個for循環搞定
void Mul(int** matrixA, int** matrixB, int** matrixC)
{
for(int i = 0; i < 2; ++i)
{
for(int j = 0; j < 2; ++j)
{
matrixC[i][j] = 0;
for(int k = 0; k < 2; ++k)
{
matrixC[i][j] += matrixA[i][k] * matrixB[k][j];
}
}
}
}
解法二、Strassen算法
在解法一中,我們用了3個for循環搞定矩陣乘法,但當兩個矩陣的維度變得很大時,O(n^3)的時間複雜度將會變得很大,於是,我們需要找到一種更優的解法。
一般說來,當數據量一大時,我們往往會把大的數據分割成小的數據,各個分別處理。遵此思路,如果丟給我們一個很大的兩個矩陣呢,是否可以考慮分治的方法循序漸進處理各個小矩陣的相乘,因爲我們知道一個矩陣是可以分成更多小的矩陣的。
如下圖,當給定一個兩個二維矩陣A B時:
這兩個矩陣A B相乘時,我們發現在相乘的過程中,有8次乘法運算,4次加法運算:
矩陣乘法的複雜度主要就是體現在相乘上,而多一兩次的加法並不會讓複雜度上升太多。故此,我們思考,是否可以讓矩陣乘法的運算過程中乘法的運算次數減少,從而達到降低矩陣乘法的複雜度呢?答案是肯定的。
1969年,德國的一位數學家Strassen證明O(N^3)的解法並不是矩陣乘法的最優算法,他做了一系列工作使得最終的時間複雜度降低到了O(n^2.80)。
他是怎麼做到的呢?還是用上文A B兩個矩陣相乘的例子,他定義了7個變量:
如此,Strassen算法的流程如下:
- 兩個矩陣A B相乘時,將A, B, C分成相等大小的方塊矩陣:
;
- 可以看出C是這麼得來的:
- 現在定義7個新矩陣(讀者可以思考下,這7個新矩陣是如何想到的):
- 而最後的結果矩陣C 可以通過組合上述7個新矩陣得到:
表面上看,Strassen算法僅僅比通用矩陣相乘算法好一點,因爲通用矩陣相乘算法時間複雜度是,而Strassen算法複雜度只是。但隨着n的變大,比如當n >> 100時,Strassen算法是比通用矩陣相乘算法變得更有效率。
如下圖所示:
解法三、持續優化
根據wikipedia上的介紹,後來,Coppersmith–Winograd 算法把 N* N大小的矩陣乘法的時間複雜度降低到了:,而2010年,Andrew Stothers再度把複雜度降低到了,一年後的2011年,Virginia Williams把複雜度最終定格爲:
參考文獻
- 快速排序算法:http://blog.csdn.net/v_july_v/article/details/6116297;
- 快速排序算法的深入分析:http://blog.csdn.net/v_july_v/article/details/6211155;
- gnuhpc:http://blog.csdn.net/gnuhpc/article/details/6207285;
- wikipedia上關於Strassen算法的介紹:http://zh.wikipedia.org/wiki/%E6%96%BD%E7%89%B9%E6%8B%89%E6%A3%AE%E6%BC%94%E7%AE%97%E6%B3%95;
- 第42章部分圖來自此文“ Computer Algorithms: Strassen's Matrix Multiplication” :http://www.stoimen.com/blog/2012/11/26/computer-algorithms-strassens-matrix-multiplication/;
- 上文的翻譯版,來自圖靈社區:http://www.ituring.com.cn/article/17978;
- Coppersmith–Winograd 算法: http://en.wikipedia.org/wiki/Coppersmith%E2%80%93Winograd_algorithm;
後記
編程藝術原計劃寫到第五十章,如今只剩下最後八章,感謝各位一直以來的關注。預祝本博客所有的讀者新春快樂,在馬年一切都能心想事成,thanks。
July、二零一四年一月二十八日。