分治算法的解和幂乘算法及其优化以及汉诺塔算法的介绍

article/2025/9/18 0:14:21
分治算法就是分成若干个和原问题一样性质的子问题,然后逐步递归这些子问题,直到遇到规模很小的子问题,求出子问题的解,归结成原问题的解的算法就是分治算法
分治算法为递归问题,可以用来解决快排、二分归并、斐波那契数列、锦标赛问题、凸包问题等

      递归方程主要分为两类:(1)(2)f(n)=af(n/b)+d(n)
      第一种的方程可以用迭代或者递归树求解,如汉诺塔;第二种的方程可以用迭代、递归树、主定理求解,如二分归并排序
      本篇主要介绍分支算法的推导方程的解,即主定理、递归树,以及幂乘运算及其优化、汉诺塔问题

1、递归树

什么是递归树

      递归树是迭代过程的一种图像表述。递归树往往被用于求解递归方程, 它的求解表示比一般的迭代会更加的简洁与清晰。
      递归树有如下概念:
1、递归树是迭代计算的模型。
2、递归树的生成过程与迭代过程一致。
3、递归树上所有项恰好是迭代之后产生和式中的项。
4、对递归树上的项求和就是迭代后方程的解。

递归树的生成

T(n)是原问题的解,我们把T(n)写成T(n)=T(m1)+T(m2)+…+T(mt)+f(m1)+f(m2)+…+f(mt)      在这里前面由T写成的方程是递归子问题,而后面由f写成的是归结子问题解的操作。

这里我们把T写成的式子称为函数项,把f写成的式子成为非函数项

对于T(n)是原问题的解。我们可以把非函数项写成根,把函数项写成叶子,如图:

左边的原问题可以写成右边的递归树,右边的递归树根是非函数部分,也就是归结子问题解的部分,而叶子是函数部分,也就是划分子问题的部分,然后我们发现,这棵递归树所有节点的和就是左边原问题的解,这是第一步迭代的过程。

举个例子:我们在上一篇提到过二分归并排序的算法是T(n)=2T(n/2)+n,这里我们看到,T(n)是原问题的解,而2T(n/2)是函数部分,n是非函数部分。我们可以把2T(n/2)画成叶节点,把n画成根节点。做出迭代两步迭代的递归树:

那么我们可以得出递归树的生成规则:
1、初始:递归树只有根结点,其值为T(n)
2、不断继续下述过程:
将函数项叶结点的迭代式T(m)表示成二层子树
用该子树替换该页结点
3、继续递归树的生成,直至树中无函数项(只有初值)为止。

我们参考上边的继续将二分归并的递归树画出来:

我们发现右边的n是左边节点的和,所以原问题的解=所有节点的和=将右边的n全部加起来就可以了
这是一棵完全二叉树,我们知道完全二叉树的深度是logn,那么logn个n相乘不就是nlogn嘛。所以我们得出二分归并排序的时间复杂度是O(nlogn)。

递归树的应用

接下来,我们通过递归树来分析一个递归方程:
T(n)=2T(n/2)+nlogn (n/2 log(n/2)=n/2(logn-log2)=n/2(logn-1))
作图,得:

