算法导论笔记<1>
第一章 基础知识
略过
第二章 算法基础
2.1 插入排序
- 书中提到了循环不变式, 定义入下:
初始化:循环在某一次开始之前,它为真。
保持:如果循环的某次迭代之前它为真,那么下次迭代之前仍为真
终止:再循环终止时,不变式为我们提供一个有用的性质,该性质有助于证明算法是正确的。
这种方法类似于数学归纳法,只不过,数学归纳法是无限迭代的,而该方法在for或者while语句停止时,归纳停止,运用这种方法,可以帮我们有条理的证明某算法在for或while循环上正确完备的达到了我们的期望。
2.2 分析算法
算法的复杂度通常有以下几种情况:
- 最坏情况:对于任何规模为n的输入,算法运行的最长时间,通常用来衡量算法的优劣
- 最好情况:一般不会用到
- 平均情况:在特定情况下,我们对其感兴趣,通常我们用概率分析技术来求平均情矿的复杂度
一般衡量一个算法的复杂度,我们并不对其详细的多项式的运算次数感兴趣,我们更关注的是其增长量级,简单来说,当输入规模增长 N 倍时,算法的运行次数是呈N , logN , N2 增长
2.3 设计算法
通常算法设计的技巧有:
- 分治法
- 贪心法
- 动态规划
回溯法
在这里,作者简单的介绍了一下分治法:- 分解:将原问题分解为若干子问题,这些子问题是原问题的规模较小的实例
- 解决:解决这些子问题,通常会将这些子问题分解的够小,可以直接求解
- 合并:将这些子问题的解归并还原成原问题的解
并以归并排序举例
在归并排序中:- 分解:将待排序的
N
个元素分解成各具
N/2 个元素的子序列 - 解决:使用归并排序递归的排序两个子序列
- 合并:合并两个已经排序好的两个子序列
若我们用一个无限大的元素置于序列末,有助于简化序列的合并的代码,通常我们称这种功能的标记叫做哨兵
通过分析,我们不难退出归并排序拥有 NlogN 的时间界,相比于插入排序和冒泡和选择排序为何归并排序更好的性能呢?
个人见解(以下就是在一本正经的扯淡)
为达成同一目标不同算法之间的时间和空间差异并非是凭空产生的,算法的时间优化往往来自于以下三个方面:
- 空间与时间的取舍
- 不同操作之间的分摊
- 对元素本身的利用
对于第一种情况,我们完全可以从一种算法思想方法上看到,他就是大名鼎鼎的动态规划
而对于第二种情况,我们可以从并查集身上看到,我们可以选择Find()操作为 O(1) 而使Union()操作为 O(N2)
第三种 在各种排序算法上体现的尤为明显 其中最明显的就是桶排,它利用了作为自然数的自然排序,将已知范围的自然数排序降低到了 O(N) 的程度。
言归正传
归并排序利用了排序本身的什么特性才使其降低到 NlogN 的程度呢?
答案也很简答,当A大于B,B大于C,那么我们无需比较就可以的出A大于C,那么在归并排序中哪里用到了这一点呢 ?
答案是归并,也是归并排序名称的来源。
将设有字序列
A1A2A3A4...
,和
B1B2B3B4....
根据归并排序的性质我们可以的出,
A1<A2<A3<A4....
对于
B1,B2,B3...
也是同理,那么此时我们只要比较
A1
与
B1
我们就可以确定
A1
为最大(最小)的元素,此时我们又可以将剩下的
A2,A3,A4...
看做一个新的
A1,A2,A3..
的问题,直至一方序列为空,我们可以发先对于已排序的序列,我们只用
O(1)
的复杂度就得出了序列中最大(最小)的元素.
那么我们回头来看看哪三种低效的排序算法呢:
冒泡排序,虽然我们花了很大力气(相当多的swap())得出他的最大值,但是其中的潜在蕴含的大小关系并没有被我们利用到,对于插入排序也是同样的结果,我们虽然选出了最大值,但对于下一个最大值的选取并没有丝毫的帮助.那么插入排序呢?
有,我们可以用二分查找利用这种关系,但是然后呢?我们还是得将后边的元素移到后边去,希尔排序和堆排序乃至归并排序都各自找出了一套解决方案(其实他们本质是相同的),于是他们成了 NlogN 的算法。
当然,以上只是作者个人的胡扯罢了,各种奇妙的排序方案岂能是小小的一片文章可以谈尽的.