递归排序核心
递归排序的核心是 分与合
分的最终结果 就是将原数组中每一个数字分作一个数组, 合就是 所有小数组不断排序,合并的过程。
合并的过程是先将两个含有一个数字的数组排序,合并(每次比较两个数组的最小值,将更小值加入新数组中,当有一个数组无元素,则将还有元素的数组追加到新数组中即完成排序,合并。新数组已是有序)为一个含有两个元素的有序数组。然后是两个含两个元素的有序数组排序合并为含四个元素的有序数组,两个含四个元素的有序数组合并为含八个元素的有序数组。一直到最终合并为一个数组即完成排序(当然其中有一个小数组可能不是含2的次方个数元素,但处理方式是一样的)。
为什么要用循环迭代实现
我在学归并排序时是先学的递归法实现,在看优化方法时有说到可以用循环迭代的方法实现效率会更高,然后就去了解循环迭代实现归并排序。
然而感觉能搜到的循环迭代法实现感觉不太能理解,至少我在有归并排序的理解在先的情况下看了十几分钟代码还不大懂其中过程。我就想能不能用比较容易理解的方式利用循环迭代实现归并排序。
然后就有了利用队列,用循环迭代的方法完成归并排序。该方法容易理解,也不必考虑些什么递归三要素。并且通过后文的简单的效率分析,能看出处理效率也很好。
循环迭代实现的整体思路
分,平时我们循环一个数组的时候,不就是不断取出其中每一个数字吗,这不是和归并排序中 分 的最终结果如出一辙吗。只要我们将循环中得到每一个数字做成一个含一个数字的数组加入队列中,即可完成分的过程。
合,只要队列中的元素不唯一(队列中每一个元素都是数组哦),不断从队首取出两个数组,排序合并成一个数组,加入到队尾。直至队列元素唯一,最终队列中的唯一数组即是排序好的数组。
搞清楚了整体思路,代码就呼之欲出了。
代码如下:
from _collections import deque
def que_merge(array):
merge_queue = deque()
# 开始分
# 此处是将原列表中元素pop出加入队列,循环结束后原列表会变成只剩一个数字。
# 若不想改变原列表用for循环将每一个数字变为列表加入队列即可。
while len(array) > 1:
# 列表弹出两个列表排序合并,这样进入队列的就直接是含两个数字的有序列表了。当然,不合并直接加入队列也是一样的,并不会影响合的过程。
one = array.pop()
other = array.pop()
if one < other:
merge_list = [one, other]
merge_queue.appendleft(merge_list)
else:
merge_list = [other, one]
merge_queue.appendleft(merge_list)
if array:
merge_queue.appendleft([array.pop()])
# 队列准备就绪,开始排序,合并
while len(merge_queue) > 1:
merge_list1 = merge_queue.pop()
merge_list2 = merge_queue.pop()
l = 0
r = 0
new_list = []
while l < len(merge_list1) and r < len(merge_list2):
if merge_list1[l] < merge_list2[r]:
new_list.append(merge_list1[l])
l += 1
else:
new_list.append(merge_list2[r])
r += 1
# 将两个列表剩余的元素追加到新列表中(有一个列表元素是空)。
new_list += merge_list1[l:]
new_list += merge_list2[r:]
merge_queue.appendleft(new_list)
# 最终队列中只剩已排序好的列表,pop出返回即可
return merge_queue.pop()
性能测试
用网上搜索得到的他人的循环实现的归并排序,命名为otherMergeSort(来自https://www.jianshu.com/p/bbba9b717f64
),自己写的递归实现的排序,队列循环实现的排序,快速排序进行简单的数量为百万级别的排序性能测试。结果如下。后面会贴出各排序代码及辅助函数的代码。
网上循环实现的归并排序用时 18.8429724 s
队列循环迭代归并排序用时 10.696805300000001 s
递归归并排序用时 10.5611496 s
快排用时 7.46601840000001 s
可以看出队列循环迭代实现的方法与递归排序实现的方法执行时间差不多,都比网上循环实现的方法好一点。然而前三个排序终归都还是归并排序,还是比不过快排呀。
循环实现与递归实现异同
在效率上是差不多的(不知道为什么别人说循环实现效率会高一点,在简单性能测试看来排序时间没多大的区别,或许还有优化的点我没找到?还是空间效率或其他问题呢?欢迎讨论)
在链表的归并排序上应该只能用循环实现的方法,因为链表无法快速取得中点来分得小数组。
优化杂思
在学习时有看到几个优化的点。递归实现的时候传入参数可以把数组切片变为起始点位置。我优化了之后用同款性能测试,性能并没有什么区别,并且代码更不易理解了。同样的,循环实现和递归实现的性能在上面的性能测试下也没什么区别。希望有大神来解一下惑。
最能优化的是在排序合并时用索引取出两列表排序的最小值来比较之后加入新列表,而不是直接pop(0)取出,因为列表pop(0)的时间复杂度是O(n),而用列表索引取是O(1)。这样优化后一百万数量的排序从60S变成10S。
性能测试用到的排序算法代码及辅助函数代码
简单性能测试代码
if __name__ == '__main__':
int_num = 1000000
last = int_num - 1
max = 1000000
try_num = 1
print('网上循环实现的归并排序用时', timeit.timeit("otherMergeSort({})".format(generateRandomArray(int_num, 0, max)),
setup="from __main__ import otherMergeSort",
number=try_num))
print('队列循环迭代归并排序用时', timeit.timeit("que_merge({})".format(generateRandomArray(int_num, 0, max)),
setup="from __main__ import que_merge",
number=try_num))
print('递归归并排序用时',timeit.timeit("my_emerge_sort({})".format(generateRandomArray(int_num, 0, max)),
setup="from __main__ import my_emerge_sort",
number=try_num))
print('快排用时',timeit.timeit("quick_sort({},{},{})".format(generateRandomArray(int_num, 0, int_num), 0, last),
setup="from __main__ import quick_sort",
number=try_num))
辅助函数代码
生成随机数组
def generateRandomArray(n, min, max):
arr = [randint(min, max,) for _ in range(n)]
return arr
网上循环实现的归并排序
def otherMergeSort(nums):
l = len(nums)
global nums_for_compare
nums_for_compare = list(range(l))
sz = 1
# sz = 1, 2, 4, 8
while sz < l:
# left = 0, 2, 4, 6
left = 0
while left < l - sz:
__merge_of_two_sorted_array(nums, left, left + sz - 1, min(left + sz + sz - 1, l - 1))
left += 2 * sz
sz *= 2
自己写的递归实现的归并排序
def my_emerge_sort(array):
length = len(array)
if length <= 1:
return array
left_list = my_emerge_sort(array[:length // 2])
right_list = my_emerge_sort(array[length // 2:length])
l, r = 0, 0
new_list = []
while l < len(left_list) and r < len(right_list):
if left_list[l] <= right_list[r]:
new_list.append(left_list[l])
l += 1
else:
new_list.append(right_list[r])
r += 1
new_list += left_list[l:]
new_list += right_list[r:]
return new_list
快速排序代码
def quick_sort(lists, i, j):
if i >= j:
return lists
pivot = lists[i]
low = i
high = j
while i < j:
while i < j and lists[j] >= pivot:
j -= 1
lists[i] = lists[j]
while i < j and lists[i] <= pivot:
i += 1
lists[j] = lists[i]
lists[j] = pivot
quick_sort(lists, low, i - 1)
quick_sort(lists, i + 1, high)
return lists