根据完全二叉树定理,n=2^k,即k=logn(n是节点数,k是深度)
将上图右边的式子相加得,
nlogn+n(logn-1)+n(logn-2)+…+n(logn-k+1)
= nlogn+nlogn+nlogn+…+nlogn-n(1+2+3+4+…+k-1)
= nklogn(有多少深度,就有多少的logn。绝对不是n个logn) -n(k(k-1)/2)
= nklogn-n((k^2-k)/2
这里我们把k=logn带进去得,
= n(logn)2-n((logn)2/2-(logn)/2)
= n(logn)2-n(logn)2/2-n(logn)/2
= O(n(logn)2) (读作:logn的平方乘以n)

递归树分的叉数还是要看递归的规模是多少。如果是T(n)=3T(n/2)+n-1或者T(n)=T(n/9)+T(4n/9)+T(4n/9)+n,那就是要分三个叉了

这是用递归树来解这些递归方程

2、主定理

什么是主定理

主定理就是在涉及f(n)=af(n/b)+d(n)递推方程时,可以更快的运用主定理直接求解,不用繁琐计算

在f(n)=af(n/b)+d(n)中
a 是规约后的子问题的个数
n/b 是规约后子问题的规模
d(n) 是规约过程及组合子问题的解的工作量

首先我得说清楚O是上界,比如说f(n)=n2+n,我们就说f(n)=O(n^2),像这样f(n)=O(g(n)),f(n)的阶不高于g(n)的阶
而Ω是下界,比如说f(n)=n2+n,f(n)=Ω(n^2),像这样f(n)=Ω(g(n)),f(n)的阶不低于g(n)的阶
Θ是当f(n)=O(g(n))而且f(n)=Ω(g(n))则记作f(n)=Θ(g(n))。


就能得出这样的解

这里不再证明了,如果感兴趣的话,可以从网上找找证明。这里我们直接应用

主定理的应用

比如,我们要求解二分归并排序的递推方程
T(n)=2T(n/2)+n
这里a=2,b=2,fn=n
根据主定理得,n=n,也就是说f(n)=Θ(n),那么T(n)=Θ(nlogn)

再比如,T(n)=9T(n/3)+n
这里a=9,b=3,f(n)=n
根据主定理得,n^log3 9=n2,而f(n)=n,我们可以找到一个E=1(主定理1),使得f(n)=O(n),那么T(n)=Θ(n2)
或者说,我们可以根据下面的解来直接得出T(n)=O(n2)

再再比如:T(n)=T(2n/3)+1
这里a=1,b=3/2,f(n)=1
根据主定理得,1=n0,即T(n)=Θ(logn)
或者说,我们可以根据下面的解来直接得出T(n)=O(logn)

再看一下,T(n)=3T(n/4)+nlogn
这里a=3,b=4,f(n)=nlogn
根据主定理得,n^(log4 3)约等于n0.793,那么有E=0.2,使得nlogn=Ω(n^(log4 3)),而且还要使af(n/b)<=cf(n)成立,
则3(n/4)log(n/4)<=cnlogn
3(n/4)(logn-log4)=3(n/4)(logn-2)=3(n/4)logn-3(n/2)
这里我们发现,只要c>=3/4,上述不等式成立。则主定理3成立,即T(n)=Θ(nlogn)
或者说,我们可以根据下面的解来直接得出T(n)=O(nlogn)

当然并不是说所有的递推方程都可以用主定理来解决
大家来看T(n)=2T(n/2)+nlogn
这里a=2,b=2,f(n)=nlogn
首先要知道在都单调递增的情况下,幂函数都是大于对数函数的

你要明白对数函数增长缓慢,而幂函数增长程度是比对数函数要大的多。而指数函数,要比幂函数大的多的多,相比大家听说过指数爆炸吧。如果你的代码量的时间复杂度是个指数函数(比如:2n),我就这么说,你的数据量如果超过了27。那么你的运行时间就可能会超过1秒。所以说算法就是尽量解决这一类问题,让时间复杂度降一个量
对于时间复杂度的量,就是指数的量>多项式的量>对数的量。你这样的每降一次,那么你的运行效率就会提高不止一个档次。运行时间慢就要考虑你的代码问题,而代码混乱的问题就是代码设计模式的问题,当然设计模式也是可以用来提高性能和效率的,这一点在游戏开发格外重要。所以设计模式+数据结构+算法都是程序员的内功心法

好了我们回到正题,f(n)要等于Ω(n(1+E))(E>0)。也就是,n·logn=Ω(n·nE)(E>0)。而且幂函数都是大于对数函数。所以logn<nE,因此找不到一个E满足那个下界(大于等于那个函数)。因此不成立,所以不满足主定理3。也就不能求解。

而且去用下面的解c是logn,不是常数。所以也解不出来。

那要怎么处理呢,我们可以用递归树解这个递归方程,求解在上面递归树那里有,我已经写出来了。

主定理就是解决求递归方程解的问题的,当然要根据情况进行求解。

3、幂乘算法及其优化

什么是幂乘算法?

很简单,就是求nx次方,例如求25。25=32

我们先来看普通的幂乘算法

普通幂乘算法

幂乘算法,我记得学循环的时候,不就接触了这个东西了嘛。你用for写,用while写,不都可以。都是很简单的东西,包括递归

比如啊,你要求an,那么你要传入a,n到函数(方法)里面,用for就是n- -,n>=1,或者啊,就是i=0,i<n,i++。反正方法体里面都是 res*=a(res就是结果)。

对于递归就是n为0,返回1,n为1,返回本身,其他的就返回a*pow(a,n-1),一直递归,找到出口,然后将值一步步返回

不多说了,直接上代码

public static void main(String[] args) {Random random=new Random();int a=random.nextInt(15);int r=random.nextInt(10);System.out.println("计算"+a+"的"+r+"次方的值");int res=pow(a, r);System.out.println("结果是:"+res);}//简单幂乘(迭代写法)public static int pow(int a,int r) {int res=1;for(int i=0;i<r;i++) {res*=a;}return res;}//简单幂乘(递归写法)public static int pow2(int a,int r) {if(r==0)return 1;else if(r==1)return a;else {return a*pow2(a,r-1);}}
简单幂乘算法分析

顺序相乘,实则就是n从1到n本身,总共n个数。那么时间复杂度就是O(n)

如果我们还有优化,那就是要优化到O(logn),下面我们来看一下优化的幂乘算法。

快速幂算法

我们考虑用分治法进行划分

当n为偶数时,可以划成n/2个a和n/2个a相乘。这两个是均匀划分的子问题,而且对于这两个n/2个a,既然你都算出来了n/2个a是多少了,那么对于右面我们就不必再计算了,只做一个子问题的计算就行了,右面的直接拿来用就行了。就因为这样减少了一半的时间。就这样递归下去。

当n为奇数时,我们可以划分成2个(n-1)/2个的a的子问题相乘,然后再和a相乘。也就是
(n-1)/2个a与(n-1)/2个a与a相乘(大家要多思考,多想想)。这样也是近似减半的算法,也差不多减少了一半的时间。就这样递归下去。

我们分析下不管奇数还是偶数,我们都可以用(a,n/2)来进行递归,此时,n为1,就返回本身。否则继续递归。

我们在n=1的时候找到了res=a之后向上返回之后,(在n-1次递归的时候的n要不是2,要不是3。大家可以想想是不是),好我们继续思考,然后进入n的奇偶判断

我们根据上面的图不难想到,如果是偶数,那么就是res乘以res,如果是奇数那么就是res乘以res乘以a

好,我们来看下代码

public static void main(String[] args) {Random random=new Random();int a=random.nextInt(15);int r=random.nextInt(10);System.out.println("计算"+a+"的"+r+"次方的值");int res=pow3(a, r);System.out.println("结果是:"+res3);}//快速幂算法public static int pow3(int a,int r) {int res=1;if(r==0)return 1;else if(r==1)return a;else res=pow3(a, r>>1);if(r%2==0)return res*res;  //如果r为偶数,则乘其本身elsereturn res*res*a;  //如果r为奇数,则乘其本身和a}
快速幂算法的分析

首先,递归肯定是T(n/2),然后乘以res和乘以a,都是一步计算,我们都可以想成O(1)的量。因此T(n)=T(n/2)+O(1)
用主定理分析解出时间复杂度就是O(logn)。

快速幂算法下,有一个重要的应用叫作斐波那契数列。我们再下一篇(分治算法的改进和矩阵(斐波那契数列和Strassen矩阵)下的分治算法)中去说

4、汉诺塔问题

什么是汉诺塔问题

汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。

抽象成数学问题就是
如下图所示,从左到右有A、B、C三根柱子,其中A柱子上面有从小叠到大的n个圆盘,现要求将A柱子上的圆盘移到C柱子上去,期间只有一个原则:一次只能移到一个盘子且大盘子不能在小盘子上面,求移动的步骤

移动的步骤就是这样的

                                     

对于汉诺塔问题,就是这样分析

对于三个盘子的情况是这样的:

我们先把n-1个盘子放到B处

然后把底下那个最大的盘子放到目标位置处

最后将那个n-1个盘子放到目标位置处

ok,移完了

对于那个什么n-1个盘子怎么移过去的,我不管。

而且代码也可以这么写。先放伪码
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EhZzMwvd-1586745625936)(/localImg/ArtImage/2020/02/2020022300453016.jpg)]
大家可以想想刚才的步骤,再看看伪码。你会豁然开朗的。

