排序算法:merge sort(python)

基本原理

merge sort就是用divide and conquer的方法来实现sort。
它将一个要倍排序的序列,分成两个已经排好序的序列,在将他们的合并起来。

在合并的时候,首先指针都在两个序列的最前端,然后比较大小,将符合的放入新的序列中,指针再后移,再进行相同的比较过程。

错误的第一次实现

错误示例!

#merge sort
def merge(L_l,L_r):
	i = 0
	j = 0
	L_new = []
	while i<len(L_l) and j<len(L_r):
		if L_l[i] > L_r[j]:
			L_new.append(L_l[i])
			i += 1
		elif L_l[i] < L_r[j]:
			L_new.append(L_r[j]) 
			j += 1
		else:
			L_new.append(L_r[j])
			L_new.append(L_l[i])
			i += 1
			j += 1
	print("L_new="+str(L_new))
	return L_new

def mergesort(L):
	print("lenl="+str(len(L)))
	if len(L) <= 1:
		return L
	else:
		mid = len(L)//2 
		print("mid="+str(mid))
		L_sort = L[:]
		lefthalf = L[:mid]
		righthalf = L[mid:]
		L_l = mergesort(lefthalf)
		print("L_l done")
		print("L_l="+str(L_l))
		# mid = len(L)//2
		print("mid2="+str(mid))
		# L_sort = L[:]
		L_r = mergesort(righthalf)
		L = merge(L_l,L_r)
		return L

L = [1,5,6,3,7,8]
mergesort(L)
print(L)

此方法错误,打印debug信息如下。

L(1)=[1, 5, 6, 3, 7, 8]
mid(1)=3
lefthalf(1)=[1, 5, 6]
righthalf(1)=[3, 7, 8]
L(2)=[1, 5, 6]
mid(2)=1
lefthalf(2)=[1]
righthalf(2)=[5, 6]
L(3)=[1]
L_l_after(2=[1]
L(3)=[5, 6]
mid(3)=1
lefthalf(3)=[5]
righthalf(3)=[6]
L(4)=[5]
L_l_after(3=[5]
L(4)=[6]
L_r_after(3=[6]
L_new=[6]
L_after(3=[6]
L_r_after(2=[6]
L_new=[6]
L_after(2=[6]
L_l_after(1=[6]
L(2)=[3, 7, 8]
mid(2)=1
lefthalf(2)=[3]
righthalf(2)=[7, 8]
L(3)=[3]
L_l_after(2=[3]
L(3)=[7, 8]
mid(3)=1
lefthalf(3)=[7]
righthalf(3)=[8]
L(4)=[7]
L_l_after(3=[7]
L(4)=[8]
L_r_after(3=[8]
L_new=[8]
L_after(3=[8]
L_r_after(2=[8]
L_new=[8]
L_after(2=[8]
L_r_after(1=[8]
L_new=[8]
L_after(1=[8]
[1, 5, 6, 3, 7, 8]

从这个里面可以看到,是merge出的问题,没有将两个全部加入。可以发现,是while循环没有做完出现的问题。因为前面的while结束后,可能还有剩余的部分没有加入,因此需要在写循环把之后的全部加入。

更改后的代码如下:

#merge sort

def merge(L_l,L_r):
	i = 0
	j = 0
	L_new = []
	while i<len(L_l) and j<len(L_r):
		if L_l[i] > L_r[j]:
			L_new.append(L_l[i])
			i += 1
		elif L_l[i] < L_r[j]:
			L_new.append(L_r[j]) 
			j += 1
		else:
			L_new.append(L_r[j])
			L_new.append(L_l[i])
			i += 1
			j += 1
	while i < len(L_l):
		L_new.append(L_l[i])
		i += 1
	while j < len(L_r):
		L_new.append(L_r[j])
		j += 1
	# print("L_new="+str(L_new))
	return L_new

def mergesort(L,count):
	count += 1
	# print("L("+str(count)+")="+str(L))
	if len(L) <= 1:
		return L
	else:
		mid = len(L)//2 
		# print("mid("+str(count)+")="+str(mid))
		# L_sort = L[:]
		lefthalf = L[:mid]
		righthalf = L[mid:]
		# print("lefthalf("+str(count)+")="+str(lefthalf))
		# print("righthalf("+str(count)+")="+str(righthalf))
		L_l = mergesort(lefthalf,count)
		# print("L_l_after("+str(count)+"="+str(L_l))
		L_r = mergesort(righthalf,count)
		# print("L_r_after("+str(count)+"="+str(L_r))
		L = merge(L_l,L_r)
		# print("L_after("+str(count)+"="+str(L))
		return L

count = 0 
L = [1,5,6,3,7,8]
L_new = mergesort(L,count)
print(L_new)

此刻可以返回正确的结果。

insert sort 与 merge sort的对比

insert sort比merge sort 的优点在与内存。如果想要完成merge sort,那么需要一个theta(n)的auxiliary space(辅助空间),但是in place sort 只需要theta(1)的auxiliary space。因为merge sort需要复制左右两个序列然后把新的序列存入。

为了解决这个问题,人们提出了in-place merge sort,但是running time会很差,为theta(n^2),因此并不是常用。

时间复杂度

为nlog(n),该过程可以用树来直观的看出。
由merge sort的原理列出式子为:
T(n) = 2T(n/2) + c*n
我们可以通过一个recurrence tree来直观的看出其复杂度。

在这里树里面,每级的叶子都有相同的量。

下面一次为延伸,来使用树计算一些其他的式子的时间复杂度。
例1: T(n) = 2T(n/2) + c

在这种情况下,叶子是主导作用,因此时间复杂度以此计算。

例2: T(n) = 2T(n/2) + c*n^2

在这种情况下,根起主导作用。

原文地址:https://www.cnblogs.com/litingyu/p/9199502.html