12.1 归并排序
排序算法分为两大类:比较排序和分布排序
比较排序(comparsion sort):数据可以通过排序关键字的逻辑关系比较来实现升序或降序排列。这种逻辑关系比较基于数字大小或字母顺序。
分布排序(distribution sort):将所有排序关键字分成基于每一个关键字的中间组。
归并排序(merge sort):使用分治法来排列关键字,并将之储存在可变序列中。
12.1.1 算法描述
该算法将原列表从中间分割成两个子列表,两个子列表的元素个数相近,重复该过程,直至不能再分割为止。此时,列表分成一个个只含一个元素的子列表。此时,每两个子列表按照大小排列,重新组合成一个子列表,重复该过程,直至重新组成一个原列表的排序列表为止。
12.1.2 基本实现
上述将列表分割,以及将已排序的子列表合并的过程可以使用递归来实现。
#-*-coding: utf-8-*-
# 归并排序的基本实现
def pythonMergeSort(theList):
if len(theList) <= 1:
return theList
else:
mid = len(theList) / 2 # 计算中点
leftHalf = pythonMergeSort(theList[:mid])
rightHalf = pythonMergeSort(theList[mid:])
newList = mergeOrderedLists(leftHalf, rightHalf)
return newList
但这一实现还是几个缺陷,首先是它依赖于切片操作,但是如果是对数组来进行排序就不可行了。其次,就是随着列表的分割,在每一次递归调用,都有新列表结构创建,而这是相当低效的。最后就是新的排序列表,是创建出来的,并不是在原列表修改而来。
12.1.3 改进的实现
我们使用虚拟子组(virtual sublists)来实现归并排序。虚拟子组实际上就是对每次分割的两个子组的第一个元素在原列表的索引进行标记,这样就可以对原列表进行操作。
#-*-coding: utf-8-*-
# 使用虚拟子列来实现归并排序
def recMergeSort(theSeq, first, last, tmpArray):
if first == last:
return
else:
mid = (first + last) / 2
# 分隔序列,此步开始递归
recMergeSort(theSeq, first, mid, tmpArray)
recMergeSort(theSeq, mid, last, tmpArray)
# 合并已排序序列
mergeVirtualSeq(theSeq, first, mid+1, last+1, tmpArray) #
def mergeVirtualSeq(theSeq, left, right, end, tmpArray):
a = left # 序列前半部分指针,从left开始
b = right # 序列后半部分指针,从right开始
m = 0 # 临时数组索引
# 以下过程类似于合并排序列表
while a < right and b < end:
if theSeq[a] < theSeq[b]:
tmpArray[m] = theSeq[a]
a += 1
else:
tmpArray[m] = theSeq[b]
b += 1
m += 1
while a < right:
tmpArray[m] = theSeq[a]
a += 1
m += 1
while b < end:
tmpArray[m] = theSeq[b]
b += 1
m += 1
# 将临时数组复制到原序列中
for i in range(end-left):
theSeq[i + left] = tmpArray[i]
这一实现需要使用临时数组来储存每一次合并已排序列表完成后的列表。
#-*-coding: utf-8-*-
# 归并排序的包装函数
def mergeSort(theSeq):
n = len(theSeq)
tmpArray = Array(n)
recMergeSort(theSeq, 0, n-1, tmpArray)
12.1.4 效率分析
归并算法的时间复杂度是O(n log n)。
我们先从recMergeSort()函数开始,计算中点需要O(1),再加上计算各个子列表计算中点的次数,总共是n - 1次,即O(n),而之后的合并排序列表的时间复杂度是O(n),n是新合并列表的元素个数。因为每一层的元素总个数与原列表一致,所以每一层所有合并列表的操作,总的时间复杂度是O(n)。总共有log n层,因此执行log n次,所以,总的时间复杂度是O(n log n)。