分而治之 by邓俊辉老师 主定理 最大子序和 归并排序 众数 linearselect diameter cloest pair

分而治之divide and conquer DAC

将一个问题分成两个子问题。
两个问题的规模相当、两个子问题加起来的规模不能比原规模大。最好两个子问题不相关。
如果两个子问题耦合性太强那么可以考虑动态规划(DP)。

分而治之不得不利用递归。
减而治之,也可以描述为递归,但是若用递归实现,会出现爆栈的问题,一般不用递归描述。

分而治之的算法性能的描述:
Master Theorem 主定理、大师定理: T(n) = a*T(n/b) + O(f(n))
规模为n的问题,分成a个子问题,每个子问题的规模n/b.
第一步任务分开divide和任务合并merge所需的时间复杂度-f(n)

给定 a、b、f(n) 能够确定复杂度:
a为子任务数目,b为子任务的问题规模,f(n)为子问题分合的时间
一般三种情况。取决于logba b为底

主要比较 log ⁡ b a \log _{b} a logba与f(n)的复杂度关系 b暗示base基底
T(n)<cf(n) 严格小于 那么是small o
如果是小于等于那么是big o
kd-tree搜索算法 其复杂度为 O ( n ) \mathcal{O}(\sqrt{n}) O(n )
Θ ( n log ⁡ b a ) \Theta\left(n^{\log _{b} a}\right) Θ(nlogba) 指与 n log ⁡ b a n^{\log _{b} a} nlogba 严格相等
binary search 二分查找 mergesort归并排序
ω ( n log ⁡ b a ) \omega\left(n^{\log _{b} a}\right) ω(nlogba) 指比 n log ⁡ b a n^{\log _{b} a} nlogba

计算大的那个复杂度,相等的话就相乘
在这里插入图片描述

