《算法导论》笔记--归并排序 & 算法原理
归并排序
算法原理:
现在还是沿用牌堆的比喻:
一共两个操作:
1.分解:
现在我们将牌分为两堆,对其中的每一堆都进行 '分为两堆' 这个操作,直到单个堆的元素只有一个
此时每个牌堆都是有序的
2.合并
我们对每两个临近的牌堆(记为A、B)进行:
当前每个牌堆仅一张牌,我们比较A和B的大小,将其中较小的一张牌放在另一张的上面
以此类推,对34、56、78...号牌堆进行这个操作
这样,我们获得了若干个单个都有序的牌堆
接下来,我们继续取临近的两个牌堆(记为A、B),进行:
取出两堆顶上的一张,比较大小,将其中较小的放入手中,重复这个操作(如果此时手中有牌,那么放于上一张的下面),直到至少一堆已空
那么这时,一共会有3种情况:
1.两堆均空
2.A空
3.B空
对于1,我们继续合并34、56、78...号牌堆,重复这个操作
对于2、3,显然,此时剩余的所有元素一定比单独的这一堆中最大的都大,读者可以自己尝试证明一下(提示:反证法)
那么我们可以将剩余的所有元素都直接按序排在单独这一堆后
(严格证明见下文)
我们继续重复合并操作,直到只剩一堆,那么此时的牌堆就是有序的,排序完成
具体算法实现
我们规定,a为待排序数组,共有n个元素(0 to n-1)
l为目前区间的左端点,r为右端点,mid为中间点
其中mid = l+(r-l)/2 (至于为什么这么做,而不是简单的(l+r)/2,请看下文)
开始时,\(l = 0,r = n-1,mid = l+(r-l)/2\)
我们执行两个操作:
1.分解:令a分为[l,mid]、[mid+1,r]两段,对子段继续按此分解,直到l=r(即单一元素)
2.合并:
我们每次合并[l,mid]、[mid+1,r]两段,保证每段内部都是有序的
也就是说,我们需要一个合并函数,类似:merge(a,l,r,mid)
将[l,mid]记为L,[mid+1,r]记为R,再定义一个辅助数组tmp,用来保存合并完的数组,之后拷贝到数组a
令k为当前tmp的下标,开始时k=0
令i,j为当前L、R的下标,开始时为0
然后:如果L[i]>=R[j],那么tmp[k] = L[i],i--;
否则tmp[k] = R[j],j--;
重复直到至少其中一堆的top < 0(即堆中没有任何数);
随后,我们将剩余的所有元素拷贝至tmp的末尾
等到合并完的区间就等于a时,算法结束, a数组有序;
循环不变式证明
P.S. 这里的证明方式更通俗,《算法导论》给出了另外一种证明
由于分解这一步仅仅相当于分解问题为多个子问题,因此,我们不对这个阶段证明循环不变式
那么,我们列出合并阶段的循环不变式:
记住:在过程中,我们力图维护的,就是其循环不变式
那么,我们其实一直在维护的是tmp数组的有序性
所以,循环不变式为:
tmp[0..k]始终保存L[0..i]、R[0..j]中前k小的数
前提条件:子序列LR均有序
我们开始证明:
1.初始化: 此时tmp中什么也没有,因此符合循环不变式
2.保持:在算法过程中,我们有两个阶段:
- 一、在两个子序列均非空时,我们每次取L[i]、R[j]中较小的那一个
- 二、在至少一个为空时,我们将剩余的所有元素直接移动到tmp末尾
我们挨个证明, - 一、若取了\(L[i]、R[j]\)中较小的那一个导致tmp无序,那么说明有一个数\(M>min(L[i]、R[j])\)且在tmp中,而M一定来自L、R其中一个数组;
若M来自于\(min(L[i]、R[j])\)所在的数组,那么说明此数组无序,于前提不符,所以不存在这样的M,即tmp有序;
若M来自于\(max(L[i]、R[j])\)所在的数组,那么说明,在选择M的那一轮循环中,M为较小的,因此另一堆剩余的所有元素一定都大于M,M这一堆剩余的所有元素也一定都大于M
所以之后不会存在一个比M小的数\(min(L[i]、R[j])\)了,与前提不符,所以不存在这样的M; - 二、若这个操作导致tmp无序,那么在剩余这一堆中一定存在一个数Q,使得\(Q<tmp[k]\),那么,tmp[k]只可能来自L、R
如果和Q同堆,那么由于\(Q<tmp[k]\),说明此堆无序,所以不存在Q
若不同堆,那么说明这一堆剩余所有元素均大于tmp[k],则剩余的堆应该是这一堆,与假设不符;
\(Q.E.D\)
形式化的循环不变式证明:
形式化定义:
设 \(L[1..m]\) 和 \(R[1..p]\) 为有序数组,合并时不变式:
\(temp[1..k]\) 包含 \(L[1..i]\) 和 \(R[1..j]\) 中前 \(k\) 小元素且有序
数学归纳证明:
初始化:\(k=0, i=1, j=1\),空数组满足条件
保持:
若 \(L[i] \leq R[j]\),则 \(L[i]\) 是剩余元素最小者
由归纳假设 \(temp[1..k]\) 有序,追加 \(L[i]\) 仍有序
终止:\(i>m\) 或 \(j>p\) 时,剩余元素直接追加仍有序
为什么是mid = l+(r-l)/2
我们先尝试展开:
\(mid = l+(r-l)/2 = l+0.5r-0.5l = 0.5r+0.5l = 1/2(r+l)\);
与直接\(mid = (l+r)/2\)本质上是一致的
那么为什么还要变形呢
想这样一个问题:倘若r、l均取int类型的最大值的一半+1,\(mid = (l+r)/2\)会发生什么呢
显然,\(l+r\)超过了int类型的最大值,会导致溢出
综上,我们选择\(mid = l+(r-l)/2\);
Upt 2025.8.6
GhostSilver
《算法导论》笔记--归并排序 & 算法原理的更多相关文章
- 《算法导论》读书笔记之排序算法—Merge Sort 归并排序算法
自从打ACM以来也算是用归并排序了好久,现在就写一篇博客来介绍一下这个算法吧 :) 图片来自维基百科,显示了完整的归并排序过程.例如数组{38, 27, 43, 3, 9, 82, 10}. 在算法导 ...
- MIT算法导论笔记
详细MIT算法导论笔记 (网络链接) 第一讲:课程简介及算法分析 (Sheridan) 第二讲:渐近符号.递归及解法 (Sheridan) 第三讲:分治法(1)(Sheridan) 第四讲:快排及随 ...
- MIT公开课:算法导论 笔记(一)
课程链接:http://open.163.com/special/opencourse/algorithms.html 第一课:算法分析基础 1.介绍插入排序与归并排序,计算并比较最坏运行时间 2.算 ...
- 算法导论 - 基础知识 - 算法基础(插入排序&归并排序)
在<算法导论>一书中,插入排序作为一个例子是第一个出现在该书中的算法. 插入排序: 对于少量元素的排序,它是一个有效的算法. 插入排序的工作方式像许多人排序一手扑克牌.开始时,我们手中牌为 ...
- 《算法导论》归并排序----merge-sort
伪代码请见<算法导论>2.3节 merge-sort实现: public class MergeSort { public static void sort(double [ ...
- 算法导论笔记:18B树
磁盘作为辅存,它的容量要比内存大得多,但是速度也要慢许多,下面就是磁盘的的结构图: 磁盘驱动器由一个或多个盘片组成,它们以固定的速度绕着主轴旋转,数据存储于盘片的表面,磁盘驱动器通过磁臂末尾的磁头来读 ...
- 算法导论笔记——第二十章 van Emde Boas树
当关键字是有界范围内的整数时,能够规避Ω(lglgn)下界的限制,那么在类似的场景下,我们应弄清楚o(lgn)时间内是否可以完成优先队列的每个操作.在本章中,我们将看到:van Emde Boas树支 ...
- 算法导论笔记——第十八章 B树
18.1 B树的定义 18.2 B树的基本操作 与一棵二叉搜索树一样,可以在从树根到叶子这个单程向下过程中将一个新的关键字插入B树中.为了做到这一点,当沿着树向下查找新的关键字所属位置时,就分裂沿途 ...
- 算法导论学习-prim算法
一. 关于最小生成树 对于无向连通图G=(V,E),其中V表示图的顶点,E表示图的边,对于每条边都有一个权值,可以理解为边a->b的权值C为从a走到b要走的路程为C.现在我们希望找到一个无回路的 ...
- [算法导论]迪克斯特拉算法 @ Python
class Graph: def __init__(self): self.V = [] self.w = {} class Vertex: def __init__(self, x): self.k ...
随机推荐
- Kafka King 推荐一款漂亮、现代、实用的kafka客户端
Kafka King 一个漂亮.现代.实用的kafka客户端,使用python flet.flutter构建. Github主页:https://github.com/Bronya0/Kafka-Ki ...
- SynchronizedHashMap和ConcurrentHashMap的区别
ConcurrrentHashMap ConcurrentHashMap 使用锁分离技术来保证在多线程下的性能.它每次锁住一个桶,默认将 hash 表分为 16 个桶,诸如put和remove 等 ...
- python基础—集合
一.集合(数字,字符串,元组) 1.定义 由不同元素组成的集合,集合中是一组无序排列的哈希值,可以作为字典的key 2.特性 无序,不同元素组成,必须是不可变类型 3.set输出与去重 s=set(' ...
- Git镜像网站和Git网站提速方法
最近开始学习使用git,但是因为git是国外的网站,所以基本就是无法访问.如下图: 通过在网上查找资料,我发现了几个访问git的方法. 方法一.通过镜像网站 镜像网站一: https://github ...
- 题解:P1763 埃及分数
题目链接:link. 先放上代码,然后再讲解: #include<bits/stdc++.h> using namespace std; typedef long long ll; ll ...
- Kubernetes二进制文件下载链接
这里,我们下载kubernetes的二进制文件在其官网下载即可: 具体链接:https://kubernetes.io/docs/setup/release/notes/
- kards卡组推荐——苏美隐蔽
声明:此卡组抗快能力极差,害怕炮兵和小飞机为中后期 隐蔽核心思路: 在第一回合,尽量找杜斯团,找不到如果对方有单位,可以打一个镰刀 第二回合,①有杜斯团:打出隐蔽单位,按隐蔽顺序(附1)出,如果只有1 ...
- ArcObject SDK 015 出图
1.核心出图代码 出图主要是靠IExport接口,继承该接口的类如下图所示. 出不同格式的图,实例化不同的类即可.例如导出jpg格式的图片的代码如下. private void Export(stri ...
- UFT send email
- kubernetes源码解析
下载并且编译 git clone https://github.com/kubernetes/kubernetes.git cd kubernetes make cd _output 查看编译产物