算法详解阅读笔记
使用书籍——算法详解
出于转统计学的缘故,需要良好的算法基础,所以在朋友推荐下,选择《算法详解》系列图书入门,全套4卷。这套书是Tim Roughgarden教授在其在线算法课程的基础之上编写的,在线课程Coursera上可以找到。(系列书籍上的所有算法用伪代码描述,代码部分我用Python语言描述)
算法详解(卷一)内容概述
系列图书的首卷涵盖四个主题:
- 渐进分析和大O表示法
- 分治算法和主方法
- 随机化算法
- 排序和选择
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))
写在最后
第一次写博客,若有不妥,希望得到大家指正。后期会继续发布相关书籍的读书笔记以及自己的一些刷题经历,希望能和大家一起学习。