二分的中点问题
对于整数 x,若 x >= 0,则 x / 2 和 x >> 1 没有任何区别;
若 x < 0,则 x / 2 是下取整(比如 -19 / 2 == -9), 而 x >> 1 是上取整((-19 >> 1) == -10

greatest slice: 最大子序和

https://leetcode-cn.com/problems/maximum-subarray/?utm_source=LCUS&utm_medium=ip_redirect&utm_campaign=transfer2china
对于任给的整数区间,找出其中总和最大的区段。//有多个时,短者优先。
法一:暴力搜索:二重循环。时间复杂度。计和是还要嵌套一个for循环。三重循环。O(n^3)

int gs_BF(int A[], int n){ // 蛮力策略O(n^3)
	int gs = A[0];
	for(int i = 0; i<n; i++)
		for(int j=i; j<n; j++){ //枚举所有的区段
			int s=0; 
			for(int k=i; k<=j; k++)   //再用O(n)时间求和 i到j  s-sum
				s += A[k];    
			if( gs < s ) gs =s;  //择优、更新
		}
		return gs;
}

法二:求和,利用递增的总和,那么常数次时间就可以得到下一次总和。但是依然有两重循环O(n^2)

int gs_IC(int A[], int n){ // incremental strategy  
	int gs = A[0]; //当前已知的最大值
	for(int i = 0; i<n; i++){
		int s=0; 
		for(int j=i; j<n; j++){ //枚举所有的区段
			s += A[j];  //递增得到其总和
			if( gs < s ) gs =s;  //择优、更新
		}
	}
	return gs;
}

法三:分而治之:
将计算范围转化 A[lo,hi)—— [lo,mi) [mi,hi)
但是存在三种情况,属于一边、跨界。从左边找greatslice 从右边找greatslice ,跨界找greatslice
一遍的话可以找
跨界的话寻找最大,以mi为起点或者终点的最大区间,在中间进行家和。
2*T(n/2)+O(n) ——> 复杂度优化至O(nlogn)

int gs_DC(int A[], int lo, int hi){ //divide and conquer O(n*logn) 
	if( hi-lo < 2) return A[lo]; //递归基
	int mi = (lo+hi)/2; //在中点切分
	int gsL = A[mi-1], sL = 0, i = mi; //枚举
	while(lo<i--)	//所有 [i,mi)类区段
		if( gsL < (sL+=A[i])) 
			gsL = sL; //择优、更新
	int gsR = A[mi], sR = 0, j=mi-1; //枚举
	while(++j<hi)	//所有 [mi,j)类区段
		if( gsR < (sR+=A[j]))
			gsR = sR;
	return max(gsL+gsR, max(gs_DC(A,lo,mi),gs_DC(A,mi,hi))); //递归
}

法四:减而治之
复杂度减小到O(n)
总和非正的最短后缀S(k,hi),必定与 最大子串GS(lo,hi)=A[i,j)无交。
可以利用反证法证明。
若相交,则相交的部分,必定为正的,不然GS不成立
——> 非相交的总和非正最短后缀S(k,hi)部分为负数 与最短相矛盾

因此此法为:从末尾开始逐一查找,逐步减除非负的后缀

int gs_LS(int A[], int n){ //linear scan O(n)
	int gs= A[0], s=0, i=n, j=n;
	while(0<i--){ //对于当前区间[i,j)
		s += A[i];  //递增得到其总和 O(1)
		if(gs<s) gs=s;		//择优更新
		if(s<0) {s=0;j=i;}	//减除负和后缀
	} 
	return gs;
}

乘法 multiplication : Naive + DAC

n位数与n位数相乘—直接相乘的话 复杂度为O(n^2)
因为每一个组合都要做一次个位数的乘法。一共有n^2个组合
将n位数的变为 n/2与n/2 位。 将他们加起来。(ab)*(cd) =ac00+bd+(bd+ad)0
其时间复杂度:T(n) = 4T(n/2) + O(n) = O(n^2)
加入一个不等式 bc+ad = ac + bd - (a-b)(c-d) 减少1次乘法 因为ac.bd之前都已经算过了
主定理的a由4变为3.
T(n) = 3T(n/2) + O(n) = O(n^1.6)

归并排序 mergesort :DAC

reduce by division & merge
先分为两个部分,逐步分,分到递归基只有一个。再把两个有序的序列得到整体有序的序列

template<typename T>
void Vector<T>::mergeSort(Rank lo, Rank hi){//[lo,hi)
	if(hi-lo < 2) return;
	int mi = (lo+hi) >>1; //以中点为界
	mergeSort(lo,mi);//对前半段排序
	mergeSort(mi,hi);//对后半段排序
	merge(lo,mi,hi);//归并
}

二路归并—2-way Merge。
选取出两个队列最小的元素(这两个队列已经是有序的了),进入一个整体有序的新序列中。这种只需要线性的时间就可以了。
T(n) = 2T(n/2) + O(n) = O(nlogn) 历史上第一个达到此复杂度的算法。

insertion sort 插入排序 ——最好情况全为顺序O(n);最坏情况全部逆序 O(n^2)
selection sort 选择排序O(n^2)

衡量插入排序:逆序对
逆序对 inversion = I 逆序对的数目直接影响插入排序的性能。
每个元素上逆序的数目求和,即为所有逆序对数目。I
查找与挪动。O(n+I) n为查找的时间。I为逆序对插入所需的时间。
输入敏感性。与输入序列有关。

统计逆序对I数量

在lo与hi之间的inversion有多少。
利用分而治之的思想。
每一半统计inversion Il(inversion left) Ir(inversion right);
merge时,统计缺少跨越两部分的inversion。再加上原本左右有的inversion数量

如何统计跨越二者的inversion。
改造mergesort:在merge的过程中附加。
mergesort即把一个有序的子序列与另一个有序的子序列如何合二为一,注意在头上,摘走最小的。即two-way merge。
附加一步统计 merge inversion 。 LL(有序) LR RL RR I+=|LR|

其复杂度仍为O(nlogn) ,即相当于做了两次mergesort

众数 majority 减而治之

集合中有一半以上的元素同为m,则称之为众数(主流数)。
法一:哈希表
法二:减而治之:
在偶数个前缀中,若某元素x出现的次数好为一半。而且后缀中再次出现了众数m,那么m就是众数。
从前往后选,首先默认第一个为m。
c是关于m的计数器。初始化为1。从此以后,若出现m则c++,若没出现m则c–。
当c触底(0)时就可以减除下来。

linearselect(A, k) 寻找第k大的元素

递归。
让Q为一个小常数
0.若(n=|A|<Q) 返回 trivialselect(A,k)
1、否则 将A均匀分为n/Q段。每一条的宽度为Q。
2、对每一段进行排序,每一条中均可以找出一个median,取出n/Q的中位数。
3、递归linearselect — 在这几段median中,寻找(M)global median 即median of medians。
注意 从一大堆median 中寻找median 有极高的似然性寻找到median。
4、所有的数据 分为三类:L\E\G l less E equal G great E是指global median O(n)时间可以完成分拣
如果是E 直接return就可以了。
第一部分 有可能 E切大了。减去E&G。。
反之 切除L&E 注意下一次递归要找第 k-L-E 个大的元素。
5、第k个数—一共有三种情况:按情况再递归地调用linear select

在这里插入图片描述

每一次的复杂度都为n。加起来也为线性的复杂度。
递归调用linearselect找出M。 问题规模为 n/Q 因为一共n/Q份
减而治之,linearselect 选取。 问题规模为 3*n/4 为什么?由M决定。中位数能减去四分之一。
其余的复杂度都是线性的。

由下图推理可得,Q一般选取5.
实际中一般采用quickselect,因为linearselect常数项通常远大于2,复杂度大。在这里插入图片描述
备注:类似于快速排序的找第k大,只有一点不同:类快排是随机pivot,而这个linear selection的pivot是各组中位数的中位数(median of medians)
快速排序的问题在于无法保证划分的均匀,而使用 median of medians 策略的话,可以保证pivot比较接近中位数(课程中给出界是3/4,一个更好的界是7/10,但是不影响得出线性的结论)
所以过程大概就是:把当前的数分为Q=5大小的若干组,求出各组的中位数(共n/Q个),再递归求出这些数的中位数(求n个数的中位数等价于求第n/2大的数,是原问题的子问题),以此为pivot按照快排中的partition方法再递归就可以了

diameter 二叉树直径

树的直径:即从一点开始到另一点的最大路径
简化—二叉树的直径。
divide 左右子树分别报告各自的diameter。
也包含三种情况:全局diameter,落在各自的左右子树中 或 贯通于左侧以及右侧。
计算左孩子中最深的节点—计算右孩子最深的节点。左右各有一个节点最长的路,再加起来。
计算这三种情况中寻找一个最长的直径。即 diam(x) = max{diam(x.lc), diam(x.rc), height(x,lc)+height(x.rc)+2 }

closet pair 最近邻

计算几何 的经典问题
所有平面上的点中,哪两个点的欧式几何距离最近。
蛮力算法:for循环 二重。
分而治之:所有的点分为左右两个子集。分而治之进行递归。
nearest neighbor nn(L)&nn®
nn(S) = min{nn(L), nn®, cross(L,R) }
最后一种情况是跨越界线:
分支定界 Branch and bound 在搜索时的策略。
—— 离界的边上的条带设置两倍的δ宽 δ为左右两边最小距离,左边为δ,右边也为δ(δ选取左右子树中的最小的距离)
不仅横向的长度可以分支定界,纵向的长度也可以分支定界。因此这些最邻近点必须在两个边长为δ的正方形中。在每一个界内最多有六个点。
因此最多的情况是左边n/2的点乘以 右边的六个点。 即为O(n)
因此合并的merge过程为O(n)
T(n) = 2T(n/2) + O(n) = O(n logn)
与two-way merge 很像 (左侧为固定点,右侧的window从下到上 有进有出)

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值