分治法在实际应用中是很常用的,分治算法一般会有两种情况:
第一种情况就是将值返回回来使用;第二种就是重点在过程,出口仅仅可能就是一个return;
本篇是介绍一个分治算法的简单应用:选最大和最小
1、选最大算法
这个算法,也是在接触编程语言的时候敲过的最多的算法。
选最大算法就是循环遍历一遍数组,然后将数组中的值和max进行比较。最后输出max,即是原问题的解。
具体代码分析就不再详细介绍了。
这里先写个伪码:
max<-a[1]
for i<-2 to n do
if max<a[i]
then max<-a[i]
return max
具体的代码实现是这样的:
public static void main(String[] args) {
// TODO Auto-generated method stub
int a[]=new int[11];
Random random=new Random();
for(int i=0;i<a.length;i++)
a[i]=random.nextInt(101); //随机生成11个0到100的数字
System.out.println("生成的结果是:"+Arrays.toString(a));
int k=Findmax(a);
}
//找最大数
public static int Findmax(int a[]) {
int maxi=0;
for(int i=1;i<a.length;i++) {
if(a[maxi]<a[i]) {
maxi=i;
}
}
return maxi;
}
我们经过代码分析得出:数组从1到n-1遍历,每个遍历都要进行1次比较。最后选出最大值返回。因此得出T(n)=n-1。
2、选最大和最小
蛮力法
刚刚在上面介绍了选最大算法。而对于最小,我们也可以用那样的算法找到最小值。
也就是我们可以先找到最大值,然后将最大值去掉,然后再循环数组找到最小值
当然我们也可以这样做,直接将max和数组元素进行比较,如果元素比max大,则将a[i]给max;否则就说明这个元素可能会很小,就去将元素和min进行比较。最后找到max,min
因此编写代码得:
public static void main(String[] args) {
// TODO Auto-generated method stub
int a[]=new int[11];
Random random=new Random();
for(int i=0;i<a.length;i++)
a[i]=random.nextInt(101); //随机生成11个0到100的数字
System.out.println("生成的结果是:"+Arrays.toString(a));
SelectmaxAndmin(a);
}
//找最大和最小的数
public static void SelectmaxAndmin(int a[]) {
if(a[0]>a[1]) {
maxi=0;
mini=1;
}
else {
maxi=1;
mini=0;
}
for(int i=2;i<a.length;i++) {
if(a[maxi]<a[i])
maxi=i;
else if(a[mini]>a[i]) {
mini=i;
}
}
System.out.println("最大数是:"+a[maxi]+",最小数是:"+a[mini]);
}
System.out.println("最大数是:"+a[maxi]+",最小数是:"+a[mini]);
}
对蛮力法进行分析
我们看到啊,一开始在对a[0],a[1]做比较的目的:减少一次在循环中的比较。
我们将2到n-1进入循环比较。我们考虑一种情况。假设a[0]就是最大值,那么是不是后面的从a[2]开始的这n-2个数都进入了循环体中else if进行比较。当然我们也能看到if肯定也进入了。那么我们分析出进入if的比较次数是n-2次,进入else if的也是n-2次。而一开始的判断是1次。那么我们分析出T(n)=n-2+n-2+1=2n-3
那么有没有一种方法进行改进算法呢,当然是有的
分组法
对于分组法的算法是这样的:
1、我们将数组里面的元素两两分组,进行比较。大的进入max数组里,小的进入min数组里
两两分组中,如果遇到孤立元素,则这个孤立元素不必进行分组
2、在max数组里和孤立元素中找出最大值,在min数组和孤立元素中找出最小值。找出的最大值和最小值就是问题的解
我们进行代码分析,我们能够看到是将数组里面的元素进行两两分组,因此我们就能得出max[n/2],min[n/2]
分组中是要两两进行比较,i=0,i<n-1,i+=2 a[i]和a[i+1]进行比较,谁大进入max里,谁小进入min里。为什么是i<n-1,这是因为最后一个一定是a[n-2]和a[n-1]的比较。刚刚好不会涉及到a[n]这种错误的表示
然后对于分好组后的遍历,我们要进行分类讨论。如果n是奇数,最后最大和最小都要加一次和孤立元素的比较;如果n是偶数,最后直接得出问题的解
我们看下代码:
public static void main(String[] args) {
// TODO Auto-generated method stub
int a[]=new int[11];
Random random=new Random();
for(int i=0;i<a.length;i++)
a[i]=random.nextInt(101); //随机生成11个0到100的数字
System.out.println("生成的结果是:"+Arrays.toString(a));
SelectmaxAndmin2(a);
}
//分组代码
public static void SelectmaxAndmin2(int a[]) {
int num1=0,num2=0;
int max[]=new int[a.length/2];
int min[]=new int[a.length/2];
int pmax=-0x3f3f3f3f;
int pmin=0x3f3f3f3f;
//分组
for(int i=0;i<a.length-1;i+=2) {
if(a[i]<a[i+1]) {
max[num1++]=a[i+1];
min[num2++]=a[i];
}
else {
max[num1++]=a[i];
min[num2++]=a[i+1];
}
}
//遍历两个数组
if(a.length%2!=0) {
for(int i=0;i<max.length;i++) {
if(pmax<max[i]) {
pmax=max[i];
}
}
if(pmax<a[a.length-1])
pmax=a[a.length-1];
for(int i=0;i<min.length;i++) {
if(pmin>min[i]) {
pmin=min[i];
}
}
if(pmin>a[a.length-1])
pmin=a[a.length-1];
}
else {
for(int i=0;i<max.length;i++) {
if(pmax<max[i]) {
pmax=max[i];
}
}
for(int i=0;i<min.length;i++) {
if(pmin>min[i]) {
pmin=min[i];
}
}
}
System.out.println("最大值是:"+pmax+",最小值是:"+pmin);
}
我们对分组法进行分析:
对这种算法进行时间复杂度分析,就是分析比较次数
分组中的比较次数是n/2次
如果是奇数我们在对max和min数组和孤立元素进行遍历比较的时候,我们按照找最值2(⌈n/2⌉-1)(上取整是因为会多加一次和孤立元素进行比较)
最后得出了T(n)=n/2+2(⌈n/2⌉-1)=n/2+2⌈n/2⌉-2=⌈3n/2⌉-2
我们发现分组算法比蛮力算法的T(n)要低
分治算法
分治算法是这样的:
1、首先将数组一分为二为L1,L2
2、递归的在L1中寻找max1,min1
3、递归的在L2中寻找max2,min2
4、max=MAX(max1,max2)
5、min=MIN(min1,min2)
得出的max和min是问题的解
对于类似这样分治算法我们可以去考虑如下写法:
1、定划分,我们考虑将整体划分成两个规模较小的与原问题同性质的子问题。然后
else{划分代码+递归代码}
2、定出口:if(出口标志){出口代码(1、返回值初值类型2、返回出口,即直接写一个return;)}
3、归结解:将所有的解组合到一个大的集合。
1、我们将通过递归改变的小集合传到递归参数里。然后将这些小的集合组合到一个大集合里
2、我们直接传一个大集合到递归参数里,通过各种变换直接输出大集合,即大集合就是原问题的解
3、可以将大数组定义成静态类型,这样在递归变化时,可以时刻调用或改变大集合里面的内容,最后将大集合输出
这里就不再进行详细分析,大家可以尝试分析下:
public static int fmax=-0x3f3f3f3f; //记录最大值
public static int fmin=0x3f3f3f3f; //记录最小值
public static void main(String[] args) {
// TODO Auto-generated method stub
int a[]=new int[11];
Random random=new Random();
for(int i=0;i<a.length;i++)
a[i]=random.nextInt(101); //随机生成11个0到100的数字
System.out.println("生成的结果是:"+Arrays.toString(a));
SelectmaxAndmin3(a, 0, a.length-1);
System.out.println("最大值:"+fmax+",最小值是:"+fmin);
}
//分治法
public static void SelectmaxAndmin3(int a[],int l,int r) {
if(r==l) {
fmax=Math.max(fmax, a[l]);
fmin=Math.min(fmin, a[l]);
return;
}
else if(l+1==r) {
fmax=Math.max(Math.max(a[l], a[r]), fmax);
fmin=Math.min(Math.min(a[l], a[r]), fmin);
return;
}
else {
int mid=(l+r)>>1;
SelectmaxAndmin3(a, l, mid);
SelectmaxAndmin3(a, mid+1, r);
}
}
最后我们再对分治法进行分析:
对于这个分析还是通过计量递归来分析
T(n)=2T(n/2)+2 (2是两次寻找max和min)
T(2)=1
通过迭代法能够分析出T(n)=3n/2 -2
我们能够分析出:分治算法和分组算法一样,T(n)是一样的值
尽管时间复杂度都是O(n),但是后两个方法却稍稍能提高效率
3、分治算法总结
分治算法就是将原问题划分成与原问题相同性质的子问题,直接划分注意尽量均衡。子问题通过计算得出的问题的解归结成原问题的解。算法实现要注意递归或迭代实现
分治算法的时间复杂度分析,给出关于时间复杂度函数的递推方程和初值。
分治算法的时间复杂度就是原问题规约成子问题中,原问题的工作量就是所有子问题工作量之和+规约和综合解的工作量之和
改进分治算法的途径就是:
1、减少子问题的个数(降低a)
2、增加预处理(降低f(n))
分治算法举例:
检索算法:二分检索
排序算法:快速排序、二分归并排序
选择算法:第k小问题
凸包问题以及快速傅里叶变换FFT算法
学分治算法是学它的思想和分治方法以及在代码中是如何应用、如何划分的
以上就是分治算法的介绍