对于内部那些操作交由递归来实现就行了。

然而我们可以分析得出
(1)中间的一步是把最大的一个盘子由A移到C上去;
(2)中间一步之上可以看成把A上n-1个盘子通过借助辅助塔(C塔)移到了B上,
(3)中间一步之下可以看成把B上n-1个盘子通过借助辅助塔(A塔)移到了C上;

对于内部递归是怎么操作的,大家可以自己动手分析下,这里就放个图

好,我们上代码

public static void main(String[] args) {// TODO Auto-generated method stubint n=3;  //共有3层char a='A';  //A是出发点char b='B';  //B是暂存点char c='C';  //C是终点Hanoi(n, a, b, c);}public static void Hanoi(int n,char A,char B, char C) {if(n==1) Move(A,C); //将出发点移动到终点else {Hanoi(n-1, A, C, B);  //将n-1个塔从出发点移动到暂存点,其中以终点为辅助Move(A, C);Hanoi(n-1, B, A, C);  //将n-1个塔从暂存点移动到终点,其中以出发点为辅助}}public static void Move(char from,char to) {System.out.println(from+"——>"+to);}

把n调调,既可以实现3个,也可以实现4个。

汉诺塔算法的分析

两个递归而且都是从n-1开始的,所以是2T(n-1)。对于从A移动到C是1步,那么就是
T(n)=2T(n-1)+1
T(1)=1
求解这个方程的思路是
T(n)+1=2(T(n-1)+1)
T(n)+1/(T(n-1)+1)=2
也就是T(n)+1是初值为2,公比为2的等比数列
因此得出T(n)=2^n
所以汉诺塔的时间复杂度是O(2^n),居然是个指数级,那么我们要思考有没有一个优化来降低这个时间复杂度。

目前的答案是:没有,不存在一个多项式时间的算法。

        靡不有初,鲜克有终《诗经·大雅·荡》

http://chatgpt.dhexx.cn/article/yHeFtUhJ.shtml

相关文章

递归求解汉诺塔问题(详细解答)

汉诺塔移动步数的计算 具体汉诺塔的规则大家自行百度吧&#xff0c;就不介绍了&#xff0c;这里介绍一下如何求解汉诺塔移动步数的计算思想。 当汉诺塔层数为一层时&#xff0c;很显然只需要一步&#xff0c;直接从A杆移动到C杆 当层数为两层时&#xff0c;则需要三步&#x…

汉诺塔算法

2019独角兽企业重金招聘Python工程师标准>>> 有A、B和C 3跟柱子&#xff0c;在A上从下往上按照从小到大的顺序放着64个圆盘&#xff0c;以B为中介&#xff0c;把盘子全部移动到C上。移动过程中&#xff0c;要求任意盘子的下面要么没有盘子&#xff0c;要么只能有比它…

汉诺塔算法的理解

2019独角兽企业重金招聘Python工程师标准>>> 当盘子数为两个时&#xff0c;移动图如下&#xff1a; 移动规律为&#xff1a; 步骤盘子编号源柱子目标柱子11AB22AC31BC 当盘子数为3个时&#xff1a; 移动规律为&#xff1a; 步骤盘子编号源柱子目标柱子11AC22AB31CB4…

汉诺塔递归算法

2019独角兽企业重金招聘Python工程师标准>>> import java.util.Scanner;/*** 汉诺塔* * author JayChang* */ public class HanoiResolve {/*** 移动位置* * param positionA* param positionB*/public static void move(String positionA, String positionB) {Syst…

数据结构——树和二叉树 6-1 求二叉树高度 (20 分)

本题要求给定二叉树的高度。 函数接口定义&#xff1a; int GetHeight( BinTree BT ); 其中BinTree结构定义如下&#xff1a; typedef struct TNode *Position; typedef Position BinTree; struct TNode{ElementType Data;BinTree Left;BinTree Right; }; 要求函数返回给定…

PTA 函数题 求二叉树高度(C语言)

本题要求给定二叉树的高度。 函数接口定义&#xff1a; int GetHeight( BinTree BT ); 其中BinTree结构定义如下&#xff1a; typedef struct TNode *Position; typedef Position BinTree; struct TNode{ElementType Data;BinTree Left;BinTree Right; }; 要求函数返回给定…

6-1 求二叉树高度

6-1 求二叉树高度 (15 分) 本题要求给定二叉树的高度。 函数接口定义&#xff1a; int GetHeight( BinTree BT );其中BinTree结构定义如下&#xff1a; typedef struct TNode *Position; typedef Position BinTree; struct TNode{ElementType Data;BinTree Left;BinTree Rig…

二叉树的构造及求解二叉树高度

题目描述 1、参考题目解释构造一棵二叉树&#xff1b; 2、求解二叉树的高度 3、有余力同学尝试打印这棵二叉树&#xff08;以树的形态&#xff0c;非必须&#xff09; 输入 A(B(E,C(D(F(,G),),) 输出 二叉树高度为: 6 代码实现 #include <iostream> //#include &l…

二叉树4:二叉树求树高度(超级详细)

一、思路 什么是树高&#xff1f; 树的高度(或深度)就是树中结点的最大层数。 在这里使用后序遍历的递归算法。 对每一个结点都进行如下操作&#xff1a; 后序遍历其左子树求树高后序遍历其右子树求树高对这个结点进行下面操作&#xff1a; 比较其左右子树的高度大小若左&g…

二叉树的高度和深度

二叉树的高度和深度定义 &#xff08;对某个节点来说&#xff09; 深度是指从根节点到该节点的最长简单路径边的条数&#xff1b; 高度是指从最下面的叶子节点到该节点的最长简单路径边的条数&#xff1b; &#xff08;对二叉树&#xff09; 深度是从根节点数到它的叶节点&…

关于360提示发现木马—HEUR/QVM.Malware.Gen

今天用Visual Studio写的关于三层架构的程序 在启动运行时&#xff0c;弹出了360关于"检测到木马程序"的提示框&#xff1a; 虽然知道自己写的是正规程序&#xff0c;但初看时还是被吓了一跳&#xff0c;于是立马上网查阅相关资料和解答&#xff0c;结论是&#xff…

WebRTC 之 RTX

AbstractWebRTC RTX 笔记AuthorsWalter FanCategorylearning noteStatusv1.0Updated2020-08-28LicenseCC-BY-NC-ND 4.0 什么是 RTX RTX 就是重传 Retransmission, 将丢失的包重新由发送方传给接收方。 Webrtc 默认开启 RTX (重传)&#xff0c;它一般采用不同的 SSRC 进行传输&a…

红与蓝:现代Webshell检测引擎免杀对抗与实践

上半年Webshell话题很火,业界举办了数场对抗挑战赛,也发布了多篇站在安全产品侧,着重查杀思路的精彩文章,但鲜有看到以蓝军视角为主的paper。 作为多场挑战赛的参赛者及内部红蓝对抗的参与者,笔者试着站在蓝军角度,聊聊现代Webshell对抗的一些思路,也以PHP Webshell为例…

堪比熊猫烧香!中国新型蠕虫病毒大爆发!电脑瞬间报废

堪比熊猫烧香&#xff01;中国新型蠕虫病毒大爆发&#xff01;电脑瞬间报废 近日&#xff0c;深信服安全团队监测到一种名为incaseformat的病毒&#xff0c;全国各个区域都出现了被incaseformat病毒删除文件的用户。 经调查&#xff0c;该蠕虫正常情况下表现为文件夹蠕虫&#…

Dreh zelle acht hoch

Android6.0权限和SharedPreferences存储 static class MyTask extends AsyncTask<String,String,String> { Override protected String doInBackground(String... strings) { FileOutputStream outnull; InputStream inputStreamnull;//网络连接的输入流 HttpURLConnecti…

json.cp37-win32.pyd HEUR/QVM30.2.223F.Malware...

问题 使用pyinstaller导出exe,运行时候会被360杀毒报木马. 病毒类型是HEUR/QVM30.2.223F.Malware.Gen 木马文件是 pandas库下的一个文件 \Python\Python37-32\Lib\site-packages\pandas\_libs\json.cp37-win32.pyd 原因 32位的pandas文件是 json.cp37-win32.pyd (32位的,3…

Herm Chart

参考链接 https://blog.csdn.net/QianLiStudent/article/details/111872100 https://www.jianshu.com/p/4bd853a8068b 1 概念 1.1 Helm 1.1.1 Helm是什么&#xff1f; Helm 是 Kubernetes 的包管理器。包管理器类似于我们在 Ubuntu 中使用的apt、Centos中使用的yum 或者Pyt…

关于桌面程序被安全软件误判为HEUR:Trojan.Win32.Generic的解决方案

最近写了一个桌面程序&#xff0c;里面用了些读取系统环境变量、提取文件图标、启动外部程序之类的操作。 然后…………卡巴斯基就把它识别成了HEUR:Trojan.Win32.Generic………… 咱遵纪守法好程序&#xff0c;怎么说是木马就是木马了呢&#xff1f;&#xff1f;&#xff1f; …

从0到1学会使用SpringBoot 搭建mock Server

做过接口测试的同学一定听说过mock Server&#xff0c;大家会觉得其很神秘&#xff0c;很高大上&#xff01;mock Server出现的原因是现今的业务系统很少有孤立存在的&#xff0c;它们或多或少需要使用兄弟团队或是其他公司提供的服务&#xff0c;这给我们的联调和测试造成了麻…

Postman mockserver详细教程

转自&#xff1a; https://blog.csdn.net/testdeveloper/article/details/80559538 模客API接口链接&#xff1a;http://mock-api.com/ http://mock-api.com/ http://mock-api.com/ http://mock-api.com/ 1.发送一个request 发送请求之后在History标签下保存了请求的数据&a…