【一】算法入门分析----分治算法

一、基本概念

字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)

二、基本思想及策略

分治法的设计思想是:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。

分治策略是:对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。这种算法设计策略叫做分治法。

如果原问题可分割成k个子问题,1<k≤n,且这些子问题都可解并可利用这些子问题的解求出原问题的解,那么这种分治法就是可行的。由分治法产生的子问题往往是原问题的较小模式,这就为使用递归技术提供了方便。在这种情况下,反复应用分治手段,可以使子问题与原问题类型一致而其规模却不断缩小,最终使子问题缩小到很容易直接求出其解。这自然导致递归过程的产生。分治与递归像一对孪生兄弟,经常同时应用在算法设计之中,并由此产生许多高效算法。

三、分治法适用的情况

分治法所能解决的问题一般具有以下几个特征:
1) 该问题的规模缩小到一定的程度就可以容易地解决
2) 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。
3) 利用该问题分解出的子问题的解可以合并为该问题的解;
4) 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。

第一条特征是绝大多数问题都可以满足的,因为问题的计算复杂性一般是随着问题规模的增加而增加;
第二条特征是应用分治法的前提它也是大多数问题可以满足的,此特征反映了递归思想的应用;
第三条特征是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一条和第二条特征,而不具备第三条特征,则可以考虑用贪心法或动态规划法。
第四条特征涉及到分治法的效率,如果各子问题是不独立的则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然可用分治法,但一般用动态规划法较好。

四、分治法的基本步骤

分治法在每一层递归上都有三个步骤:
1.分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
2.解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题;
== 3.合并:将各个子问题的解合并为原问题的 。==
它的一般的算法设计模式如下:

