带你直观地感受归并排序的效率

带你直观地感受归并排序的效率

写在前面

归并排序,即Merge Sort Algorithm,是一种高效,泛用、基于比较的排序算法。

归并排序算法是一种由计算机科学奠基人冯·诺依曼(John von Neumann)于1945年所发明的分治算法(Divide And Conquer Algorithm) [ 3 ] ^{[3]} [3]

本文将延续上一篇:带你直观地感受二分搜索,利用python的图形库matplotlib与科学计算库scipy、numpy等软件包,用可视化的方法,呈现出不同量级数据规模下,归并排序的效率表现。

1. 算法描述

  1. 将输入的数组一分为二,分为左子集与右子集。
  2. 递归这个过程,直到子数组长度为1
  3. 合并子数组,创建一个临时数组,分别给两个子集一个指针,比较两个指针,把较小的*填入临时数组,剩余没有填入的,全部填入临时数组。
  4. 合并结束后,排序结束。
  • 以下给出递归流程
Created with Raphaël 2.2.0 开始 是否长度为1 返回 合并成有序数组 将数组一分为二 yes no
  • 更直观地表示这个过程

在这里插入图片描述

2. 实现

2.2 核心代码

def merge(arr, l, m, r): 
    n1 = m - l + 1
    n2 = r- m 
  
    # create temp arrays 
    L = [0] * (n1) 
    R = [0] * (n2) 
  
    # Copy data to temp arrays L and R
    for i in range(0 , n1): 
        L[i] = arr[l + i] 
  
    for j in range(0 , n2): 
        R[j] = arr[m + 1 + j] 
  
    # Merge the temp arrays back into arr[l..r] 
    i = 0    
    j = 0    
    k = l   
  
    while i < n1 and j < n2 : 
        if L[i] <= R[j]: 
            arr[k] = L[i] 
            i += 1
        else: 
            arr[k] = R[j] 
            j += 1
        k += 1
  
    # Copy the remaining elements of L[]
    while i < n1: 
        arr[k] = L[i] 
        i += 1
        k += 1
  
    # Copy the remaining elements of R[]
   
    while j < n2: 
        arr[k] = R[j] 
        j += 1
        k += 1

# sub-array of arr to be sorted 
def mergeSort(arr,l,r): 
    if l < r: 
        m = (l+(r-1))//2
  
        mergeSort(arr, l, m) 
        mergeSort(arr, m+1, r) 
        merge(arr, l, m, r) 
  

Tips:

  1. 这边的难点,是子集边界的判断以及偏移的计算

  2. 在做代码实现时,为了保持数组的稳定性(stability),要把处于原本处在前面的元素放在结果序列的前面

2.3 实验代码

产生一个无序随机的数组
# generate a random sorted array
def generateArray(n):
    A = np.random.randint(0,n,size=n)
    return A

实验选取不同数量级、每次实验100次

# 结果存入数组
size=10000000
log_size=int(math.log(size,10))
n_time=100

print(log_size)
rs = np.zeros([log_size,n_time])


i =1
m = 0

while i<size and m<log_size:
    #i:数据规模
    #确定一个目标值
    
    k = np.random.randint(0,i)
    print(i)
    # j:测试次数(每个数据规模测100次,暂时)
    for j in range(n_time):
        A = generateArray(i)

        # profile CPU time
        t1 = float(t.time())
        
        mergeSort(A,0,len(A)-1)
        
        t2 = float(t.time())
        
        delta_time = t2 - t1
        rs[m][j]= delta_time
    i=i * 10
    m = m+1

print(rs)

拟合

# nlogn对数拟合
def func(x,a,b):
    print(x)
    y = np.multiply(a,x*np.log(x))+b
    return y

具体的拟合方法在上一篇二分查找的效率实验中已说明

# draw the graph
x = np.arange(log_size)
y = rs

# 行:数据规模   列:实验次数
df = pd.DataFrame(y,index=x,columns=np.arange(n_time))

df["mean"] = df.mean(axis=1)
df['var'] =  df.std(axis=1)

fig = plt.figure()

x =[1,1e1,1e2,1e3,1e4,1e5,1e6]    
    
#x =[1,1e1,1e2]    

y = df["mean"]

popt, pcov = curve_fit(func,x,y)
         
plt.errorbar(x,func(x,popt[0],0)*1e-6,yerr=df["var"]*4,fmt='r:',\
               label='proformance in differenct data sizes')

plt.savefig("merge.png",type="png")
plt.legend()

实验结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jqu2XdNt-1603107625058)(/Users/alex/Desktop/CSDN/blogs/Algo/MergeSort/image/merge.png)]

可以看到,归并排序的 图像约呈 n l o g ( n ) nlog(n) nlog(n) 的曲线形状

3. 总结

归并排序作为一种 O ( n l o g n ) O(nlogn) O(nlogn) 的高效排序算法,其效率量级确实要高于 O ( n 2 ) O(n^2) O(n2) 的算法,并且可以保持序列的稳定性

然而,对比另一种 O ( n l o g n ) O(nlogn) O(nlogn)的排序方式:快速排序算法(Quick Sort Algorithm)其效率在实际情况下往往高于归并排序(虽然快速排序牺牲了稳定性)。

这是由于归并排序,由于涉及到递归,往往需要额外的函数栈空间,还需要额外在子函数内创建数组以归并,这就提高了其空间复杂度,也就是 O ( n l o g n ) O(nlogn) O(nlogn)

4. 参考资料

[1]《Design and Analysis of Algorithm Using Python》 程振波等著 清华大学出版社

[2] 《数据结构与算法之美》 王争

[3] wikipedia “Merge Sort”

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值