带你直观地感受归并排序的效率
写在前面
归并排序,即Merge Sort Algorithm,是一种高效,泛用、基于比较的排序算法。
归并排序算法是一种由计算机科学奠基人冯·诺依曼(John von Neumann)于1945年所发明的分治算法(Divide And Conquer Algorithm) [ 3 ] ^{[3]} [3]。
本文将延续上一篇:带你直观地感受二分搜索,利用python的图形库matplotlib与科学计算库scipy、numpy等软件包,用可视化的方法,呈现出不同量级数据规模下,归并排序的效率表现。
1. 算法描述
- 将输入的数组一分为二,分为左子集与右子集。
- 递归这个过程,直到子数组长度为1
- 合并子数组,创建一个临时数组,分别给两个子集一个指针,比较两个指针,把较小的*填入临时数组,剩余没有填入的,全部填入临时数组。
- 合并结束后,排序结束。
- 以下给出递归流程
- 更直观地表示这个过程
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:
这边的难点,是子集边界的判断以及偏移的计算
在做代码实现时,为了保持数组的稳定性(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()
实验结果
可以看到,归并排序的 图像约呈 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] 《数据结构与算法之美》 王争