Divide-and-Conquer(P){
	if  |P| ≤ N{
		then return(ADHOC(P))else{
		将P分解为较小的子问题 P1 ,P2 ,...,Pk
		for i←1 to k
			do yi ← Divide-and-Conquer(Pi) // 递归解决Pi
		T ← MERGE(y1,y2,...,yk) // 合并子问题return(T)
}

其中|P|表示问题P的规模;
N为一阈值,表示当问题P的规模不超过N时,问题已容易直接解出,不必再继续分解。
ADHOC§是该分治法中的基本子算法,用于直接解小规模的问题P。
因此,当P的规模不超过N时直接用算法ADHOC§求解。
算法MERGE(y1,y2,…,yk)是该分治法中的合并子算法,用于将P的子问题P1 ,P2 ,…,Pk的相应的解y1,y2,…,yk合并为P的解。

五、分治法的复杂性分析

一个分治法将规模为n的问题分成k个规模为n/m的子问题去解。设分解阀值N=1,且adhoc解规模为1的问题耗费1个单位时间。再设将原问题分解为k个子问题以及用merge将k个子问题的解合并为原问题的解需用f(n)个单位时间。用T(n)表示该分治法解规模为|P|=n的问题所需的计算时间,则有:T(n)= k T(n/m)+f(n)
通过迭代法求得方程的解:
递归方程及其解只给出n等于m的方幂时T(n)的值,但是如果认为T(n)足够平滑,那么由n等于m的方幂时T(n)的值可以估计T(n)的增长速度。通常假定T(n)是单调上升的,从而当mi≤n<mi+1时,T(mi)≤T(n)<T(mi+1)。

六、依据分治法设计程序时的思维过程

实际上就是类似于数学归纳法,找到解决本问题的求解方程公式,然后根据方程公式设计递归程序。
1、一定是先找到最小问题规模时的求解方法
2、然后考虑随着问题规模增大时的求解方法
3、找到求解的递归函数式后(各种规模或因子),设计递归程序即可。

七、可使用分治法求解的一些经典问题

(1)二分搜索
(2)大整数乘法
(3)Strassen矩阵乘法
(4)棋盘覆盖
(5)合并排序
(6)快速排序
(7)线性时间选择
(8)最接近点对问题
(9)循环赛日程表
(10)汉诺塔

(1)二分搜索

public static int binSearch(int[] arr,int k,int left,int right){
        if(left<=right) {

            int mid = (right + left)/2;
            if(arr[mid] == k){
                return mid;
            }else if(arr[mid] < k){
                return binSearch(arr,k,mid+1,right);
            }else{
                return binSearch(arr,k,left,mid);
            }
        }
        return -1;
    }

(2)大整数乘法

大整数相乘:A、B两个整数,A有n位(123456……n),B有m位(123456……m),求A*B的结果
我们把整数A由规模n分为n1和n2,把整数B由规模m分为m1和m2,如下图:
在这里插入图片描述

则A分为n1位的A1和n2位,B分为m1位的B1和m2位的B2,如下式所示:
在这里插入图片描述
以此类推,我们可以把A1、A2、B1、B2继续划分,直至最小单位。(这里在编程时需要用递归来实现)
那么A和B的相乘就可以表示为:
在这里插入图片描述
现在是要计算四个大整数相乘,我们可以通过变换使得上式变成三个大整数相乘,如下式:
在这里插入图片描述

import java.math.BigInteger;
import java.util.Scanner;
import org.stone.stack.MyBigAdd;
/**
 * @ClassName_MyBigIntegerMutiply
 * @author_Stone6762
 * @CreationTime_2016年10月22日 下午3:39:57
 * @Description_ 大整数乘法A x B    AB可以为不同的位数 
 * 采用的是: 分治的思想,二分 
 */
public class MyBigIntegerMutiply1 {

    /**
     * @Description:未优化的大数相乘
     * @param a
     * @param b
     * @return a*b={a1*10^(n1/2)+a0}*{b1*10^(n2/2)+b0} 
     */
    public static String Mutiply1(String a, String b)// 用字符串读入2个大整数
    {
        String result = "";
        if (a.length() == 1 || b.length() == 1)// 递归结束的条件
                       //其中一个长度为1,另一个不一定
            result = "" + Long.valueOf(a) * Long.valueOf(b);
        else// 如果2个字符串的长度都 >= 2
        {
            //1.分割成  a1  a0  b1  b0
            int lengthA0 = a.length() / 2;
            int lengthA1=a.length()-lengthA0;
            String a1 = a.substring(0, lengthA1); // 截取前一半的字符串(较短的一半)
            String a0 = a.substring(lengthA1, a.length()); // 截取后一半的字符串

            int lengthB0 = b.length() / 2;
            int lengthB1=b.length()-lengthB0;
            String b1 = b.substring(0, lengthB1);
            String b0 = b.substring(lengthB1, b.length());
            // * a*b=
            // * (a1*b1)* 10^[(n1+n2)/2 ]
            // * +(a1*b0)*10^(n1/2)
            // * +(a0*b1)*10^(n2/2)
            // * +(a0*b0)
            //2.计算展开式中的乘法
            String a1b1 = Mutiply1(a1, b1);
            String a1b0 = Mutiply1(a1, b0);
            String a0b1 = Mutiply1(a0, b1);
            String a0b0 = Mutiply1(a0, b0);

            //3.模拟移位
            String resulta1b1 = a1b1;
            for (int i = 0; i < lengthA0+lengthB0; i++) {
                resulta1b1 += "0";
            }
            String resulta1b0 = a1b0;
            for (int i = 0; i <lengthA0; i++) {
                resulta1b0 += "0";
            }
            String resulta0b1 = a0b1;
            for (int i = 0; i < lengthB0; i++) {
                resulta0b1 += "0";
            }   
            //4.大数相加
            result = MyBigAdd.add(resulta1b1, resulta1b0);
            result = MyBigAdd.add(result, resulta0b1);
            result = MyBigAdd.add(result, a0b0);
        }
        return result;
    }

    /** 
     * @Description拿BigInteger自身大数相乘来判断自身算法的正确与否
     * @param args
     */
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        while (scan.hasNext()) {

            String aString=scan.next();
            String bString=scan.next();

            BigInteger aBigInteger=new BigInteger(aString);
            BigInteger bBigInteger=new BigInteger(bString);

            String reslut1=aBigInteger.multiply(bBigInteger).toString();
            String result2=Mutiply1(aString, bString);

            System.out.println("标准答案:  "+reslut1);
            System.out.println("计算结果:  "+result2);

            System.out.println("结果是否正确:  "+reslut1.equals(result2));
        }

    }
}

(3)汉诺塔问题

public class Hanoi {
    
    //使用递归法求解含有n个不同大小盘子的汉诺塔移动路径,参数n为盘子数,把A塔上盘子全部移动到C塔上,B为过渡塔
    public static void recursionHanoi(int n,char A,char B,char C){
        if(n == 1){
            System.out.print(A+"——>"+C+"\n");    
        }else{
            recursionHanoi(n-1,A,C,B);         //使用递归先把A塔最上面的n-1个盘子移动到B塔上,C为过渡塔
            System.out.print(A+"——>"+C+"\n");       //把A塔中底下最大的圆盘,移动到C塔上
            recursionHanoi(n-1,B,A,C);         //使用递归把B塔上n-1个盘子移动到C塔上,A为过渡塔
        }
    }

   public static void main(String[] args){
        System.out.println("请输入盘子总数n:");
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();    
        recursionHanoi(n,'A','B','C');    
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值