很多的算法都是遞歸的結構,遞歸的目的呢,是在自己調用自己的時候,將問題分解成更小的問題,這個過程也叫做divide-and-conquer,通過把原來的問題的一個大問題,分解成一個更小的問題,再把更小的問題分解成微不足道的問題,再一一解決所有所有的問題。
devide-and-conquer一般是這樣來解決問題的:
Divide:將問題分解成更小的,但是相似的問題
Conquer:遞歸解決這些小問題,如果小問題已經足夠小,直接解決;否則可以重復Divide的過程
Combine:將解決小問題的方案合並和大的解決方案中,從而解決更大的問題
今天要說的歸並排序,就是這樣的一種模式。
歸並排序算法
Divide:將n個要排序的元素分成兩半,各占n/2
Conquer:用歸並排序分別對兩個n/2的元素排序
Combine:將兩個排序的結果合並(Merge),產出最終結果
這是一個遞歸的過程,那遞歸到什么時候是個頭呢?
當被分割的序列長度是1了,就該結束了,一個元素就不用排序了吧。
設想一個我們有
MERGE(A, p, q, r)
A就是我們要排序的序列
p,q,r都是A的index,滿足條件p<=q<r,我們假設A[p..q]和A[q+1..r]是已經排序排好的,是有序的數組,於是我們總共有n個元素需要排序n = r - p + 1。
還是用打牌的觀點來說,假如我們現在把一副牌洗亂,然后分成兩堆,左右各一半。然后分別將左右兩堆按從小到大排序,牌面均向上。
現在,我們想把這兩堆牌合並在一起,並且是有序疊放的。就可以這樣做:
- 比較左邊和右邊的牌,取小的那一張,拿出來,扣在桌子上。
- 再次比較兩堆牌,取較小的一張,拿出來,扣在桌子上
- 重復上面的動作,知道所有的牌都合並在一起。
這就是歸並排序。
可是這里面有個小問題,但某些情況下,可以左邊已經沒有牌了,右邊還有一堆。這時候去比較左右兩邊的時候,是不是要先檢查一下是否還有牌呢?
舉個例子,左邊的牌是
1,3,4,5
右邊的牌是
2,6,7,8
我們來做歸並排序:
- 比較左右兩堆,取出最小的1,扣在桌上
- 比較左右兩堆,取出最小的2,扣在桌上
- 比較左右兩堆,取出最小的3,扣在桌上
- 比較左右兩堆,取出最小的4,扣在桌上
- 比較左右兩堆,取出最小的5,扣在桌上
現在呢,1,2,3,4,5都排好序了,左邊牌堆沒有牌了。右邊還有3張。那比較左右兩邊的時候,左邊不就是null
了?
這個時候呢,可以考慮弄個正無窮∞出來,兩個牌堆是
1,3,4,5,∞和2,6,7,8,∞那就不用判斷了。
根據這個思想,得出我們的偽代碼。
偽代碼
Merge(left, right)
let result[1..left.length+right.length] be new arrays
n = 0
m = 0
while n < left.length and m < right.length
if left[n] <= right [m]
result.add(left[n])
n = n + 1
else
result.add(right[m])
m = m + 1
result.add(left[n..left.length])//如果還有剩下,加入左邊剩余的部分
result.add(right[m..right.length])//如果右邊還有剩下,加入右邊部分
return result
但是,怎么體現遞歸呢?
這兒還得有一段
Merge-Sort(seq, p, r)
if seq.length <=1
return seq
middle = seq.length / 2
left = Merge-Sort(seq[1..middle])
right = Merge-Sort(seq[middle..seq.length])
return Merge-Sort(left, right)
Python實現
def merge(left, right):
result = []
n, m = 0, 0
while n < len(left) and m < len(right):
if left[n] <= right[m]:
result.append(left[n])
n += 1
else:
result.append(right[m])
m += 1
result += left[n:]
result += right[m:]
return result
def sort(seq):
if len(seq) <= 1:
return seq
middle = int(len(seq) / 2)
left = sort(seq[:middle])
right = sort(seq[middle:])
return merge(left, right)
源碼:Github-Syler-Fun-Merge-Sort-Python
Java實現
public static void sort(int[] seq, int left, int right) {
if (left < right) {
int middle = (left + right) / 2;
sort(seq, left, middle);
sort(seq, middle + 1, right);
merge(seq, left, right);
}
}
public static void merge(int[] seq, int l, int r) {
int mid = (l + r) / 2;
int i = l;
int j = mid + 1;
int count = 0;
int temp[] = new int[r - l + 1];
while (i <= mid && j <= r) {
if (seq[i] < seq[j]) {
temp[count++] = seq[i++];
} else {
temp[count++] = seq[j++];
}
}
while (i <= mid) {
temp[count++] = seq[i++];
}
while (j <= r) {
temp[count++] = seq[j++];
}
count = 0;
while (l <= r) {
seq[l++] = temp[count++];
}
}