算法详解(卷一)——MergeSort算法(Python)

使用书籍——算法详解

出于转统计学的缘故,需要良好的算法基础,所以在朋友推荐下,选择《算法详解》系列图书入门,全套4卷。这套书是Tim Roughgarden教授在其在线算法课程的基础之上编写的,在线课程Coursera上可以找到。(系列书籍上的所有算法用伪代码描述,代码部分我用Python语言描述)

算法详解(卷一)内容概述

系列图书的首卷涵盖四个主题:

  1. 渐进分析和大O表示法
  2. 分治算法和主方法
  3. 随机化算法
  4. 排序和选择

MergeSort算法(归并排序)

书中第四页提到——作为一个立竿见影的试验,可以试着阅读分析一下1.4节的MergeSort算法,如果搞得定,本书会比较轻松。
所以,作为可能存在的拦路虎,来具体分析一下这个老而弥坚的排序算法。

问题描述

输入:一个包含n个数的数组,以任意顺序排列。
输出:一个与输入数组相同元素的数组,但以从小到大的顺序排列。

Example

输入:[5, 4, 1, 8, 7, 2, 6, 3]
输出:[1, 2, 3, 4, 5, 6, 7, 8]

问题分析

归并算法,关键在归并两个字,顾名思义——将两个数组合并为一个大的数组。自然而然地,我们会想到,既然有数组合并的步骤,那么一定也有数组拆分的步骤。

从一个简单的例子展开分析:
我们知道,如果输入一个长度小于等于1的数组,它被认为是一个排序好了的数组,可以直接返回该输入的数组;那么,将输入的数组变得复杂些,我们该怎么利用上面提到拆分+合并的思想进行排序呢?

复杂一点的例子:如何排序[2, 1]这样长度为2的数组?

既然要拆分[2, 1],没有办法,只能将它拆成[2,]和[1,]两个数组(有些人说拆成[2, 1]和一个空数组,不开玩笑,我们不考虑做无用功),这时我们发现,两个数组的长度正好都是1嘛(两数组均已排好序),所以我们可以直接比较2和1的大小,并将这两个数组合并为[1, 2],即完成排序。

注意一个小细节,归并[2,]和[1,]这一过程操作的是两个已完成排序的数组。根据刚刚所讲的内容,我们现在可以把归并的函数写出来了。

归并函数的设计

归并的思路比较简单,大体上就是创建一个空列表sorted,比较传入归并函数的两有序列表的第一个数,利用pop方法将较小的一个数放入sorted,并及时在该数所在的数组中删除该数,这同时也更新了该列表的第一个数字。(需要注意的是,存在某列表被取空而另一列表仍有数的情况,此时无法进行数组首数字的比较。)

归并函数代码如下:

# sorted_div_1 and sorted_div_2 are sorted array
def merge(sorted_div_1, sorted_div_2):
	sorted = []
	while sorted_div_1 and sorted_div_2:
		if sorted_div_1[0] <= sorted_div_2[0]:
			sorted.append(sorted_div_1.pop(0))
		else:
			sorted.append(sorted_div_2.pop(0))
	sorted.extend(sorted_div_1)
	sorted.extend(sorted_div_2)
	return sorted

归并的工作完成了,拆分如何解决?

我们再看到上面的例子——两个长度为1的数组可以归并为一个长度为2的数组,两个长度为2的数组可以归并为一个长度为4的数组,等等。

所以,我们只需要将输入的数组拆解直至出现长度为1的小数组,后面的工作交给归并函数即可。这种将大问题拆解为多个子问题处理,以递归的方式处理子问题,并组合处理完的子问题得到答案的抽象方法就是算法中的重要思想——分治。

——再谈一谈拆解方式

最具效率的拆解方式也就是对半拆分,拆解过程图解如下(这里直接从百度图片里面找了一张**-.-**):

其中,(a、b)为拆解,(c、d)为归并。

简单分析一下这个过程,要对[7, 5, 6, 4]进行排序,就要拆解为[7, 5]和[6, 4]并归并,但两者均未排序,于是向下分别拆解为[7]-[5]和[6]-[4]并归并。通过递归拆解并归并的手段处理原数组。

当然,这张图片描述的是数组长度为偶数的情况,有朋友可能会想到奇数长度的数组如何考虑呢?其实是一样的,只是拆解的时候一个数组比另外一个数组多一个元素。

拆解函数如下:

def div(list_):
	# terminal condition
	if len(list_) <= 1:
    	return list_
    mid = len(list_)//2
    div_1 = div(list_[:mid])
    div_2 = div(list_[mid:])
    list_ = sort_result(div_1, div_2)
    return list_

如此一来,主要的两个函数部分的代码分析并写完了。最后可以加入数组输入函数用来测试,全部代码如下。

代码

# coding = utf-8


# input
def list_input():
    list_ = list(map(int, input().split()))
    print(list_)
    return list_


def merge(sorted_div_1, sorted_div_2):
	sorted = []
	while sorted_div_1 and sorted_div_2:
		if sorted_div_1[0] <= sorted_div_2[0]:
			sorted.append(sorted_div_1.pop(0))
		else:
			sorted.append(sorted_div_2.pop(0))
	sorted.extend(sorted_div_1)
	sorted.extend(sorted_div_2)
	return sorted


def div(list_):
	# terminal condition
	if len(list_) <= 1:
    	return list_
    mid = len(list_)//2
    div_1 = div(list_[:mid])
    div_2 = div(list_[mid:])
    list_ = sort_result(div_1, div_2)
    return list_


if __name__ == "__main__":
    input_list = list_input()
    print(div(input_list))

写在最后

第一次写博客,若有不妥,希望得到大家指正。后期会继续发布相关书籍的读书笔记以及自己的一些刷题经历,希望能和大家一起学习。

猜你喜欢

转载自blog.csdn.net/qq_39594496/article/details/89338792
今日推荐