0x05算法设计与分析复习(二):算法设计策略-分治法1
参考书籍:算法设计与分析——C++语言描述(第二版)
算法设计策略-分治法
分治法
分治法的基本思想
分治法就是分而治之,一个问题能够用分治法求解的要素是:第一,问题能够按照某种方法分解成若干个规模较小,相互独立且与原问题类型相同的子问题;第二,子问题足够小时可以直接求解;第三,能够将子问题的解组合成原问题的解。
由于分治法要求分解成同类子问题,并允许不断分解,使问题的规模逐步减小,最终可用已知的方法求解足够小的问题,因此分治法求解很自然导致一个递归算法。
分治法求解框架:
SolutionType DandC(ProblemType P) { ProblemType P1,P2,...,Pk; if(Small(P)) //子问题P足够小,用S(P)直接求解 return S(P); else{ Divide(P,P1,P2,...,Pk); //将问题P分解成子问题P1,P2,...,Pk return Combine(DandC(P1),DandC(P2),...,DandC(Pk)); //求解子问题,合并求解 } }
其中Small(P)
是一个布尔值函数,它判定问题P是否足够小。Divide
函数以某种方式,将问题P分解成规模较小,相互独立的若干同类型子问题。Combine
函数将各子问题的解组合成原始问题的解。特殊的,一分为二的分治法:
SolutionType DandC(int left, int right) { if(Small(left, right)) return S(left,right); else{ int m = Divide(left, right); //以m为界将问题分解成两个子问题 return Combine(DandC(left, m),DandC(m+1, right)); //分别求解子问题,合并求解 } }
如果较大的问题可以分为几个同样大小的问题,那么往往可以得到以下的递推公式:
定理:设,,和为常数,,则
分治法求最大元最小元
问题描述
在一个元素集合中寻找最大元素和最小元素的问题,即在互不相同的个数中,找出最大和最小的数。
分治法求解
用分治法求最大最小元。可以将原问题分解成大小基本相等的两个子问题。显然在一个或两个元素的表中求最大、最小元是容易的,可以直接求得;如果已经求得了由分解所得的两个字表中的最大、最小元,则原表的最大元是两个字表中的最大元之较大者,原表的最小元是两子表中的最小元之较小者。
template <class T> void SortableList<T>::MaxMin(int i, int j, T& max, T& min)const //前置条件:i和j,0<=i<=j<表长,是表的下标范围的界 { T min1, max1; if(i == j){ max=min=l[i]; //表中只有一个元素 } else{ //表中有两个元素 if(i == j-1){ if(l[i] < l[j]){ max = l[j]; min = l[i]; } else{ max = l[i]; min = l[j]; } } else{ //表中多于两个元素时 int m = (i+j)/2; //对半分割 MaxMin(i,m,max,min); //求前一半部分子表中的最大最小元素 MaxMin(m+1,j,max1,min1); //求后一半部分子表中的最大最小元素 if(max < max1){ //两子表最大元的大者为原表最大元 max = max1; } if(min > min1){ //两子表最小元的小者为原表最小元 min = min1; } } } }
使用归纳法可以证明算法的正确性。
用C语言实验如下:
//#include <stdio.h> // //int gcd(int m, int n) //{ // //欧几里得辗转相除法 // //假定m<n // if(m == 0) { // return n; // } else { // int tmp; // while(m>0) { // tmp = n%m; // n = m; // m = tmp; // } // return n; // } //} // //int Rgcd(int m, int n) //{ // //递归形式 // if(m == 0) { // return n; // } else { // return Rgcd(n%m, m); // } //} // //int main() //{ // int m, n, r1, r2; // scanf("%d%d", &m, &n); // // if(n>m) { // r1 = gcd(m,n); // r2 = Rgcd(m,n); // } else { // r1 = gcd(n,m); // r2 = Rgcd(n,m); // } // // printf("GCD of m and n: %d\n", r1); // printf("GCD of m and n: %d\n", r2); // // // return 0; //} #include <stdio.h> void MaxMin(int l[], int i, int j, int *max, int *min) //前置条件:i和j,0<=i<=j<表长,是表的下标范围的界 { int min1=l[i], max1=l[i]; if(i == j){ *max=*min=l[i]; //表中只有一个元素 } else{ //表中有两个元素 if(i == j-1){ if(l[i] < l[j]){ *max = l[j]; *min = l[i]; } else{ *max = l[i]; *min = l[j]; } } else{ //表中多于两个元素时 int m = (i+j)/2; //对半分割 MaxMin(l,i,m,max,min); //求前一半部分子表中的最大最小元素 MaxMin(l,m+1,j,&max1,&min1); //求后一半部分子表中的最大最小元素 if(*max < max1){ //两子表最大元的大者为原表最大元 *max = max1; } if(*min > min1){ //两子表最小元的小者为原表最小元 *min = min1; } } } printf("i= %d; j= %d; Max = %d; Min = %d\n", i, j, *max, *min); } int main() { int P[10] = {1, 3, 5, 2, 3 ,6 ,7, 1, 10, -11}; int max = 0, min = 0; printf("array P:"); for(int i = 0; i<10; i++) printf(" %d", P[i]); printf("\n"); MaxMin(P, 0, 9, &max, &min); return 0; }
实验结果:
array P: 1 3 5 2 3 6 7 1 10 -11 i= 0; j= 1; Max = 3; Min = 1 i= 2; j= 2; Max = 5; Min = 5 i= 0; j= 2; Max = 5; Min = 1 i= 3; j= 4; Max = 3; Min = 2 i= 0; j= 4; Max = 5; Min = 1 i= 5; j= 6; Max = 7; Min = 6 i= 7; j= 7; Max = 1; Min = 1 i= 5; j= 7; Max = 7; Min = 1 i= 8; j= 9; Max = 10; Min = -11 i= 5; j= 9; Max = 10; Min = -11 i= 0; j= 9; Max = 10; Min = -11 Press any key to continue.
时间分析
分治法求最大元最小元程序在最好、平均和最坏情况下的比较次数都为。
小结
分治法的思想:
对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。
分治法在每一层递归上都有三个步骤:
- 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
- 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题;
- 合并:将各个子问题的解合并为原问题的解。
分治法所能解决的问题一般具有以下几个特征:
- 该问题的规模缩小到一定的程度就可以容易地解决;
- 因为问题的计算复杂性一般是随着问题规模的增加而增加,因此大部分问题满足这个特征
- 该问题可以分解为若干个规模较小的相同问题;
- 这条特征是应用分治法的前提,它也是大多数问题可以满足的,此特征反映了递归思想的应用
- 利用该问题分解出的子问题的解可以合并为该问题的解;
- 能否利用分治法完全取决于问题是否具有这条特征,如果具备了前两条特征,而不具备第三条特征,则可以考虑贪心算法或动态规划算法
- 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。
- 这条特征涉及到分治法的效率,如果各子问题是不独立的,则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然也可用分治法,但一般用动态规划较好
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)