歸并排序的非遞歸實現
非遞歸的實現一直想不明白,直到看了這位大哥的博客,茅塞頓開。非常感謝這位大哥。博客地址:博客地址
遞歸實現是將復雜問題一步一步細分為簡單的子問題,直到最小子問題的時候,開始合并。
非遞歸剛好相反:從最小子問題開始一步一步解決,直到復雜的問題。
由圖片可知:
第一次:我們將數組分為 8個子數組 每個數組 1 個元素,對相鄰的兩個數組進行排序合并。
第二次:我們將數組分為 4個子數組 每個數組 2 個元素,對相鄰的兩個數組進行排序合并。
第三次:我們將數組分為 2個子數組 每個數組 4 個元素,對相鄰的兩個數組進行排序合并。
至此:排序完畢。
因此:
第一步:劃分每個子數組元素的個數(也就是子數組的長度)
1. 第一次 每個子數組元素 個數 為 1.
2. 第二次 每個子數組元素 個數 為 2.
3. 第三次 每個子數組元素 個數 為 4.
可以看出來 每個子數組元素個數 以2的倍數遞增
i = 1 #子數組長度
while i < len(seq):
i *= 2
第二步:對每個相鄰的數組進行排序合并。
假設 seq = [5,4,0,3,1,6,2,7]
首先:求得要合并的兩個相鄰數組的區間 [low:mid) [mid:height)
1. 當子數組長度為 1 的時候 要合并的相鄰兩個數組的區間為:
[0,1) [1,2) [2,3) [3,4) [4,5) [5,6) [6,7) [7,8) 子數組長度為 1
2. 子數組長度為 2 要合并的相鄰兩個數組的區間為:
[0,2) [2,4) [4,6) [6,8) 子數組長度為 2
3. 子數組長度為 4 要合并的相鄰兩個數組的區間為:
[0,4) [4,8) 子數組長度為 4
下面來求 區間中的 low mid height:
1. 當子數組為1的時候:
low mid height 分別等于:
0:1:2 i = 1 low = 0 mid = low + 1 height = low + 2
2:3:4 i = 1 low = 2 mid = low + 1 height = low + 2
4:5:6 i = 1 low = 4 mid = low + 1 height = low + 2
6:7:8 i = 1 low = 6 mid = low + 1 height = low + 2
抽象為: i = 1 low = low + 2 * i mid = low + i height = low + 2i
2. 當子數組為2的時候:
low mid height 分別等于:
0:2:4 i = 2 low = 0 mid = low + 2 height = low + 4
4:6:8 i = 2 low = 4 mid = low + 2 height = low + 4
抽象為: i = 2 low = low + 2 * i mid = low + i height = low + 2 * i
3. 當子數組為4的時候:
low mid height 分別等于:
0:4:8 i = 4 low = 0 mid = low + 4 height = low + 2 i
抽象為: i = 4 low = low + 2i mid = low + 4 height = low + 2i
綜上所述:抽象出求low mid height 通用公式:
low = low + 2 x i
mid = low + i
height = low + 2 x i
seq = [5, 4, 3, 0, 1, 2, 7, 5]
i = 1
while i < len(seq):
print '子數組 長度 : ',i
low = 0
while low < len(seq):
mid = low + i
height = low + 2*i
print 'low ',low,'mid:',mid,'height:',height
low += 2*i
i *= 2
運行結果如下:
我們此時的猜想,以及抽象出來的通用公式 在len(seq) == 2的N次方的時候是對的。
我們在換一組不同的數試試 非len(seq) == 2的N的數試試
seq = [5, 4, 3, 0, 1, 2, 7, 5, 11,9]
i = 1
while i < len(seq):
print '子數組 長度 : ',i
low = 0
while low < len(seq):
mid = low + i
height = low + 2*i
print 'low ',low,'mid:',mid,'height:',height
low += 2*i
i *= 2
運行結果如下:
這時候就出錯了。我們數組seq 一共才10個元素,height 已經達到了 12,16.mid 達到了 12.很明顯數組下標越界了。
我們需要做一些調整。
首先解決height 越界的問題。
當數組終結時候 height 最大達到了16,而我們的數組最大才10個元素。
所以我們 從 height 與 len(seq)中選擇一個最小的元素,來作為數組的右邊界。因為數組seq 長度是 10 ,height肯定不能超過 數組長度呀,不然就數組下標越界咯。
改版后的代碼如下:
seq = [5, 4, 3, 0, 1, 2, 7, 5, 11,9]
i = 1
while i < len(seq):
print '子數組 長度 : ',i
low = 0
while low < len(seq):
mid = low + i
height = min(low + 2 * i, len(seq))
print 'low ',low,'mid:',mid,'height:',height
low += 2*i
i *= 2
運行結果如下:
現在height的問題已經解決了。
現在解決 mid 的問題。
mid是用來將一個數組拆分為兩個部分的:[low,mid) [mid,height)
當mid>height的時候,很明顯嘛 [mid,height)已經沒有了,也就是說 數組已經不能再拆分了。不能拆分了我們就不拆分唄,直接pass,總有一天能輪到他,如下圖:
原諒我畫的丑·····
代碼如下:
seq = [5, 4, 3, 0, 1, 2, 7, 5, 11,9]
i = 1
while i < len(seq):
print '子數組 長度 : ',i
low = 0
while low < len(seq):
mid = low + i
height = min(low + 2 * i, len(seq))
if mid < height:
print 'low ',low,'mid:',mid,'height:',height
low += 2*i
i *= 2
運行結果如下:
這不 mid越界的問題 已經沒了。
len(seq) 為奇數的時候 同樣適用這樣的處理方式。
下面進行 合并排序。
合并與遞歸模式的合并是一樣的。
代碼如下:
def merge(seq,low,mid,height):
"""合并兩個已排序好的列表,產生一個新的已排序好的列表"""
# 通過low,mid height 將[low:mid) [mid:height)提取出來
left = seq[low:mid]
right = seq[mid:height]
print 'left:', left, 'right:', right
k = 0 #left的下標
j = 0 #right的下標
result = [] #保存本次排序好的內容
#將最小的元素依次添加到result數組中
while k < len(left) and j < len(right):
if left[k] <= right[j]:
result.append(left[k])
k += 1
else:
result.append(right[j])
j += 1
#將對比完后剩余的數組內容 添加到已排序好數組中
result += left[k:]
result += right[j:]
#將原始數組中[low:height)區間 替換為已經排序好的數組
seq[low:height] = result
print "seq:", seq
完整代碼如下:
seq = [5, 4, 3, 0, 1, 2, 7, 5, 11,9]
i = 1
while i < len(seq):
print '子數組 長度 : ',i
low = 0
while low < len(seq):
mid = low + i
height = min(low + 2 * i, len(seq))
if mid < height:
print 'low ',low,'mid:',mid,'height:',height
merge(seq,low,mid,height)
low += 2*i
i *= 2
運行結果如下: