分治算法的应用
① 分治算法时间复杂度
一:分治算法和递归
1.简述递归
我们要讲到分治算法,我觉得有必要说一下递归,他们就像一对孪生兄弟,经常同时应用在算法设计中,并由此产生许多高效的算法。
直接或间接的调用自身的算法称为递归算法。用函数自身给出定义的函数称为递归函数。
int fibonacci(int n){
if (n <= 1) return 1;
return fibonacci(n-1)+fibonacci(n-2);
}
先简单看一下经典的递归例子,博主会找个时间系统详细的总结一下关于递归的内容。
2.简述分治
分治法的设计思想是:
分–将问题分解为规模更小的子问题;
治–将这些规模更小的子问题逐个击破;
合–将已解决的子问题合并,最终得出“母”问题的解;
一个先自顶向下,再自底向上的过程。
凡治众如治寡,分数是也。—孙子兵法
3.分治法与递归的联系
由分治法产生的子问题往往是原问题的较小模式,这就为使用递归技术提供了方便。在这种情况下,反复应用分治手段,可以使子问题与原问题类型一致而其规模却不断缩小,最终使子问题缩小到很容易直接求出其解。这自然导致递归过程的产生。
二:分治法的适用条件
分治法所能解决的问题一般具有以下几个特征:
1) 该问题的规模缩小到一定的程度就可以容易地解决
2) 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。
3) 利用该问题分解出的子问题的解可以合并为该问题的解;
4) 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。
第一条特征是绝大多数问题都可以满足的,因为问题的复杂性一般是随着问题规模的增加而增加;
第二条特征是应用分治法的前提它也是大多数问题可以满足的,此特征反映了递归思想的应用;、
第三条是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一条和第二条特征,而不具备第三条特征,则可以考虑用贪心法或动态规划法。
第四条特征涉及到分治法的效率,如果各子问题是不独立的则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然可用分治法,但一般用动态规划法较好
三:分治法的基本步骤
分解问题:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;(自顶向下)
这里涉及到一个平衡子问题的思想:人们从大量实践中发现,在用分治法设计算法时,最好使子问题的规模大致相同。即将一个问题分成大小相等的k个子问题的处理方法是行之有效的。这种使子问题规模大致相等的做法是出自一种平衡子问题的思想,它几乎总是比子问题规模不等的做法要好。
解决问题:如果问题规模较小而容易被解决则直接解,否则递归地解各个子问题,以得到小问题的解。
合并结果:将各个子问题的解合并为原问题的解:(自底向上)。
它的一般算法设计模式如下:
divide-and-conquer(P){
if ( | P | <= n0) adhoc(P); //(2)解决问题:递归到小问题,则解决小规模的问题(自顶向下)
divide P into smaller subinstances P1,P2,...,Pk;//(1)分解问题
for (i=1,i<=k,i++)
yi=divide-and-conquer(Pi); //利用递归的解各子问题
return merge(y1,...,yk); //将各子问题的解合并为原问题的解(自底向上)
}
四:分治法的复杂性分析
从分治法的一般设计模式可以看出,用他设计出的程序一般是递归算法。因此分治法的计算效率通常可以用递归方程来进行分析。
一个分治法将规模为n的问题分成k个规模为n/m的子问题去解。设分解阀值(表示当问题P规模不超过n0时,问题已容易解出,不必再继续分解)n0=1,且adhoc解规模为1的问题耗费1个单位时间。再设将原问题分解为k个子问题以及用merge将k个子问题的解合并为原问题的解需用f(n)个单位时间。用T(n)表示该分治法解规模为|P|=n的问题所需的计算时间,则有:
通常可以用展开递归式的方法来解这类递归方程,反复带入求解得
② 分治、贪心五大算法
1、分治
分治(即分而治喊孙之),把一个复杂的问题分成多郑激链个相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
适用场景:二分搜索、归并排序、快速排序、大整数乘法、第K小元素、最近点对、快速傅里叶变换等。
2、动态规划
动态规划法也是把问题一层一层地分解为规模逐渐减小的同类型的子问题。动态规划通常用来求最优化问题。此类问题可以有很多可行解,我们求出的是一个最优解,可能存在多个最优解。(最优子结构、公共子问题)
与分治法的区别是:分治的子问题是相互独立的,动态规划最好解决有公共子问题的,子问题相关性很大。
使用场景:矩阵连乘、钢条切割、最长公共子序列、最优二叉搜索树、流水作业调度、0/1背包问题等。
维特比算法是动态规划在HMM中的应用,维特比算法用于解决HMM的预测或者叫解码问题。
viterbi有最优解是因为HMM每一步是条件独立的!既然后面的概率和前面的没关系,那前面选最大的概率就行了。
而beam search时后面的概率依赖于前面所有的词,相当于n-gram是满的,viterbi的n-gram是2
背包问题:
https://blog.csdn.net/wind__chaser/article/details/89457771
https://blog.csdn.net/qq_38410730/article/details/81667885
3、贪心
通过局部最优选择达铅吵到全局最优选择。贪心算法不一定总产生最优解,贪心算法是否产生优化解,需严格证明贪心算法产生最优解的条件:(最优子结构、贪心选择性)
贪心选择性:当一个问题的全局最优解可以通过局部最优解得到,称这个问题具有贪心选择性。
适用场景:活动选择问题、哈夫曼编码问题、最小生成树问题、单源最短路径问题等。
贪心算法:softmax之后取最大概率。与之对应的是,Beam Search算法
http://www.360doc.com/content/18/0618/09/17563728_763230413.shtml
https://blog.csdn.net/qq_16234613/article/details/83012046
https://www.hu.com/question/54356960
分治和动态规划的区别:
动态规划也是一种分治思想(比如其状态转移方程就是一种分治),但与分治算法不同的是,分治算法是把原问题分解为若干个子问题,
自顶向下求解子问题,合并子问题的解,从而得到原问题的解。动态规划也是把原始问题分解为若干个子问题,然后自底向上,
先求解最小的子问题,把结果存在表格中,在求解大的子问题时,直接从表格中查询小的子问题的解,避免重复计算,从而提高算法效率。
动态规划和分治法有些相像,都是把一个问题分成了很多子问题来求解,但是不同的是动态规划会记忆之前解决的子问题的结果,
避免了重复计算。判断一个问题是否能用动态规划求解,要看它是否能划分成合适的子问题,然后写出递推关系式。
动态规划得到的解一定是最优解。
③ 程序员都应该精通的六种算法,你会了吗
对于一名优秀的程序员来说,面对一个项目的需求的时候,一定会在脑海里浮现出最适合解决这个问题的方法是什么,选对了算法,就会起到事半功倍的效果,反之,则可能会使程序运行效率低下,还容易出bug。因此,熟悉掌握常用的算法,是对于一个优秀程序员最基本的要求。
那么,常用的算法都有哪些呢?一般来讲,在我们日常工作中涉及到的算法,通常分为以下几个类型:分治、贪心、迭代、枚举、回溯、动态规划。下面我们来一一介绍这几种算法。
一、分治算法
分治算法,顾名思义,是将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。
分治算法一般分为三个部分:分解问题、解决问题、合并解。
分治算法适用于那些问题的规模缩小到一定程度就可以解决、并且各子问题之间相互独立,求出来的解可以合并为该问题的解的情况。
典型例子比如求解一个无序数组中的最大值,即可以采用分治算法,示例如下:
def pidAndConquer(arr,leftIndex,rightIndex):
if(rightIndex==leftIndex+1 || rightIndex==leftIndex){
return Math.max(arr[leftIndex],arr[rightIndex]);
}
int mid=(leftIndex+rightIndex)/2;
int leftMax=pidAndConquer(arr,leftIndex,mid);
int rightMax=pidAndConquer(arr,mid,rightIndex);
return Math.max(leftMax,rightMax);
二、贪心算法
贪心算法是指在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。
贪心算法的基本思路是把问题分成若干个子问题,然后对每个子问题求解,得到子问题的局部最优解,最后再把子问题的最优解合并成原问题的一个解。这里要注意一点就是贪心算法得到的不一定是全局最优解。这一缺陷导致了贪心算法的适用范围较少,更大的用途在于平衡算法效率和最终结果应用,类似于:反正就走这么多步,肯定给你一个值,至于是不是最优的,那我就管不了了。就好像去菜市场买几样菜,可以经过反复比价之后再买,或者是看到有卖的不管三七二十一先买了,总之最终结果是菜能买回来,但搞不好多花了几块钱。
典型例子比如部分背包问题:有n个物体,第i个物体的重量为Wi,价值为Vi,在总重量不超过C的情况下让总价值尽量高。每一个物体可以只取走一部分,价值和重量按比例计算。
贪心策略就是,每次都先拿性价比高的,判断不超过C。
三、迭代算法
迭代法也称辗转法,是一种不断用变量的旧值递推新值的过程。迭代算法是用计算机解决问题的一种基本方法,它利用计算机运算速度快、适合做重复性操作的特点,让计算机对一组指令(或一定步骤)进行重复执行,在每次执行这组指令(或这些步骤)时,都从变量的原值推出它的一个新值。最终得到问题的结果。
迭代算法适用于那些每步输入参数变量一定,前值可以作为下一步输入参数的问题。
典型例子比如说,用迭代算法计算斐波那契数列。
四、枚举算法
枚举算法是我们在日常中使用到的最多的一个算法,它的核心思想就是:枚举所有的可能。枚举法的本质就是从所有候选答案中去搜索正确地解。
枚举算法适用于候选答案数量一定的情况。
典型例子包括鸡钱问题,有公鸡5,母鸡3,三小鸡1,求m钱n鸡的所有可能解。可以采用一个三重循环将所有情况枚举出来。代码如下:
五、回溯算法
回溯算法是一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。
许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。
典型例子是8皇后算法。在8 8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问一共有多少种摆法。
回溯法是求解皇后问题最经典的方法。算法的思想在于如果一个皇后选定了位置,那么下一个皇后的位置便被限制住了,下一个皇后需要一直找直到找到安全位置,如果没有找到,那么便要回溯到上一个皇后,那么上一个皇后的位置就要改变,这样一直递归直到所有的情况都被举出。
六、动态规划算法
动态规划过程是:每次决策依赖于当前状态,又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划。
动态规划算法适用于当某阶段状态给定以后,在这阶段以后的过程的发展不受这段以前各段状态的影响,即无后效性的问题。
典型例子比如说背包问题,给定背包容量及物品重量和价值,要求背包装的物品价值最大。
④ 分治算法的应用实例
下面通过实例加以说明: 给你一个装有1 6个硬币的袋子。1 6个硬币中有一个是伪造的,并且那个伪造的硬币比真的硬币要轻一些。你的任务是找出这个伪造的硬币。为了帮助你完成这一任务,将提供一台可用来比较两组硬币重量的仪器,利用这台仪器,可以知道两组硬币的重量是否相同。比较硬币1与硬币2的重量。假如硬币1比硬币2轻,则硬币1是伪造的;假如硬币2比硬币1轻,则硬币2是伪造的。这样就完成了任务。假如两硬币重量相等,则比较硬币3和硬币4。同样,假如有一个硬币轻一些,则寻找伪币的任务完成。假如两硬币重量相等,则继续比较硬币5和硬币6。按照这种方式,可以最多通过8次比较来判断伪币的存在并找出这一伪币。
另外一种方法就是利用分而治之方法。假如把1 6硬币的例子看成一个大的问题。第一步,把这一问题分成两个小问题。随机选择8个硬币作为第一组称为A组,剩下的8个硬币作为第二组称为B组。这样,就把1 6个硬币的问题分成两个8硬币的问题来解决。第二步,判断A和B组中是否有伪币。可以利用仪器来比较A组硬币和B组硬币的重量。假如两组硬币重量相等,则可以判断伪币不存在。假如两组硬币重量不相等,则存在伪币,并且可以判断它位于较轻的那一组硬币中。最后,在第三步中,用第二步的结果得出原先1 6个硬币问题的答案。若仅仅判断硬币是否存在,则第三步非常简单。无论A组还是B组中有伪币,都可以推断这1 6个硬币中存在伪币。因此,仅仅通过一次重量的比较,就可以判断伪币是否存在。
假设需要识别出这一伪币。把两个或三个硬币的情况作为不可再分的小问题。注意如果只有一个硬币,那么不能判断出它是否就是伪币。在一个小问题中,通过将一个硬币分别与其他两个硬币比较,最多比较两次就可以找到伪币。这样,1 6硬币的问题就被分为两个8硬币(A组和B组)的问题。通过比较这两组硬币的重量,可以判断伪币是否存在。如果没有伪币,则算法终止。否则,继续划分这两组硬币来寻找伪币。假设B是轻的那一组,因此再把它分成两组,每组有4个硬币。称其中一组为B1,另一组为B2。比较这两组,肯定有一组轻一些。如果B1轻,则伪币在B1中,再将B1又分成两组,每组有两个硬币,称其中一组为B1a,另一组为B1b。比较这两组,可以得到一个较轻的组。由于这个组只有两个硬币,因此不必再细分。比较组中两个硬币的重量,可以立即知道哪一个硬币轻一些。较轻的硬币就是所要找的伪币。 在n个元素中找出最大元素和最小元素。我们可以把这n个元素放在一个数组中,用直接比较法求出。算法如下:
void maxmin1(int A[],int n,int *max,int *min)
{ int i;
*min=*max=A[0];
for(i=0;i <= n;i++)
{ if(A[i]> *max) *max= A[i];
if(A[i] < *min) *min= A[i];
}
}
上面这个算法需比较2(n-1)次。能否找到更好的算法呢?我们用分治策略来讨论。
把n个元素分成两组:
A1={A[1],...,A[int(n/2)]}和A2={A[INT(N/2)+1],...,A[N]}
分别求这两组的最大值和最小值,然后分别将这两组的最大值和最小值相比较,求出全部元素的最大值和最小值。如果A1和A2中的元素多于两个,则再用上述方法各分为两个子集。直至子集中元素至多两个元素为止。
例如有下面一组元素:-13,13,9,-5,7,23,0,15。用分治策略比较的算法如下:
void maxmin2(int A[],int i,int j,int *max,int *min)
/*A存放输入的数据,i,j存放数据的范围,初值为0,n-1,*max,*min 存放最大和最小值*/
{ int mid,max1,max2,min1,min2;
if (j==i) {最大和最小值为同一个数;return;}
if (j-1==i) {将两个数直接比较,求得最大会最小值;return;}
mid=(i+j)/2;
求i~mid之间的最大最小值分别为max1,min1;
求mid+1~j之间的最大最小值分别为max2,min2;
比较max1和max2,大的就是最大值;
比较min1和min2,小的就是最小值;
} 题目:在一个(2^k)*(2^k)个方格组成的棋盘上,有一个特殊方格与其他方格不同,称为特殊方格,称这样的棋盘为一个特殊棋盘。我们要求对棋盘的其余部分用L型方块填满(注:L型方块由3个单元格组成。即围棋中比较忌讳的愚形三角,方向随意),且任何两个L型方块不能重叠覆盖。L型方块的形态如下:
题目的解法使用分治法,即子问题和整体问题具有相同的形式。我们对棋盘做一个分割,切割一次后的棋盘如图1所示,我们可以看到棋盘被切成4个一样大小的子棋盘,特殊方块必定位于四个子棋盘中的一个。假设如图1所示,特殊方格位于右上角,我们把一个L型方块(灰色填充)放到图中位置。这样对于每个子棋盘又各有一个“特殊方块”,我们对每个子棋盘继续这样分割,直到子棋盘的大小为1为止。
用到的L型方块需要(4^k-1)/3 个,算法的时间是O(4^k),是渐进最优解法。
本题目的C语言的完整代码如下(TC2.0下调试),运行时,先输入k的大小,(1<=k<=6),然后分别输入特殊方格所在的位置(x,y), 0<=x,y<=(2^k-1)。 #include<stdio.h>//#include<conio.h>//#include<math.h>inttitle=1;intboard[64][64];voidchessBoard(inttr,inttc,intdr,intdc,intsize){ints,t;if(size==1)return;t=title++;s=size/2;if(dr<tr+s&&dc<tc+s)chessBoard(tr,tc,dr,dc,s);else{board[tr+s-1][tc+s-1]=t;chessBoard(tr,tc,tr+s-1,tc+s-1,s);}if(dr<tr+s&&dc>=tc+s)chessBoard(tr,tc+s,dr,dc,s);else{board[tr+s-1][tc+s]=t;chessBoard(tr,tc+s,tr+s-1,tc+s,s);}if(dr>=tr+s&&dc<tc+s)chessBoard(tr+s,tc,dr,dc,s);else{board[tr+s][tc+s-1]=t;chessBoard(tr+s,tc,tr+s,tc+s-1,s);}if(dr>=tr+s&&dc>=tc+s)chessBoard(tr+s,tc+s,dr,dc,s);else{board[tr+s][tc+s]=t;chessBoard(tr+s,tc+s,tr+s,tc+s,s);}}voidmain(){intdr=0,dc=0,s=1,i=0,j=0;printf(printinthesizeofchess:
);scanf(%d,&s);printf(printinspecalpointx,y:
);scanf(%d%d,&dr,&dc);if(dr<s&&dc<s){chessBoard(0,0,dr,dc,s);for(i=0;i<s;i++){for(j=0;j<s;j++){printf(%4d,board[i][j]);}printf(
);}}elseprintf(thewrongspecalpoint!!
);getch();}
⑤ 背包问题用分治算法怎么解决
分治算法的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解,就可得到原问题的解。
我的想法是,比如背包承重是m,物品t[]分别是t1,t2,...tn,
算法pakage(m,t[],p[]),最简单的算法思路就是把物品ti(1<=i<=n)放入背包,然后问题就变成在背包m-ti的承重下,盛放物品newt[](newt[]=t[]-ti),用递归方法可以很简单的描述,最后p[]就是得到的结果.但是这么做算法复杂度非常高,所以必须优化。只是给你提供一个思路,希望有用
pakage(m,t[],p[]){
if(m==0||t.length()==0){
return;
}
if(null!=t){
for(int i=0;i<t.length;i++){
//把t[i]放入背包
//newt[]=t-t[i];
package(m-t[i],newt[],p[]);
}
}
}
⑥ 分治算法是什么呢
分治算法的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解,就可得到原问题的解。即一种分目标完成程序算法,简单问题可用二分法完成。
解题步骤
分治法解题的一般步骤:
(1)分解,将要解决的问题划分成若干规模较小的同类问题;
(2)求解,当子问题划分得足够小时,用较简单的方法解决;
(3)合并,按原问题的要求,将子问题的解逐层合并构成原问题的解。
⑦ 分治算法几个经典例子
分治法,字面意思是“分而治之”,就是把一个复杂的1问题分成两个或多个相同或相似的子问题,再把子问题分成更小的子问题直到最后子问题可以简单地直接求解,原问题的解即子问题的解的合并,这个思想是很多高效算法的基础。
图二
大整数乘法
Strassen矩阵乘法
棋盘覆盖
合并排序
快速排序
线性时间选择
最接近点对问题
循环赛日程表
汉诺塔