算法入门经典 分治法

1.分治法适用场景

1) 该问题的规模缩小到一定的程度就可以容易地解决

2) 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。

3) 利用该问题分解出的子问题的解可以合并为该问题的解;

4) 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。
 注意·:第三条特征是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一条和第二条特征,而不具备第三条特征,则可以考虑用贪心法或动态规划法。
2.分治的步骤:
第一步:划分问题:把问题分成一半一半抽象地划分;
第二部:递归求解:递归求解子问题;
第三步:合并问题得到解

例题:计算数组【1,2,3,4,n】的和;
分治法:
划分,前一半和后一半
递归;不断划分直到只有一个值的时候直接返回这个值;
合并;
前后加起来:
代码:

/*
划分问题:
计算左边m/2个的和 ;
递归:一直分 直到为一个直接返回;
合并:
 



*/
#include <iostream>
using namespace std; 
int ans;
int f(int a[],int x,int y,int sum)//表示x到y的sum和是多少 
{
    
    
	if(y-x==1) 
	{
    
    
	
		return a[x];
	}
	int m=x+(y-x)/2;//m是划分的标准,就是中间值;
	
	sum=f(a,x,m,sum)+f(a,m,y,sum); //sum和是前面一半的和加上后面一半的和;
	return sum;
}
int main()
{
    
    
	int a[6]={
    
    1,2,3,4,5,6};
	cout<<f(a,0,6,0)<<endl;
	return 0; 
 } 

总结小技巧:
1.分治的写法:
a.划分问题,化到最小的问题,化为前后两半
b.递归求解:每次不断划分调用本身,直到最后返回最小值;
c.合并问题:所有问题合在一起;
2.中间变量模板
m=开头+(结尾-开头)/2;
二分查找
思路
a.划分问题:分成前后各一半;
b.递归求解: 不断划分前后各一半,不断划分一直找;
c.合并问题:找到就输出;
代码:

#include <iostream>
using namespace std;
int a[6]={
    
    1,4,5,6,7,8};
int main()
{
    
    
	int key;cin>>key;
	int l=0,r=5;
	while(l <= r)
	{
    
    
		int m=l+(r-l)/2;
		
		if(a[m]==key) 
		{
    
    
			cout<<m<<endl;
			return 0;
		}
		else if(a[m]<key)
		{
    
    
			l=m+1;
		}
		else if(a[m]>key)
		{
    
    
			r=m-1;
		}
	}
}

注意点:
一个是每次的边界,结束条件,以及如何防止数据过大
为了防止数据过大,要用(r-l);
更多注意点模板在下面留言找我要,有一堆整理的
二分法求问题的解;
题目:求下面方程的根
xxx-5xx+10*x-80=0;
思想:
我们求导发现这个函数单调递增,根在0到100里面:
我们不断在0 到 100里面找值是否符合这个根;
代码:

/*
划分  0-100每次分一半
如果当前加入小了在前面找,大了在后面找; 

*/
#include <iostream>
#include <math.h>
using namespace std;
double EPS=1e-6;
double f(double x)
{
    
    
	return x*x*x-5*x*x+10*x-80;
}
int main()
{
    
    
	double l=0,r=100,x,y;
	x=l+(r-l)/2;
	y=f(x);
	while( fabs(y)> EPS)
	{
    
    
		if(y>0) r=x;
		else l=x;
		x=l+(r-l)/2;
		y=f(x); 
	}
	cout<<x<<endl;
	return 0;
}

技巧:关于浮点数的比较只能比较一个较小的数;
只要小于这个数,就算是成功;
每次小于的话扩展右边,大于的话扩展左边;
题目:划分最小面积:
一个地方给你,比如640 400,请问如何分正好分完,而且每个都是正方形:
思想:
1.划分问题:
每次不断切割正方形;
2.递归求解:直到有2个是一样的
3.合并:结束条件:
代码:

