荷蘭國旗問題(改造快速排序)
題目
問題:現有紅、白、藍三個不同顏色的小球,亂序排列在一起,請重新排列這些小球,使得紅白藍三色的同顏色的球在一起。
PS:之所以叫荷蘭國旗問題,是因為可以將紅白藍三色小球想象成條狀物,有序排列後正好組成荷蘭國旗,如下圖:
演算法解析
為了解決此問題,所以問題轉換為:給定陣列A[0…N-1],元素只能取0、1、2三個值,設計演算法,使得陣列排列成“00…0011…1122…22”的形式。
然後借鑑快速排序中partition的過程。定義三個指標:begin=0、cur=0、end=N-1,接下來進行判斷:
A[cur]==0時:
若begin==cur,則begin++,cur++
若begin≠cur,則說嗎A[begin]一定不是0,而我們的目的是0在最前面,所以A[cur]與A[begin]交換,begin++,cur++
A[cur]==1時:因為上面的判斷可以保證所有的0都在最前面,所以這裡直接cur++,begin不變,end不變就好。
A[cur]==2時:A[cur] 與A[end]交換,end--,cur不變,這樣2就一定能在最後
寫成程式碼的話就是:
while(cur<= end) {
if(a[cur] == 2){
swap(a[end],a[cur]);
end--;
}else if (a[cur] == 1) {
cur++;
}else {
if( begin == cur ) {
begin++;
cur++;
} else {
swap(a[begin], a[cur] );
begin++;
cur++;
}
}
}
利用迴圈不變式進行解釋
一開始給陣列添加了begin,cur, end是可以把陣列分成4個空間的,如下:
A [0, begin)
B [begin, cur)
C [cur, end)
D [end, N-1]
而一開始begin,cur都指向開始,end指向0,所以A, B, D一開始時 ∅。
當然不管什麼時候A, B,D這三個區間都是我們已經控制住的區域,因為:
A中一定都是0;
B中一定都是1(因為如果begin和cur都是0,則它們都會向後移動一位,於是只要begin和cur分開,則begin一定指向了1(PS:begin後面可能還有0));
D中一定都是2;
只有C才是有待考察的。
於是C中如果是0,就扔到A中,是1就扔到B中,是2就扔到D中,即:遍歷cur,根據a[cur]的值做相應處理,直到區間[cur,end]為空,即cur==end時退出。
其他
話又說回來,荷蘭國旗問題中只有0,1,2三個數,那我們能不能這樣:
從頭到尾掃一遍,記錄下0,1,2的數量,假設有八個0,五個1,3個2,那我們就直接把前八個清為0,中間五個清為1,後面3個清為2。
當然可以,不過這樣幹太不講理了,而且這個實際作用不大,比如,可以使用荷蘭國旗問題優化快速排序(見下面)。
當然如果全是int是能這樣做的(┑( ̄Д  ̄)┍)。
改造快速排序
如果這麼定義:
A[i]< key時相當於“荷蘭國旗問題”中的0
A[i]= key時相當於“荷蘭國旗問題”中的1
A[i]> key時相當於“荷蘭國旗問題”中的2
這樣就可以使用“荷蘭國旗問題”的解法來解決快速排序了,這樣一來,即使待排序的元素中有一些元素和key一樣,也能保證時間複雜度是最差是NlogN的,因為對於待排序的等於Key的數值,可以在執行下一次Partition時直接跳過,利於資料規模的降低。