1. 程式人生 > >歸併排序(MergeSort)詳解和動畫

歸併排序(MergeSort)詳解和動畫

歸併排序演算法思想:將陣列不斷二分得到子陣列,知道子陣列長度為1(自然是排序好的),對左子陣列和右子陣列分別排序,

子陣列長度為1,2,4....,

子陣列排序使用了一個輔助陣列

def exchange(arr,i,j):
    temp=arr[i]
    arr[i]=arr[j]
    arr[j]=temp

#歸併需要一個額外陣列存放歸併前的子陣列排序狀態
def merge(arr,start,mid,end):
    i=start
    j=mid+1
    aux[start:end+1]=arr[start:end+1]
    for idx in range(start,end+1):
        if i>mid:
            arr[idx] = aux[j]
            j += 1
        elif j > end:
            arr[idx] = aux[i]
            i += 1
        elif aux[j] < aux[i]:
            arr[idx] = aux[j]
            j += 1
        else:
            arr[idx] = aux[i]
            i += 1
        record.append(arr.copy())#動畫程式碼

對於i,j下標指標的邊界條件判斷和他們對應的陣列值的比較是這段程式碼的關鍵!

下面是遞迴二分(自頂向下然後再回溯)的過程

def sortUB(arr,start,end):
    if start>=end:
        return
    mid=start+(end-start)//2
    sortUB(arr,start,mid)
    sortUB(arr,mid+1,end)
    merge(arr,start,mid,end)

時間複雜度:O(nlogn)

空間複雜度:O(n)

下面看一個非常tricky的自底向上的歸併排序,就是利用陣列的索引的變化規律來重排序:

對於特定長度(size)的子陣列,第一個元素和最後一個元素的索引為:idx,idx+2*size+1

def sortBU(arr):
    n = len(arr)
    size = 1
    while size < n:
        idx = 0
        while idx < n - size:
            start=idx
            mid=idx + size - 1
            end=min(idx + 2 * size - 1,n-1)
            merge(arr,start,mid,end)
            idx += 2*size
        size *= 2

索引變化如下圖:

  

動畫程式碼如下:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.path as path
import matplotlib.animation as animation

record=[]
n=100
data=np.random.randint(0,100,n)
record.append(data.copy())
aux=data.copy()
sortBU(data)
bins=np.arange(n+1)
left = np.array(bins[:-1])
right = np.array(bins[1:])
bottom = np.zeros(len(left))
top = bottom + data
nrects = len(left)

# here comes the tricky part -- we have to set up the vertex and path
# codes arrays using moveto, lineto and closepoly

# for each rect: 1 for the MOVETO, 3 for the LINETO, 1 for the
# CLOSEPOLY; the vert for the closepoly is ignored but we still need
# it to keep the codes aligned with the vertices
nverts = nrects*(1 + 3 + 1)
verts = np.zeros((nverts, 2))
codes = np.ones(nverts, int) * path.Path.LINETO
codes[0::5] = path.Path.MOVETO
codes[4::5] = path.Path.CLOSEPOLY
verts[0::5, 0] = left
verts[0::5, 1] = bottom
verts[1::5, 0] = left
verts[1::5, 1] = top
verts[2::5, 0] = right
verts[2::5, 1] = top
verts[3::5, 0] = right
verts[3::5, 1] = bottom

fig, ax = plt.subplots()
barpath = path.Path(verts, codes)
patch = patches.PathPatch(
    barpath, facecolor='green', edgecolor='yellow', alpha=0.5)
ax.add_patch(patch)
ax.set_xlim(left[0], right[-1])
ax.set_ylim(bottom.min(), top.max())

def animate(i):
    # simulate new data coming in
    top = bottom + record[i]
    verts[1::5, 1] = top
    verts[2::5, 1] = top
    return [patch, ]

ani = animation.FuncAnimation(fig, animate, len(record), interval=10,repeat=False, blit=True)
plt.show()