/*
划分分出长和宽
递归:不断分
合并:最后一个 
*/
#include <iostream>
using namespace std;
int f(int m,int n)
{
    
    
	if(m==n) return m;
	else 
	{
    
    
		if(m < n)
		{
    
    
			return f(m,n-m);
		}
		else if(m > n)
		{
    
    
			return f(n,m-n);
		}
	}
}
int main()
{
    
    
	int m,n;
	cin>>m>>n;
	cout<<f(m,n);
 } 

快速排序:

思想:
划分问题:
分成前面一半后面一半
递归:到中间:
合并:前后都有序就是有序:
代码:

/*
划分问题:
左边右边开始查找;
交换
递归到中间;
合并; 


*/
#include <iostream>
using namespace std;
void qsort(int a[],int le,int ri)
{
    
    
	int i=le,j=ri,mid=a[(le+ri)>>1];//
	while(i <= j)
	{
    
    
		while(a[i] < mid) i++;
		while(a[j] > mid) j--;
		if( i <= j)
		{
    
    
			swap(a[i],a[j]);
			i++,j--;
		}
	}
	if(le < j)qsort(a,le,j);
	if(i < ri) qsort(a,i,ri);
}

int a[7]={
    
    1,3,2,4,6,5,7};
int main()
{
    
    
	qsort(a,0,6);
	for(int i=0;i<7;++i)
	{
    
    
		cout<<a[i]<<" ";
	}
	return 0;
 } 

技巧:

int mid=a[l+r>>1];//取中间

这个为了防止溢出:
mid=a[l+(r-l)];

if(i <= j)//如果小于等于才是可以交换,不然前面又换了回来;
换完不要忘了继续走

如果没有

swap(a[i],a[j]);
			i++,j--;
			这个i++,j--的话

i++,j–是换完继续操作的,就是代表换完要继续走,不然不会结束循环:
递归左右:
左边的边界时j,右边的边界是i;

if(le < j)qsort(a,le,j);
	if(i < ri) qsort(a,i,ri);

这里是要判断的只有左边的小于j左边才递归
快速的第二种方法:
思想:
划分左右
递归求解
合并问题:
代码:

#include <iostream>
using namespace std;
void qsort(int a[],int l,int r)
{
    
    
	if(l >= r) return ;//如果l>=r就是越界了,或者相遇了不用继续了,因为一个数不用交换
	int x=a[l+r>>1],i=l-1,j=r+1;//x是中间那个数,i变为左边减去1,j变为右边加一,因为后面i先加加了,所以前面先把i处理了
	while(i < j)
	{
    
    
		do i++;while(a[i]<x);
		do j--;while(a[j]>x);
		if( i < j) swap(a[i],a[j]);如果i<j就交换了,等于就不用了
		
	}
	qsort(a,l,j);//j是前面一半的右边界
	qsort(a,j+1,r);//i是后面一半的左边界
}
int a[100000];
int main()
{
    
    
	int n;cin>>n;
	for(int i=0;i<n;++i)
	{
    
    
		cin>>a[i];
	}
	qsort(a,0,n-1);
	for(int i=0;i<n;++i)
	{
    
    
		cout<<a[i]<<" ";
	}
	return 0;
}

课后习题:
2的次方
思想:
划分:划分为当前的数对应最大的2的次方:
递归:如果可以继续划分就滑下去,每次划分的时候输出2(,递归结束回来的时候输出);
合并:每次问题调用的时候用+连接
代码:

#include<iostream>
#include<cmath>
using namespace std;
int a;
void fff(int x)
{
    
    
    for(int i=14;i>=0;--i)
    {
    
    
    	if(pow(2,i)<=x)//找到了这个数
    	{
    
    
    		if(i==0) cout<<"2(0)";//
    		else if(i==1) cout<<2;
    		else 
    		{
    
    
    			cout<<"2(";
    			fff(i);
    			cout<<")";
			}
			x-=pow(2,i);
			if(x!=0) cout<<"+";
		}
	}
}
int main()
{
    
    
    cin>>a;
    fff(a);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/m0_51373056/article/details/109860825